X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/89064717333f33c6ec477cf25e5692e3f4d6a1dd..8ef9a526cea6ecc3853f327a76da14561b36f82a:/src/common/sizer.cpp diff --git a/src/common/sizer.cpp b/src/common/sizer.cpp index af09c05b3e..930aa061bb 100644 --- a/src/common/sizer.cpp +++ b/src/common/sizer.cpp @@ -19,6 +19,7 @@ #include "wx/display.h" #include "wx/sizer.h" +#include "wx/private/flagscheck.h" #ifndef WX_PRECOMP #include "wx/string.h" @@ -87,6 +88,31 @@ WX_DEFINE_EXPORTED_LIST( wxSizerItemList ) // wxSizerItem // ---------------------------------------------------------------------------- +// check for flags conflicts +static const int SIZER_FLAGS_MASK = + wxADD_FLAG(wxCENTRE, + wxADD_FLAG(wxHORIZONTAL, + wxADD_FLAG(wxVERTICAL, + wxADD_FLAG(wxLEFT, + wxADD_FLAG(wxRIGHT, + wxADD_FLAG(wxUP, + wxADD_FLAG(wxDOWN, + wxADD_FLAG(wxALIGN_NOT, + wxADD_FLAG(wxALIGN_CENTER_HORIZONTAL, + wxADD_FLAG(wxALIGN_RIGHT, + wxADD_FLAG(wxALIGN_BOTTOM, + wxADD_FLAG(wxALIGN_CENTER_VERTICAL, + wxADD_FLAG(wxFIXED_MINSIZE, + wxADD_FLAG(wxRESERVE_SPACE_EVEN_IF_HIDDEN, + wxADD_FLAG(wxSTRETCH_NOT, + wxADD_FLAG(wxSHRINK, + wxADD_FLAG(wxGROW, + wxADD_FLAG(wxSHAPED, + 0)))))))))))))))))); + +#define ASSERT_VALID_SIZER_FLAGS(f) wxASSERT_VALID_FLAGS(f, SIZER_FLAGS_MASK) + + void wxSizerItem::Init(const wxSizerFlags& flags) { Init(); @@ -94,6 +120,8 @@ void wxSizerItem::Init(const wxSizerFlags& flags) m_proportion = flags.GetProportion(); m_flag = flags.GetFlags(); m_border = flags.GetBorderInPixels(); + + ASSERT_VALID_SIZER_FLAGS( m_flag ); } wxSizerItem::wxSizerItem() @@ -103,6 +131,7 @@ wxSizerItem::wxSizerItem() m_proportion = 0; m_border = 0; m_flag = 0; + m_id = wxID_NONE; } // window item @@ -132,8 +161,11 @@ wxSizerItem::wxSizerItem(wxWindow *window, m_proportion(proportion), m_border(border), m_flag(flag), + m_id(wxID_NONE), m_userData(userData) { + ASSERT_VALID_SIZER_FLAGS( m_flag ); + DoSetWindow(window); } @@ -154,9 +186,12 @@ wxSizerItem::wxSizerItem(wxSizer *sizer, m_proportion(proportion), m_border(border), m_flag(flag), + m_id(wxID_NONE), m_ratio(0.0), m_userData(userData) { + ASSERT_VALID_SIZER_FLAGS( m_flag ); + DoSetSizer(sizer); // m_minSize is set later @@ -183,8 +218,11 @@ wxSizerItem::wxSizerItem(int width, m_proportion(proportion), m_border(border), m_flag(flag), + m_id(wxID_NONE), m_userData(userData) { + ASSERT_VALID_SIZER_FLAGS( m_flag ); + DoSetSpacer(wxSize(width, height)); } @@ -268,6 +306,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()) @@ -451,6 +556,9 @@ void wxSizerItem::Show( bool show ) bool wxSizerItem::IsShown() const { + if ( m_flag & wxRESERVE_SPACE_EVEN_IF_HIDDEN ) + return true; + switch ( m_kind ) { case Item_None: @@ -522,6 +630,9 @@ wxSizerItem* wxSizer::Insert( size_t index, wxSizerItem *item ) if ( item->GetWindow() ) item->GetWindow()->SetContainingSizer( this ); + if ( item->GetSizer() ) + item->GetSizer()->SetContainingWindow( m_containingWindow ); + return item; } @@ -760,11 +871,13 @@ void wxSizer::DeleteWindows() } } -wxSize wxSizer::Fit( wxWindow *window ) +wxSize wxSizer::ComputeFittingClientSize(wxWindow *window) { + wxCHECK_MSG( window, wxDefaultSize, "window can't be NULL" ); + // take the min size by default and limit it by max size - wxSize size = GetMinWindowSize(window); - wxSize sizeMax = GetMaxWindowSize(window); + wxSize size = GetMinClientSize(window); + wxSize sizeMax; wxTopLevelWindow *tlw = wxDynamicCast(window, wxTopLevelWindow); if ( tlw ) @@ -772,31 +885,51 @@ wxSize wxSizer::Fit( wxWindow *window ) // hack for small screen devices where TLWs are always full screen if ( tlw->IsAlwaysMaximized() ) { - size = tlw->GetSize(); + return tlw->GetClientSize(); } - else // normal situation - { - // limit the window to the size of the display it is on - int disp = wxDisplay::GetFromWindow(window); - if ( disp == wxNOT_FOUND ) - { - // or, if we don't know which one it is, of the main one - disp = 0; - } - sizeMax = wxDisplay(disp).GetClientArea().GetSize(); + // limit the window to the size of the display it is on + int disp = wxDisplay::GetFromWindow(window); + if ( disp == wxNOT_FOUND ) + { + // or, if we don't know which one it is, of the main one + disp = 0; } + + sizeMax = wxDisplay(disp).GetClientArea().GetSize(); + + // space for decorations and toolbars etc. + sizeMax = tlw->WindowToClientSize(sizeMax); + } + else + { + sizeMax = GetMaxClientSize(window); } if ( sizeMax.x != wxDefaultCoord && size.x > sizeMax.x ) - size.x = sizeMax.x; + size.x = sizeMax.x; if ( sizeMax.y != wxDefaultCoord && size.y > sizeMax.y ) - size.y = sizeMax.y; + size.y = sizeMax.y; + return size; +} - window->SetSize( size ); +wxSize wxSizer::ComputeFittingWindowSize(wxWindow *window) +{ + wxCHECK_MSG( window, wxDefaultSize, "window can't be NULL" ); - return size; + return window->ClientToWindowSize(ComputeFittingClientSize(window)); +} + +wxSize wxSizer::Fit( wxWindow *window ) +{ + wxCHECK_MSG( window, wxDefaultSize, "window can't be NULL" ); + + // set client size + window->SetClientSize(ComputeFittingClientSize(window)); + + // return entire size + return window->GetSize(); } void wxSizer::FitInside( wxWindow *window ) @@ -825,59 +958,31 @@ void wxSizer::SetSizeHints( wxWindow *window ) // Preserve the window's max size hints, but set the // lower bound according to the sizer calculations. - wxSize size = Fit( window ); + // This is equivalent to calling Fit(), except that we need to set + // the size hints _in between_ the two steps performed by Fit + // (1. ComputeFittingClientSize, 2. SetClientSize). That's because + // otherwise SetClientSize() could have no effect if there already are + // size hints in effect that forbid requested client size. - window->SetSizeHints( size.x, - size.y, - window->GetMaxWidth(), - window->GetMaxHeight() ); + const wxSize clientSize = ComputeFittingClientSize(window); + + window->SetMinClientSize(clientSize); + window->SetClientSize(clientSize); } +#if WXWIN_COMPATIBILITY_2_8 void wxSizer::SetVirtualSizeHints( wxWindow *window ) { - // Preserve the window's max size hints, but set the - // lower bound according to the sizer calculations. - FitInside( window ); - wxSize size( window->GetVirtualSize() ); - window->SetVirtualSizeHints( size.x, - size.y, - window->GetMaxWidth(), - window->GetMaxHeight() ); -} - -wxSize wxSizer::GetMaxWindowSize( wxWindow *window ) const -{ - return window->GetMaxSize(); -} - -wxSize wxSizer::GetMinWindowSize( wxWindow *window ) -{ - wxSize minSize( GetMinSize() ); - wxSize size( window->GetSize() ); - wxSize client_size( window->GetClientSize() ); - - return wxSize( minSize.x+size.x-client_size.x, - minSize.y+size.y-client_size.y ); } +#endif // WXWIN_COMPATIBILITY_2_8 // TODO on mac we need a function that determines how much free space this // min size contains, in order to make sure that we have 20 pixels of free // space around the controls wxSize wxSizer::GetMaxClientSize( wxWindow *window ) const { - wxSize maxSize( window->GetMaxSize() ); - - if ( maxSize != wxDefaultSize ) - { - wxSize size( window->GetSize() ); - wxSize client_size( window->GetClientSize() ); - - return wxSize( maxSize.x + client_size.x - size.x, - maxSize.y + client_size.y - size.y ); - } - else - return wxDefaultSize; + return window->WindowToClientSize(window->GetMaxSize()); } wxSize wxSizer::GetMinClientSize( wxWindow *WXUNUSED(window) ) @@ -1082,6 +1187,33 @@ wxSizerItem* wxSizer::GetItem( size_t index ) return m_children.Item( index )->GetData(); } +wxSizerItem* wxSizer::GetItemById( int id, bool recursive ) +{ + // This gets a sizer item by the id of the sizer item + // and NOT the id of a window if the item is a window. + + wxSizerItemList::compatibility_iterator node = m_children.GetFirst(); + while (node) + { + wxSizerItem *item = node->GetData(); + + if (item->GetId() == id) + { + return item; + } + else if (recursive && item->IsSizer()) + { + wxSizerItem *subitem = item->GetSizer()->GetItemById( id, true ); + if (subitem) + return subitem; + } + + node = node->GetNext(); + } + + return NULL; +} + bool wxSizer::Show( wxWindow *window, bool show, bool recursive ) { wxSizerItem *item = GetItem( window, recursive ); @@ -1280,6 +1412,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 ); } @@ -1342,105 +1503,160 @@ wxFlexGridSizer::~wxFlexGridSizer() void wxFlexGridSizer::RecalcSizes() { - int nitems, nrows, ncols; - if ( (nitems = CalcRowsCols(nrows, ncols)) == 0 ) + int nrows, ncols; + if ( !CalcRowsCols(nrows, ncols) ) return; - wxPoint pt( GetPosition() ); - wxSize sz( GetSize() ); + const wxPoint pt(GetPosition()); + const wxSize sz(GetSize()); - AdjustForGrowables(sz, m_calculatedMinSize, nrows, ncols); + AdjustForGrowables(sz); - sz = wxSize( pt.x + sz.x, pt.y + sz.y ); + wxSizerItemList::const_iterator i = m_children.begin(); + const wxSizerItemList::const_iterator end = m_children.end(); - int x = pt.x; - for (int c = 0; c < ncols; c++) + int y = 0; + for ( int r = 0; r < nrows; r++ ) { - int y = pt.y; - for (int r = 0; r < nrows; r++) + if ( m_rowHeights[r] == -1 ) { - int i = r * ncols + c; - if (i < nitems) + // this row is entirely hidden, skip it + for ( int c = 0; c < ncols; c++ ) { - wxSizerItemList::compatibility_iterator node = m_children.Item( i ); + if ( i == end ) + return; + + ++i; + } - wxASSERT_MSG( node, _T("Failed to find node") ); + continue; + } - int w = wxMax( 0, wxMin( m_colWidths[c], sz.x - x ) ); - int h = wxMax( 0, wxMin( m_rowHeights[r], sz.y - y ) ); + const int hrow = m_rowHeights[r]; + int h = sz.y - y; // max remaining height, don't overflow it + if ( hrow < h ) + h = hrow; - SetItemBounds( node->GetData(), x, y, w, h); - } - if (m_rowHeights[r] != -1) - y = y + m_rowHeights[r] + m_vgap; + int x = 0; + for ( int c = 0; c < ncols && i != end; c++, ++i ) + { + const int wcol = m_colWidths[c]; + + if ( wcol == -1 ) + continue; + + int w = sz.x - x; // max possible value, ensure we don't overflow + if ( wcol < w ) + w = wcol; + + SetItemBounds(*i, pt.x + x, pt.y + y, w, h); + + x += wcol + m_hgap; + } + + if ( i == end ) + return; + + y += hrow + m_vgap; + } +} + +// helper function used in CalcMin() to sum up the sizes of non-hidden items +static int SumArraySizes(const wxArrayInt& sizes, int gap) +{ + // Sum total minimum size, including gaps between rows/columns. + // -1 is used as a magic number meaning empty row/column. + int total = 0; + + const size_t count = sizes.size(); + for ( size_t n = 0; n < count; n++ ) + { + if ( sizes[n] != -1 ) + { + if ( total ) + total += gap; // separate from the previous column + + total += sizes[n]; + } + } + + return total; +} + +void wxFlexGridSizer::FindWidthsAndHeights(int nrows, int ncols) +{ + // 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() ) + { + // 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; + + if ( sz.y > m_rowHeights[row] ) + m_rowHeights[row] = sz.y; + if ( sz.x > m_colWidths[col] ) + m_colWidths[col] = sz.x; } - if (m_colWidths[c] != -1) - x = x + m_colWidths[c] + m_hgap; } + + AdjustForFlexDirection(); + + m_calculatedMinSize = wxSize(SumArraySizes(m_colWidths, m_hgap), + SumArraySizes(m_rowHeights, m_vgap)); } wxSize wxFlexGridSizer::CalcMin() { - int nrows, - ncols; - size_t i, s; + int nrows, + ncols; // Number of rows/columns can change as items are added or removed. if ( !CalcRowsCols(nrows, ncols) ) return wxSize(); - m_rowHeights.SetCount(nrows); - m_colWidths.SetCount(ncols); - // We have to recalcuate the sizes in case the item minimum size has + // 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. - for( s = m_rowHeights.GetCount(), i = 0; i < s; ++i ) - m_rowHeights[ i ] = -1; - for( s = m_colWidths.GetCount(), i = 0; i < s; ++i ) - m_colWidths[ i ] = -1; - - wxSizerItemList::compatibility_iterator node = m_children.GetFirst(); + m_rowHeights.assign(nrows, -1); + m_colWidths.assign(ncols, -1); - i = 0; - while (node) + // 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 *item = node->GetData(); + wxSizerItem * const item = *i; if ( item->IsShown() ) { - wxSize sz( item->CalcMin() ); - int row = i / ncols; - int col = i % ncols; - - m_rowHeights[ row ] = wxMax( wxMax( 0, sz.y ), m_rowHeights[ row ] ); - m_colWidths[ col ] = wxMax( wxMax( 0, sz.x ), m_colWidths[ col ] ); + item->CalcMin(); } - - node = node->GetNext(); - i++; } - AdjustForFlexDirection(); + // 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); - // Sum total minimum size, including gaps between rows/columns. - // -1 is used as a magic number meaning empty column. - int width = 0; - for (int col = 0; col < ncols; col++) - if ( m_colWidths[ col ] != -1 ) - width += m_colWidths[ col ] + m_hgap; - if (width > 0) - width -= m_hgap; - - int height = 0; - for (int row = 0; row < nrows; row++) - if ( m_rowHeights[ row ] != -1 ) - height += m_rowHeights[ row ] + m_vgap; - if (height > 0) - height -= m_vgap; - - m_calculatedMinSize = wxSize( width, height ); return m_calculatedMinSize; } @@ -1470,116 +1686,146 @@ void wxFlexGridSizer::AdjustForFlexDirection() // and now fill it with the largest value for ( n = 0; n < count; ++n ) { - array[n] = largest; + // don't touch hidden rows + if ( array[n] != -1 ) + array[n] = largest; } } } - -void wxFlexGridSizer::AdjustForGrowables(const wxSize& sz, const wxSize& minsz, - int nrows, int ncols) +// helper of AdjustForGrowables() which is called for rows/columns separately +// +// parameters: +// delta: the extra space, we do nothing unless it's positive +// growable: indices or growable rows/cols in sizes array +// sizes: the height/widths of rows/cols to adjust +// proportions: proportions of the growable rows/cols or NULL if they all +// should be assumed to have proportion of 1 +static void +DoAdjustForGrowables(int delta, + const wxArrayInt& growable, + wxArrayInt& sizes, + const wxArrayInt *proportions) { - // what to do with the rows? by default, resize them proportionally - if ( sz.y > minsz.y && ( (m_flexDirection & wxVERTICAL) || (m_growMode == wxFLEX_GROWMODE_SPECIFIED) ) ) - { - int sum_proportions = 0; - int growable_space = 0; - int num = 0; - size_t idx; - for (idx = 0; idx < m_growableRows.GetCount(); idx++) - { - // Since the number of rows/columns can change as items are - // inserted/deleted, we need to verify at runtime that the - // requested growable rows/columns are still valid. - if (m_growableRows[idx] >= nrows) - continue; + if ( delta <= 0 ) + return; - // If all items in a row/column are hidden, that row/column will - // have a dimension of -1. This causes the row/column to be - // hidden completely. - if (m_rowHeights[ m_growableRows[idx] ] == -1) - continue; - sum_proportions += m_growableRowsProportions[idx]; - growable_space += m_rowHeights[ m_growableRows[idx] ]; - num++; - } + // total sum of proportions of all non-hidden rows + int sum_proportions = 0; - if (num > 0) - { - for (idx = 0; idx < m_growableRows.GetCount(); idx++) - { - if (m_growableRows[idx] >= nrows ) - continue; - if (m_rowHeights[ m_growableRows[idx] ] == -1) - m_rowHeights[ m_growableRows[idx] ] = 0; - else - { - int delta = (sz.y - minsz.y); - if (sum_proportions == 0) - delta = (delta/num) + m_rowHeights[ m_growableRows[idx] ]; - else - delta = ((delta+growable_space)*m_growableRowsProportions[idx]) / sum_proportions; - m_rowHeights[ m_growableRows[idx] ] = delta; - } - } - } - } - else if ( (m_growMode == wxFLEX_GROWMODE_ALL) && (sz.y > minsz.y) ) + // number of currently shown growable rows + int num = 0; + + const int max_idx = sizes.size(); + + const size_t count = growable.size(); + size_t idx; + for ( idx = 0; idx < count; idx++ ) { - // rounding problem? - for ( int row = 0; row < nrows; ++row ) - m_rowHeights[ row ] = sz.y / nrows; + // Since the number of rows/columns can change as items are + // inserted/deleted, we need to verify at runtime that the + // requested growable rows/columns are still valid. + if ( growable[idx] >= max_idx ) + continue; + + // If all items in a row/column are hidden, that row/column will + // have a dimension of -1. This causes the row/column to be + // hidden completely. + if ( sizes[growable[idx]] == -1 ) + continue; + + if ( proportions ) + sum_proportions += (*proportions)[idx]; + + num++; } - // the same logic as above but for the columns - if ( sz.x > minsz.x && ( (m_flexDirection & wxHORIZONTAL) || (m_growMode == wxFLEX_GROWMODE_SPECIFIED) ) ) + if ( !num ) + return; + + // the remaining extra free space, adjusted during each iteration + for ( idx = 0; idx < count; idx++ ) { - int sum_proportions = 0; - int growable_space = 0; - int num = 0; - size_t idx; - for (idx = 0; idx < m_growableCols.GetCount(); idx++) - { - // Since the number of rows/columns can change as items are - // inserted/deleted, we need to verify at runtime that the - // requested growable rows/columns are still valid. - if (m_growableCols[idx] >= ncols) - continue; + if ( growable[idx] >= max_idx ) + continue; - // If all items in a row/column are hidden, that row/column will - // have a dimension of -1. This causes the column to be hidden - // completely. - if (m_colWidths[ m_growableCols[idx] ] == -1) - continue; - sum_proportions += m_growableColsProportions[idx]; - growable_space += m_colWidths[ m_growableCols[idx] ]; - num++; - } + if ( sizes[ growable[idx] ] == -1 ) + continue; - if (num > 0) + int cur_delta; + if ( sum_proportions == 0 ) { - for (idx = 0; idx < m_growableCols.GetCount(); idx++) - { - if (m_growableCols[idx] >= ncols ) - continue; - if (m_colWidths[ m_growableCols[idx] ] == -1) - m_colWidths[ m_growableCols[idx] ] = 0; - else - { - int delta = (sz.x - minsz.x); - if (sum_proportions == 0) - delta = (delta/num) + m_colWidths[ m_growableCols[idx] ]; - else - delta = ((delta+growable_space)*m_growableColsProportions[idx])/sum_proportions; - m_colWidths[ m_growableCols[idx] ] = delta; - } - } + // no growable rows -- divide extra space evenly among all + cur_delta = delta/num; + num--; } + else // allocate extra space proportionally + { + const int cur_prop = (*proportions)[idx]; + cur_delta = (delta*cur_prop)/sum_proportions; + sum_proportions -= cur_prop; + } + + sizes[growable[idx]] += cur_delta; + delta -= cur_delta; } - else if ( (m_growMode == wxFLEX_GROWMODE_ALL) && (sz.x > minsz.x) ) +} + +void wxFlexGridSizer::AdjustForGrowables(const wxSize& sz) +{ + if ( (m_flexDirection & wxHORIZONTAL) || (m_growMode != wxFLEX_GROWMODE_NONE) ) + { + DoAdjustForGrowables + ( + 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); + } + + // Only redo if info was actually used + if( didAdjustMinSize ) { - for ( int col=0; col < ncols; ++col ) - m_colWidths[ col ] = sz.x / ncols; + DoAdjustForGrowables + ( + sz.x - m_calculatedMinSize.x, + m_growableCols, + m_colWidths, + m_growMode == wxFLEX_GROWMODE_SPECIFIED ? &m_growableColsProportions + : NULL + ); + } +} + + 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 + ); } } @@ -1633,16 +1879,50 @@ void wxBoxSizer::RecalcSizes() if ( m_children.empty() ) return; + const wxCoord totalMinorSize = GetSizeInMinorDir(m_size); + // the amount of free space which we should redistribute among the // stretchable items (i.e. those with non zero proportion) - const int delta = SizeInMajorDir(m_size) - SizeInMajorDir(m_minSize); + int delta = GetSizeInMajorDir(m_size) - GetSizeInMajorDir(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; + wxSizerItemList::const_iterator i ; + for ( 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 = GetSizeInMajorDir(szMin-szMinPrev); + if( deltaChange ) + { + // Since we passed available space along to the item, it should not + // take too much, so delta should not become negative. + delta -= deltaChange; + } + majorMinSum += GetSizeInMajorDir(item->GetMinSizeWithBorder()); + } + // And update our min size + SizeInMajorDir(m_minSize) = majorMinSum; + + + // might have a new delta now + delta = GetSizeInMajorDir(m_size) - GetSizeInMajorDir(m_minSize); // the position at which we put the next child wxPoint pt(m_position); - const wxCoord totalMinorSize = SizeInMinorDir(m_size); - - for ( wxSizerItemList::const_iterator i = m_children.begin(); + int totalProportion = m_totalProportion; + for ( i = m_children.begin(); i != m_children.end(); ++i ) { @@ -1653,21 +1933,24 @@ void wxBoxSizer::RecalcSizes() const wxSize sizeThis(item->GetMinSizeWithBorder()); - // adjust the size in the major direction using the proportion - wxCoord majorSize = SizeInMajorDir(sizeThis); - if ( item->GetProportion() ) + wxCoord majorSize = GetSizeInMajorDir(sizeThis); + const int propItem = item->GetProportion(); + if ( propItem ) { - // as at least one visible item has non-zero proportion the total - // proportion must be non zero - majorSize += (delta * item->GetProportion()) / m_totalProportion; + const int deltaItem = (delta * propItem) / totalProportion; + + majorSize += deltaItem; + + delta -= deltaItem; + totalProportion -= propItem; } // apply the alignment in the minor direction wxPoint posChild(pt); - wxCoord minorSize = SizeInMinorDir(sizeThis); + wxCoord minorSize = GetSizeInMinorDir(sizeThis); const int flag = item->GetFlag(); if ( flag & (wxEXPAND | wxSHAPED) ) { @@ -1719,10 +2002,9 @@ wxSize wxBoxSizer::CalcMin() continue; const wxSize sizeMinThis = item->CalcMin(); - - SizeInMajorDir(m_minSize) += SizeInMajorDir(sizeMinThis); - if ( SizeInMinorDir(sizeMinThis) > SizeInMinorDir(m_minSize) ) - SizeInMinorDir(m_minSize) = SizeInMinorDir(sizeMinThis); + SizeInMajorDir(m_minSize) += GetSizeInMajorDir(sizeMinThis); + if ( GetSizeInMinorDir(sizeMinThis) > GetSizeInMinorDir(m_minSize) ) + SizeInMinorDir(m_minSize) = GetSizeInMinorDir(sizeMinThis); m_totalProportion += item->GetProportion(); } @@ -1730,6 +2012,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 = GetSizeInMajorDir(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 = GetSizeInMajorDir(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 = GetSizeInMajorDir(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 = GetSizeInMajorDir(minSz); + int szMinor = GetSizeInMinorDir(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 //--------------------------------------------------------------------------- @@ -1943,7 +2443,7 @@ void wxStdDialogButtonSizer::Realize() Add((wxWindow*)m_buttonNegative, 0, wxALIGN_CENTRE | wxLEFT | wxRIGHT, 3); } - // according to HIG, in explicit apply windows the order is: + // according to HIG, in explicit apply windows the order is: // [ Help Apply Cancel OK ] if (m_buttonApply) Add((wxWindow*)m_buttonApply, 0, wxALIGN_CENTRE | wxLEFT | wxRIGHT, 3);