From 15f7c30516facfb272fc116aec4d3f9c3a9de085 Mon Sep 17 00:00:00 2001 From: Robert Roebling Date: Sat, 8 Dec 2007 11:37:17 +0000 Subject: [PATCH] Added wxWrapSizer (modified patch: [1826950] Wrapping Sizer) from Arne Steinarson git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@50568 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- docs/latex/wx/category.tex | 1 + docs/latex/wx/classes.tex | 1 + docs/latex/wx/sizer.tex | 6 +- docs/latex/wx/wrapsizer.tex | 53 +++++ include/wx/sizer.h | 43 ++++ include/wx/window.h | 8 + samples/layout/layout.cpp | 62 ++++- samples/layout/layout.h | 20 +- src/common/sizer.cpp | 447 ++++++++++++++++++++++++++++++++++-- 9 files changed, 608 insertions(+), 33 deletions(-) create mode 100644 docs/latex/wx/wrapsizer.tex diff --git a/docs/latex/wx/category.tex b/docs/latex/wx/category.tex index 1a18ca2aae..ff9602fd90 100644 --- a/docs/latex/wx/category.tex +++ b/docs/latex/wx/category.tex @@ -187,6 +187,7 @@ These are the classes relevant to sizer-based layout. \twocolitem{\helpref{wxGridBagSizer}{wxgridbagsizer}}{Another grid sizer that lets you specify the cell an item is in, and items can span rows and/or columns.} \twocolitem{\helpref{wxBoxSizer}{wxboxsizer}}{A sizer for laying out windows in a row or column} \twocolitem{\helpref{wxStaticBoxSizer}{wxstaticboxsizer}}{Same as wxBoxSizer, but with a surrounding static box} +\twocolitem{\helpref{wxWrapSizer}{wxwrapsizer}}{A sizer which wraps its child controls as size permits} \end{twocollist} \overview{Constraints overview}{constraintsoverview} describes constraints-based layout. diff --git a/docs/latex/wx/classes.tex b/docs/latex/wx/classes.tex index 9754c13f47..68dbe32439 100644 --- a/docs/latex/wx/classes.tex +++ b/docs/latex/wx/classes.tex @@ -458,6 +458,7 @@ \input vscrolledwindow.tex \input window.tex \input wupdlock.tex +\input wrapsizer.tex \input createevt.tex \input windowdc.tex \input destroyevt.tex diff --git a/docs/latex/wx/sizer.tex b/docs/latex/wx/sizer.tex index f4c4f94bc5..36c801e152 100644 --- a/docs/latex/wx/sizer.tex +++ b/docs/latex/wx/sizer.tex @@ -4,8 +4,10 @@ wxSizer is the abstract base class used for laying out subwindows in a window. Y cannot use wxSizer directly; instead, you will have to use one of the sizer classes derived from it. Currently there are \helpref{wxBoxSizer}{wxboxsizer}, \helpref{wxStaticBoxSizer}{wxstaticboxsizer}, -\helpref{wxGridSizer}{wxgridsizer} -\helpref{wxFlexGridSizer}{wxflexgridsizer} and \helpref{wxGridBagSizer}{wxgridbagsizer}. +\helpref{wxGridSizer}{wxgridsizer}, +\helpref{wxFlexGridSizer}{wxflexgridsizer}, +\helpref{wxWrapSizer}{wxwrapsizer} + and \helpref{wxGridBagSizer}{wxgridbagsizer}. The layout algorithm used by sizers in wxWidgets is closely related to layout in other GUI toolkits, such as Java's AWT, the GTK toolkit or the Qt toolkit. It is diff --git a/docs/latex/wx/wrapsizer.tex b/docs/latex/wx/wrapsizer.tex new file mode 100644 index 0000000000..46d5e03bc3 --- /dev/null +++ b/docs/latex/wx/wrapsizer.tex @@ -0,0 +1,53 @@ +\section{\class{wxWrapSizer}}\label{wxwrapsizer} + +A wrap sizer lays out its items in a single line, like a box sizer - as long +as there is space available in that direction. Once all available space in +the primary direction has been used a new line is added an items are added there. + +So a wrap sizer has a primary orientation for adding items, and adds lines +as needed in the secondary direction. + +\wxheading{Derived from} + +\helpref{wxBoxSizer}{wxboxsizer}\\ +\helpref{wxSizer}{wxsizer}\\ +\helpref{wxObject}{wxobject} + +\wxheading{Include files} + + + +\wxheading{Library} + +\helpref{wxCore}{librarieslist} + +\wxheading{See also} + +\helpref{wxBoxSizer}{wxboxsizer}, \helpref{wxSizer}{wxsizer}, \helpref{Sizer overview}{sizeroverview} + + + +\latexignore{\rtfignore{\wxheading{Members}}} + +\membersection{wxWrapSizer::wxWrapSizer}\label{wxwrapsizerwxwrapsizer} + +\func{}{wxWrapSizer}{\param{int }{orient}, \param{int }{flags}} + +Constructor for a wxWrapSizer. {\it orient} determines the primary direction of +the sizer (the most common case being wxHORIZONTAL). The flags parameter may have +the value wxEXTEND_LAST_ON_EACH_LINE. This will cause the last item one each line +to use any remaining space on that line. + +\membersection{wxWrapSizer::InformFirstDirection}\label{wxwrapsizerinformfirstdirection} + +\func{bool}{InformFirstDirection}{\param{int }{direction}, \param{int }{size}, +\param{int }{availableOtherDir }} + +Not used by an application. This is the mechanism by which sizers can inform +sub-items of the first determined size component. The sub-item can then better +determine its size requirements. + +Returns true if the information was used (and the sub-item min size was updated). + + + diff --git a/include/wx/sizer.h b/include/wx/sizer.h index 4b88513ecb..332b08d804 100644 --- a/include/wx/sizer.h +++ b/include/wx/sizer.h @@ -345,6 +345,12 @@ public: wxPoint GetPosition() const { return m_pos; } + // Called once the first component of an item has been decided. This is + // used in algorithms that depend on knowing the size in one direction + // before the min size in the other direction can be known. + // Returns true if it made use of the information (and min size was changed). + bool InformFirstDirection( int direction, int size, int availableOtherDir=-1 ); + // these functions delete the current contents of the item if it's a sizer // or a spacer but not if it is a window void AssignWindow(wxWindow *window) @@ -551,6 +557,11 @@ 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 informtion (and recalculated min size) + 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 ) @@ -748,6 +759,7 @@ public: protected: void AdjustForFlexDirection(); void AdjustForGrowables(const wxSize& sz); + void FindWidthsAndHeights(int nrows, int ncols); // the heights/widths of all rows/columns wxArrayInt m_rowHeights, @@ -860,6 +872,37 @@ private: DECLARE_CLASS(wxBoxSizer) }; +//--------------------------------------------------------------------------- +// wxWrapSizer - A box sizer that can wrap items on several lines when +// widths exceed available width. +//--------------------------------------------------------------------------- + +// Borrow unused flag value +#define wxEXTEND_LAST_ON_EACH_LINE wxFULL_REPAINT_ON_RESIZE + +class WXDLLEXPORT wxWrapSizer: public wxBoxSizer +{ +public: + wxWrapSizer( int orient=wxHORIZONTAL, int flags=wxEXTEND_LAST_ON_EACH_LINE ); + virtual ~wxWrapSizer(); + + virtual void RecalcSizes(); + virtual wxSize CalcMin(); + + virtual bool InformFirstDirection( int direction, int size, int availableOtherDir ); + +protected: + int m_prim_size_last; // Size in primary direction last time + int m_n_line; // Number of lines + wxBoxSizer m_rows; // Rows of items + int m_flags; + + void AdjustPropLastItem(wxSizer *psz, wxSizerItem *itemLast); + +private: + DECLARE_DYNAMIC_CLASS(wxWrapSizer); +}; + //--------------------------------------------------------------------------- // wxStaticBoxSizer //--------------------------------------------------------------------------- diff --git a/include/wx/window.h b/include/wx/window.h index 852c4c3b59..f0db87e135 100644 --- a/include/wx/window.h +++ b/include/wx/window.h @@ -500,6 +500,14 @@ public: // components of the result respectively 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). + virtual bool InformFirstDirection( int WXUNUSED(direction), int WXUNUSED(size), int WXUNUSED(availableOtherDir) ) + { return false; } // window state // ------------ diff --git a/samples/layout/layout.cpp b/samples/layout/layout.cpp index 606813fc27..110b6ef213 100644 --- a/samples/layout/layout.cpp +++ b/samples/layout/layout.cpp @@ -66,6 +66,7 @@ BEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_MENU(LAYOUT_TEST_GB_SIZER, MyFrame::TestGridBagSizer) EVT_MENU(LAYOUT_TEST_SET_MINIMAL, MyFrame::TestSetMinimal) EVT_MENU(LAYOUT_TEST_NESTED, MyFrame::TestNested) + EVT_MENU(LAYOUT_TEST_WRAP, MyFrame::TestWrap) END_EVENT_TABLE() // Define my frame constructor @@ -83,6 +84,7 @@ MyFrame::MyFrame() file_menu->Append(LAYOUT_TEST_GB_SIZER, _T("Test &gridbag sizer...\tF4")); file_menu->Append(LAYOUT_TEST_SET_MINIMAL, _T("Test Set&ItemMinSize...\tF5")); file_menu->Append(LAYOUT_TEST_NESTED, _T("Test nested sizer in a wxPanel...\tF6")); + file_menu->Append(LAYOUT_TEST_WRAP, _T("Test wrap sizers...\tF6")); file_menu->AppendSeparator(); file_menu->Append(LAYOUT_QUIT, _T("E&xit"), _T("Quit program")); @@ -211,6 +213,12 @@ void MyFrame::TestNested(wxCommandEvent& WXUNUSED(event) ) newFrame->Show(true); } +void MyFrame::TestWrap(wxCommandEvent& WXUNUSED(event) ) +{ + MyWrapSizerFrame *newFrame = new MyWrapSizerFrame(_T("Wrap Sizer Test Frame"), 50, 50); + newFrame->Show(true); +} + void MyFrame::OnAbout(wxCommandEvent& WXUNUSED(event) ) { @@ -306,7 +314,7 @@ void MyFlexSizerFrame::InitFlexSizer(wxFlexGridSizer *sizer, wxWindow* parent) } } -MyFlexSizerFrame::MyFlexSizerFrame(const wxChar *title, int x, int y ) +MyFlexSizerFrame::MyFlexSizerFrame(const wxString &title, int x, int y ) : wxFrame(NULL, wxID_ANY, title, wxPoint(x, y) ) { wxFlexGridSizer *sizerFlex; @@ -387,7 +395,7 @@ MyFlexSizerFrame::MyFlexSizerFrame(const wxChar *title, int x, int y ) // MySizerDialog // ---------------------------------------------------------------------------- -MySizerDialog::MySizerDialog(wxWindow *parent, const wxChar *title) +MySizerDialog::MySizerDialog(wxWindow *parent, const wxString &title) : wxDialog(parent, wxID_ANY, wxString(title)) { // Begin with first hierarchy: a notebook at the top and @@ -461,7 +469,7 @@ BEGIN_EVENT_TABLE(MyGridBagSizerFrame, wxFrame) END_EVENT_TABLE() -MyGridBagSizerFrame::MyGridBagSizerFrame(const wxChar *title, int x, int y ) +MyGridBagSizerFrame::MyGridBagSizerFrame(const wxString &title, int x, int y ) : wxFrame( NULL, wxID_ANY, title, wxPoint(x, y) ) { wxPanel* p = new wxPanel(this, wxID_ANY); @@ -575,7 +583,7 @@ BEGIN_EVENT_TABLE(MySimpleSizerFrame, wxFrame) EVT_MENU( ID_SET_BIG, MySimpleSizerFrame::OnSetBigSize) END_EVENT_TABLE() -MySimpleSizerFrame::MySimpleSizerFrame(const wxChar *title, int x, int y ) +MySimpleSizerFrame::MySimpleSizerFrame(const wxString &title, int x, int y ) : wxFrame( NULL, wxID_ANY, title, wxPoint(x, y) ) { wxMenu *menu = new wxMenu; @@ -621,7 +629,7 @@ void MySimpleSizerFrame::OnSetBigSize( wxCommandEvent& WXUNUSED(event)) // ---------------------------------------------------------------------------- -MyNestedSizerFrame::MyNestedSizerFrame(const wxChar *title, int x, int y ) +MyNestedSizerFrame::MyNestedSizerFrame(const wxString &title, int x, int y ) : wxFrame( NULL, wxID_ANY, title, wxPoint(x, y) ) { wxMenu *menu = new wxMenu; @@ -661,3 +669,47 @@ MyNestedSizerFrame::MyNestedSizerFrame(const wxChar *title, int x, int y ) GetSizer()->SetSizeHints( this ); } + +// ---------------------------------------------------------------------------- +// MyWrapSizerFrame +// ---------------------------------------------------------------------------- + + +MyWrapSizerFrame::MyWrapSizerFrame(const wxString &title, int x, int y ) + : wxFrame( NULL, wxID_ANY, title, wxPoint(x, y) ) +{ + wxMenu *menu = new wxMenu; + + menu->Append(wxID_ABOUT, "Do nothing"); + + wxMenuBar *menu_bar = new wxMenuBar; + menu_bar->Append(menu, "&File"); + + SetMenuBar( menu_bar ); + + wxBoxSizer *root = new wxBoxSizer( wxVERTICAL ); + + // A number of checkboxes inside a wrap sizer + wxSizer *ps_mid = new wxStaticBoxSizer( wxVERTICAL, this, "Wrapping check-boxes" ); + wxSizer *ps_mid_wrap = new wxWrapSizer(wxHORIZONTAL); + ps_mid->Add( ps_mid_wrap, 100, wxEXPAND ); + for( int ix=0; ix<6; ix++ ) + ps_mid_wrap->Add( new wxCheckBox(this,wxID_ANY,wxString::Format("Option %d",ix+1)), 0, wxALIGN_CENTRE|wxALIGN_CENTER_VERTICAL, 5 ); + root->Add( ps_mid, 0, wxEXPAND | wxALL, 5 ); + + // A shaped item inside a box sizer + wxSizer *ps_bottom = new wxStaticBoxSizer( wxVERTICAL, this, "With wxSHAPED item" ); + wxSizer *ps_bottom_box = new wxBoxSizer(wxHORIZONTAL); + ps_bottom->Add( ps_bottom_box, 100, wxEXPAND ); + ps_bottom_box->Add( new wxListBox(this,wxID_ANY,wxPoint(0,0),wxSize(70,70)), 0, wxEXPAND|wxSHAPED ); + ps_bottom_box->Add( 10,10 ); + ps_bottom_box->Add( new wxCheckBox(this,wxID_ANY,"A much longer option..."), 100, 0, 5 ); + + root->Add( ps_bottom, 1, wxEXPAND | wxALL, 5 ); + + // Set sizer for window + SetSizer( root ); + root->Fit( this ); + root->SetSizeHints( this ); +} + diff --git a/samples/layout/layout.h b/samples/layout/layout.h index 4c0aa56fa6..d7a8f8ef72 100644 --- a/samples/layout/layout.h +++ b/samples/layout/layout.h @@ -29,6 +29,7 @@ public: void TestGridBagSizer(wxCommandEvent& event); void TestNested(wxCommandEvent& event); void TestSetMinimal(wxCommandEvent& event); + void TestWrap(wxCommandEvent& event); void OnAbout(wxCommandEvent& event); void OnQuit(wxCommandEvent& event); @@ -57,7 +58,7 @@ protected: class MyFlexSizerFrame : public wxFrame { public: - MyFlexSizerFrame(const wxChar *title, int x, int y ); + MyFlexSizerFrame(const wxString &title, int x, int y ); private: void InitFlexSizer(wxFlexGridSizer *sizer, wxWindow* parent); @@ -68,7 +69,7 @@ private: class MySizerDialog : public wxDialog { public: - MySizerDialog(wxWindow *parent, const wxChar *title); + MySizerDialog(wxWindow *parent, const wxString &title ); }; @@ -76,7 +77,7 @@ public: class MyGridBagSizerFrame : public wxFrame { public: - MyGridBagSizerFrame(const wxChar *title, int x, int y ); + MyGridBagSizerFrame(const wxString &title, int x, int y ); void OnHideBtn(wxCommandEvent&); void OnShowBtn(wxCommandEvent&); @@ -101,7 +102,7 @@ private: class MySimpleSizerFrame : public wxFrame { public: - MySimpleSizerFrame(const wxChar *title, int x, int y ); + MySimpleSizerFrame(const wxString &title, int x, int y ); void OnSetSmallSize( wxCommandEvent &event); void OnSetBigSize( wxCommandEvent &event); @@ -119,13 +120,21 @@ private: class MyNestedSizerFrame : public wxFrame { public: - MyNestedSizerFrame(const wxChar *title, int x, int y ); + MyNestedSizerFrame(const wxString &title, int x, int y ); private: wxTextCtrl *m_target; }; +// a frame with several wrapping sizers + +class MyWrapSizerFrame: public wxFrame +{ +public: + MyWrapSizerFrame(const wxString &title, int x, int y ); +}; + // controls and menu constants enum { @@ -135,6 +144,7 @@ enum LAYOUT_TEST_PROPORTIONS, LAYOUT_TEST_SET_MINIMAL, LAYOUT_TEST_NESTED, + LAYOUT_TEST_WRAP, LAYOUT_QUIT = wxID_EXIT, LAYOUT_ABOUT = wxID_ABOUT }; diff --git a/src/common/sizer.cpp b/src/common/sizer.cpp index 3b1a77280a..3b18a56b59 100644 --- a/src/common/sizer.cpp +++ b/src/common/sizer.cpp @@ -272,6 +272,73 @@ wxSize wxSizerItem::GetSize() const return ret; } +bool wxSizerItem::InformFirstDirection(int direction, int size, int availableOtherDir) +{ + // The size that come here will be including borders. Child items should get it + // without borders. + if( size>0 ) + { + if( direction==wxHORIZONTAL ) + { + if (m_flag & wxWEST) + size -= m_border; + if (m_flag & wxEAST) + size -= m_border; + } + else if( direction==wxVERTICAL ) + { + if (m_flag & wxNORTH) + size -= m_border; + if (m_flag & wxSOUTH) + size -= m_border; + } + } + + bool didUse = false; + // Pass the information along to the held object + if (IsSizer()) + { + didUse = GetSizer()->InformFirstDirection(direction,size,availableOtherDir); + if (didUse) + m_minSize = GetSizer()->CalcMin(); + } + else if (IsWindow()) + { + didUse = GetWindow()->InformFirstDirection(direction,size,availableOtherDir); + if (didUse) + m_minSize = m_window->GetEffectiveMinSize(); + + // This information is useful for items with wxSHAPED flag, since + // we can request an optimal min size for such an item. Even if + // we overwrite the m_minSize member here, we can read it back from + // the owned window (happens automatically). + if( (m_flag & wxSHAPED) && (m_flag & wxEXPAND) && direction ) + { + if( !wxIsNullDouble(m_ratio) ) + { + wxCHECK_MSG( (m_proportion==0), false, _T("Shaped item, non-zero proportion in wxSizerItem::InformFirstDirection()") ); + if( direction==wxHORIZONTAL && !wxIsNullDouble(m_ratio) ) + { + // Clip size so that we don't take too much + if( availableOtherDir>=0 && int(size/m_ratio)-m_minSize.y>availableOtherDir ) + size = int((availableOtherDir+m_minSize.y)*m_ratio); + m_minSize = wxSize(size,int(size/m_ratio)); + } + else if( direction==wxVERTICAL ) + { + // Clip size so that we don't take too much + if( availableOtherDir>=0 && int(size*m_ratio)-m_minSize.x>availableOtherDir ) + size = int((availableOtherDir+m_minSize.x)/m_ratio); + m_minSize = wxSize(int(size*m_ratio),size); + } + didUse = true; + } + } + } + + return didUse; +} + wxSize wxSizerItem::CalcMin() { if (IsSizer()) @@ -1314,6 +1381,35 @@ wxSize wxGridSizer::CalcMin() node = node->GetNext(); } + // In case we have a nested sizer with a two step algo , give it + // a chance to adjust to that (we give it width component) + node = m_children.GetFirst(); + bool didChangeMinSize = false; + while (node) + { + wxSizerItem *item = node->GetData(); + didChangeMinSize |= item->InformFirstDirection( wxHORIZONTAL, w, -1 ); + + node = node->GetNext(); + } + + // And redo iteration in case min size changed + if( didChangeMinSize ) + { + node = m_children.GetFirst(); + w = h = 0; + while (node) + { + wxSizerItem *item = node->GetData(); + wxSize sz( item->GetMinSizeWithBorder() ); + + w = wxMax( w, sz.x ); + h = wxMax( h, sz.y ); + + node = node->GetNext(); + } + } + return wxSize( ncols * w + (ncols-1) * m_hgap, nrows * h + (nrows-1) * m_vgap ); } @@ -1456,16 +1552,8 @@ static int SumArraySizes(const wxArrayInt& sizes, int gap) return total; } -wxSize wxFlexGridSizer::CalcMin() +void wxFlexGridSizer::FindWidthsAndHeights(int nrows, int ncols) { - int nrows, - ncols; - - // Number of rows/columns can change as items are added or removed. - if ( !CalcRowsCols(nrows, ncols) ) - return wxSize(); - - // We have to recalculate the sizes in case the item minimum size has // changed since the previous layout, or the item has been hidden using // wxSizer::Show(). If all the items in a row/column are hidden, the final @@ -1483,7 +1571,9 @@ wxSize wxFlexGridSizer::CalcMin() wxSizerItem * const item = *i; if ( item->IsShown() ) { - const wxSize sz(item->CalcMin()); + // NOTE: Not doing the calculation here, this is just + // for finding max values. + const wxSize sz(item->GetMinSizeWithBorder()); const int row = n / ncols; const int col = n % ncols; @@ -1499,6 +1589,42 @@ wxSize wxFlexGridSizer::CalcMin() m_calculatedMinSize = wxSize(SumArraySizes(m_colWidths, m_hgap), SumArraySizes(m_rowHeights, m_vgap)); +} + +wxSize wxFlexGridSizer::CalcMin() +{ + int nrows, + ncols; + + // Number of rows/columns can change as items are added or removed. + if ( !CalcRowsCols(nrows, ncols) ) + return wxSize(); + + + // We have to recalculate the sizes in case the item minimum size has + // changed since the previous layout, or the item has been hidden using + // wxSizer::Show(). If all the items in a row/column are hidden, the final + // dimension of the row/column will be -1, indicating that the column + // itself is hidden. + m_rowHeights.assign(nrows, -1); + m_colWidths.assign(ncols, -1); + + // n is the index of the item in left-to-right top-to-bottom order + size_t n = 0; + for ( wxSizerItemList::iterator i = m_children.begin(); + i != m_children.end(); + ++i, ++n ) + { + wxSizerItem * const item = *i; + if ( item->IsShown() ) + { + item->CalcMin(); + } + } + + // The stage of looking for max values in each row/column has been + // made a separate function, since it's reused in AdjustForGrowables. + FindWidthsAndHeights(nrows,ncols); return m_calculatedMinSize; } @@ -1616,21 +1742,35 @@ DoAdjustForGrowables(int delta, void wxFlexGridSizer::AdjustForGrowables(const wxSize& sz) { - if ( (m_flexDirection & wxVERTICAL) || (m_growMode != wxFLEX_GROWMODE_NONE) ) + if ( (m_flexDirection & wxHORIZONTAL) || (m_growMode != wxFLEX_GROWMODE_NONE) ) { - // pass NULL instead of proportions if the grow mode is ALL as we - // should treat all rows as having proportion of 1 then DoAdjustForGrowables ( - sz.y - m_calculatedMinSize.y, - m_growableRows, - m_rowHeights, - m_growMode == wxFLEX_GROWMODE_SPECIFIED ? &m_growableRowsProportions + sz.x - m_calculatedMinSize.x, + m_growableCols, + m_colWidths, + m_growMode == wxFLEX_GROWMODE_SPECIFIED ? &m_growableColsProportions : NULL ); + + // This gives nested objects that benefit from knowing one size + // component in advance the chance to use that. + bool didAdjustMinSize = false; + int nrows, ncols; + CalcRowsCols(nrows, ncols); + + // Iterate over all items and inform about column width + size_t n = 0; + for ( wxSizerItemList::iterator i = m_children.begin(); + i != m_children.end(); + ++i, ++n ) + { + const int col = n % ncols; + didAdjustMinSize |= (*i)->InformFirstDirection(wxHORIZONTAL, m_colWidths[col], sz.y - m_calculatedMinSize.y); } - if ( (m_flexDirection & wxHORIZONTAL) || (m_growMode != wxFLEX_GROWMODE_NONE) ) + // Only redo if info was actually used + if( didAdjustMinSize ) { DoAdjustForGrowables ( @@ -1643,6 +1783,21 @@ void wxFlexGridSizer::AdjustForGrowables(const wxSize& sz) } } + if ( (m_flexDirection & wxVERTICAL) || (m_growMode != wxFLEX_GROWMODE_NONE) ) + { + // pass NULL instead of proportions if the grow mode is ALL as we + // should treat all rows as having proportion of 1 then + DoAdjustForGrowables + ( + sz.y - m_calculatedMinSize.y, + m_growableRows, + m_rowHeights, + m_growMode == wxFLEX_GROWMODE_SPECIFIED ? &m_growableRowsProportions + : NULL + ); + } +} + void wxFlexGridSizer::AddGrowableRow( size_t idx, int proportion ) { @@ -1693,15 +1848,47 @@ void wxBoxSizer::RecalcSizes() if ( m_children.empty() ) return; + const wxCoord totalMinorSize = SizeInMinorDir(m_size); + // the amount of free space which we should redistribute among the // stretchable items (i.e. those with non zero proportion) int delta = SizeInMajorDir(m_size) - SizeInMajorDir(m_minSize); + + // Inform child items about the size in minor direction, that can + // change how much free space we have in major dir and how to distribute it. + int majorMinSum = 0; + for ( wxSizerItemList::const_iterator i = m_children.begin(); + i != m_children.end(); + ++i ) + { + wxSizerItem * const item = *i; + + if ( !item->IsShown() ) + continue; + + wxSize szMinPrev = item->GetMinSizeWithBorder(); + item->InformFirstDirection(m_orient^wxBOTH,totalMinorSize,delta); + wxSize szMin = item->GetMinSizeWithBorder(); + int deltaChange = SizeInMajorDir(szMin-szMinPrev); + if( deltaChange ) + { + // Since we passed available space along to the item, it should not + // take too much ,so delat should not become negative. + delta -= deltaChange; + } + majorMinSum += SizeInMajorDir(item->GetMinSizeWithBorder()); + } + // And update our min size + SizeInMajorDir(m_minSize) = majorMinSum; + + + // might have a new delta now + delta = SizeInMajorDir(m_size) - SizeInMajorDir(m_minSize); + // the position at which we put the next child wxPoint pt(m_position); - const wxCoord totalMinorSize = SizeInMinorDir(m_size); - int totalProportion = m_totalProportion; for ( wxSizerItemList::const_iterator i = m_children.begin(); i != m_children.end(); @@ -1711,6 +1898,7 @@ void wxBoxSizer::RecalcSizes() if ( !item->IsShown() ) continue; + #ifndef __DMC__ // DMC doesn't distinguish between // int SizeInMajorDir(const wxSize& sz) const @@ -1719,7 +1907,6 @@ void wxBoxSizer::RecalcSizes() #endif wxSize sizeThis(item->GetMinSizeWithBorder()); - // adjust the size in the major direction using the proportion wxCoord majorSize = SizeInMajorDir(sizeThis); const int propItem = item->GetProportion(); @@ -1802,6 +1989,224 @@ wxSize wxBoxSizer::CalcMin() return m_minSize; } +//--------------------------------------------------------------------------- +// wxWrapSizer +//--------------------------------------------------------------------------- + +#define wxDEFAULT_PROPORTION_LAST 1000000 + +// User data to hold old proportion for last item on line +// (which might be extended) +struct wxPropHolder : public wxObject +{ + wxPropHolder( ) : m_item(0), m_propOld(0) { } + void Init( wxSizerItem *item, int propOld ) { m_item=item; m_propOld=propOld; } + + wxSizerItem *m_item; + int m_propOld; +}; + +IMPLEMENT_DYNAMIC_CLASS(wxWrapSizer, wxBoxSizer); + +wxWrapSizer::wxWrapSizer( int orient, int flags ) + : wxBoxSizer(orient), + m_prim_size_last( -1 ), + m_rows(orient^wxBOTH), + m_flags(flags) +{ +} + +wxWrapSizer::~wxWrapSizer() +{ + // Have to clear grand child items so that they're not deleted twice + for( int ix=m_rows.GetChildren().GetCount()-1; ix>=0; ix-- ) + { + wxSizer *psz = m_rows.GetItem((size_t)ix)->GetSizer(); + wxSizerItemList &sl = psz->GetChildren(); + while( sl.GetLast() ) + sl.Erase( sl.GetLast() ); + } +} + + +bool wxWrapSizer::InformFirstDirection( int direction, int size, int WXUNUSED(availableOtherDir) ) +{ + if( !direction ) + { + // Better to keep value, then CalcMin will work better + //m_prim_size_last = -1; + return false; + } + if( direction==m_orient ) + { + // The direction is same as our primary, so we can make use of it + m_prim_size_last = size; + return true; + } + else + return false; +} + + +void wxWrapSizer::AdjustPropLastItem(wxSizer *psz, wxSizerItem *itemLast) +{ + wxSizerItem *psi = m_rows.GetItem(psz); + wxASSERT(psi); + wxPropHolder *pph = (wxPropHolder*)psi->GetUserData(); + if ( !pph ) + psi->SetUserData( pph=new wxPropHolder ); + + pph->Init( itemLast, itemLast->GetProportion() ); + itemLast->SetProportion( wxDEFAULT_PROPORTION_LAST ); +} + +void wxWrapSizer::RecalcSizes() +{ + wxASSERT( m_orient&wxBOTH ); + if (m_children.GetCount() == 0) + return; + + // What we do here is to put our items into child box sizers, + // as many of them as we have lines. + + // Empty all items in all rows in owned sizer. + // We have to access the list directly, since we don't want to + // destroy the wxSizerItems. + for( int ix=m_rows.GetChildren().GetCount()-1; ix>=0; ix-- ){ + wxSizerItem *psi = m_rows.GetItem( (size_t)ix ); + + // Restore proportion for last item on line (if item has not been deleted) + wxPropHolder *pph = (wxPropHolder*)psi->GetUserData(); + if( pph && GetChildren().Find(pph->m_item) ) + pph->m_item->SetProportion(pph->m_propOld); + + wxSizer *psz = psi->GetSizer(); + wxASSERT( psz ); + wxSizerItemList &sl = psz->GetChildren(); + while( sl.GetLast() ) + sl.Erase( sl.GetLast() ); + } + + int lineSumMajor = 0; + int majorSize = SizeInMajorDir(m_size); + + // Make sure we have at least one child sizer + m_n_line = 1; + if( !m_rows.GetChildren().GetCount() ) + m_rows.Add( new wxBoxSizer(GetOrientation()), 1, wxEXPAND ); + + // The sizer where to insert items in + wxSizer *psz = m_rows.GetItem((size_t)0)->GetSizer(); + wxASSERT( psz ); + + // Now put our child items into child sizers instead + wxSizerItemList::compatibility_iterator node = m_children.GetFirst(); + wxSizerItem *item = NULL, *itemLast=NULL; + while (node) + { + item = node->GetData(); + if ( item->IsShown() ) + { + wxSize minSz = item->GetMinSize(); + int minSzMajor = SizeInMajorDir(minSz); + + // More space on this line? + if( !lineSumMajor || lineSumMajor+minSzMajor<=majorSize ) + { + lineSumMajor += minSzMajor; + } + else + { + lineSumMajor = minSzMajor; + // Get a new empty sizer to insert into + if( (int)m_rows.GetChildren().GetCount()<=m_n_line ) + m_rows.Add( new wxBoxSizer(GetOrientation()), 1, wxEXPAND ); + + // If we have extend-last-on-each-line mode, then do so now + // Note: We must store old proportion value then. + if( m_flags&wxEXTEND_LAST_ON_EACH_LINE ) + AdjustPropLastItem(psz,itemLast); + + // The sizer where to insert items in + psz = m_rows.GetItem(m_n_line++)->GetSizer(); + } + itemLast = item; + psz->Add( item ); + // If item is a window, it now has a pointer to the child sizer, + // which is wrong. Set it to point to us. + if( item->GetWindow() ) + item->GetWindow()->SetContainingSizer( this ); + } + node = node->GetNext(); + } + + // If we have extend-last-on-each-line mode, then do so now + if( m_flags&wxEXTEND_LAST_ON_EACH_LINE ) + AdjustPropLastItem(psz,itemLast); + + // If we have more sizers than lines, remove them + while( (int)m_rows.GetChildren().GetCount()>m_n_line ) + m_rows.Remove( m_n_line ); + + // Now do layout on row sizer + m_rows.SetDimension( m_position.x, m_position.y, m_size.x, m_size.y ); + + // Remember this to next time (will be overridden by InformFirstDirection if used) + m_prim_size_last = SizeInMajorDir(m_size); +} + + +wxSize wxWrapSizer::CalcMin() +{ + if (m_children.GetCount() == 0) + return wxSize(); + + // Algorithm for calculating min size: (assuming horizontal orientation) + // X: Max width of all members + // Y: Based on last X, calculate how many lines needed + // First time around, assume all items fits on one line + + int maxMajor = 0; + int minorSum = 0; + int lineMaxMinor = 0; + int lineSumMajor = 0; + m_n_line = 0; + + // precalc item minsizes and fit on lines (preliminary) + wxSizerItemList::compatibility_iterator node = m_children.GetFirst(); + while (node) + { + wxSizerItem *item = node->GetData(); + if ( item->IsShown() ) + { + wxSize minSz = item->CalcMin(); + int szMajor = SizeInMajorDir(minSz); + int szMinor = SizeInMinorDir(minSz); + if( szMajor>maxMajor ) maxMajor = szMajor; + // More space on this line? + if( m_prim_size_last<0 || !lineSumMajor || + lineSumMajor+szMajor<=m_prim_size_last ) + { + lineSumMajor += szMajor; + if( szMinor>lineMaxMinor ) + lineMaxMinor = szMinor; + } + else + { + minorSum += lineMaxMinor; // Add height of highest item on last line + m_n_line++; + lineMaxMinor = szMinor; + lineSumMajor = szMajor; + } + } + node = node->GetNext(); + } + minorSum += lineMaxMinor; // Add height of highest item on last line + + m_minSize = SizeFromMajorMinor(maxMajor, minorSum); + return m_minSize; +} + //--------------------------------------------------------------------------- // wxStaticBoxSizer //--------------------------------------------------------------------------- -- 2.45.2