1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/common/wrapsizer.cpp
3 // Purpose: provides wxWrapSizer class for layout
4 // Author: Arne Steinarson
6 // Copyright: (c) Arne Steinarson
7 // Licence: wxWindows licence
8 /////////////////////////////////////////////////////////////////////////////
10 // ============================================================================
12 // ============================================================================
14 // ----------------------------------------------------------------------------
16 // ----------------------------------------------------------------------------
18 // For compilers that support precompilation, includes "wx.h".
19 #include "wx/wxprec.h"
25 #include "wx/wrapsizer.h"
26 #include "wx/vector.h"
31 // ----------------------------------------------------------------------------
32 // helper local classes
33 // ----------------------------------------------------------------------------
35 // This object changes the item proportion to INT_MAX in its ctor and restores
36 // it back in the dtor.
37 class wxPropChanger
: public wxObject
40 wxPropChanger(wxSizer
& sizer
, wxSizerItem
& item
)
43 m_propOld(item
.GetProportion())
45 // ensure that this item expands more than all the other ones
46 item
.SetProportion(INT_MAX
);
51 // check if the sizer still has this item, it could have been removed
52 if ( m_sizer
.GetChildren().Find(&m_item
) )
53 m_item
.SetProportion(m_propOld
);
61 wxDECLARE_NO_COPY_CLASS(wxPropChanger
);
64 } // anonymous namespace
66 // ============================================================================
67 // wxWrapSizer implementation
68 // ============================================================================
70 IMPLEMENT_DYNAMIC_CLASS(wxWrapSizer
, wxBoxSizer
)
72 wxWrapSizer::wxWrapSizer(int orient
, int flags
)
77 m_availableOtherDir(0),
81 m_minItemMajor(INT_MAX
),
82 m_rows(orient
^ wxBOTH
)
86 wxWrapSizer::~wxWrapSizer()
91 void wxWrapSizer::ClearRows()
93 // all elements of the row sizers are also elements of this one (we
94 // directly add pointers to elements of our own m_children list to the row
95 // sizers in RecalcSizes()), so we need to detach them from the row sizer
96 // to avoid double deletion
97 wxSizerItemList
& rows
= m_rows
.GetChildren();
98 for ( wxSizerItemList::iterator i
= rows
.begin(),
103 wxSizerItem
* const item
= *i
;
104 wxSizer
* const row
= item
->GetSizer();
107 wxFAIL_MSG( "all elements of m_rows must be sizers" );
111 row
->GetChildren().clear();
113 wxPropChanger
* const
114 propChanger
= static_cast<wxPropChanger
*>(item
->GetUserData());
117 // this deletes propChanger and so restores the old proportion
118 item
->SetUserData(NULL
);
123 wxSizer
*wxWrapSizer::GetRowSizer(size_t n
)
125 const wxSizerItemList
& rows
= m_rows
.GetChildren();
126 if ( n
< rows
.size() )
127 return rows
[n
]->GetSizer();
129 wxSizer
* const sizer
= new wxBoxSizer(GetOrientation());
130 m_rows
.Add(sizer
, wxSizerFlags().Expand());
134 bool wxWrapSizer::InformFirstDirection(int direction
,
136 int availableOtherDir
)
141 // Store the values for later use
143 m_availableOtherDir
= availableOtherDir
+
144 (direction
== wxHORIZONTAL
? m_minSize
.y
146 m_dirInform
= direction
;
152 void wxWrapSizer::AdjustLastRowItemProp(size_t n
, wxSizerItem
*itemLast
)
154 if ( !itemLast
|| !(m_flags
& wxEXTEND_LAST_ON_EACH_LINE
) )
160 wxSizerItem
* const item
= m_rows
.GetItem(n
);
161 wxCHECK_RET( item
, "invalid sizer item" );
163 // store the item we modified and its original proportion
164 item
->SetUserData(new wxPropChanger(*this, *itemLast
));
167 wxSize
wxWrapSizer::CalcMin()
169 if ( m_children
.empty() )
172 // We come here to calculate min size in two different situations:
173 // 1 - Immediately after InformFirstDirection, then we find a min size that
174 // uses one dimension maximally and the other direction minimally.
175 // 2 - Ordinary case, get a sensible min size value using the current line
176 // layout, trying to maintain the possibility to re-arrange lines by
181 // Case 1 above: InformFirstDirection() has just been called
184 // There are two different algorithms for finding a useful min size for
185 // a wrap sizer, depending on whether the first reported size component
186 // is the opposite as our own orientation (the simpler case) or the same
187 // one (more complicated).
188 if ( m_dirInform
== m_orient
)
189 CalcMinFromMajor(m_availSize
);
191 CalcMinFromMinor(m_availSize
);
193 else // Case 2 above: not immediately after InformFirstDirection()
195 if ( m_availSize
> 0 )
197 wxSize szAvail
; // Keep track of boundary so we don't overflow
198 if ( m_dirInform
== m_orient
)
199 szAvail
= SizeFromMajorMinor(m_availSize
, m_availableOtherDir
);
201 szAvail
= SizeFromMajorMinor(m_availableOtherDir
, m_availSize
);
203 CalcMinFittingSize(szAvail
);
205 else // Initial calculation, before we have size available to us
207 CalcMaxSingleItemSize();
214 void wxWrapSizer::CalcMinFittingSize(const wxSize
& szBoundary
)
216 // Min size based on current line layout. It is important to
217 // provide a smaller size when possible to allow for resizing with
218 // the help of re-arranging the lines.
219 wxSize sizeMin
= SizeFromMajorMinor(m_maxSizeMajor
, m_minSizeMinor
);
220 if ( m_minSizeMinor
< SizeInMinorDir(m_size
) &&
221 m_maxSizeMajor
< SizeInMajorDir(m_size
) )
227 // Try making it a bit more narrow
229 if ( m_minItemMajor
!= INT_MAX
&& m_maxSizeMajor
> 0 )
231 // We try to present a lower min value by removing an item in
232 // the major direction (and preserving current minor min size).
233 CalcMinFromMajor(m_maxSizeMajor
- m_minItemMajor
);
234 if ( m_minSize
.x
<= szBoundary
.x
&& m_minSize
.y
<= szBoundary
.y
)
236 SizeInMinorDir(m_minSize
) = SizeInMinorDir(sizeMin
);
243 // If failed finding little smaller area, go back to what we had
249 void wxWrapSizer::CalcMaxSingleItemSize()
251 // Find max item size in each direction
252 int maxMajor
= 0; // Widest item
253 int maxMinor
= 0; // Line height
254 for ( wxSizerItemList::const_iterator i
= m_children
.begin();
255 i
!= m_children
.end();
258 wxSizerItem
* const item
= *i
;
259 if ( item
->IsShown() )
261 wxSize sz
= item
->CalcMin();
262 if ( SizeInMajorDir(sz
) > maxMajor
)
263 maxMajor
= SizeInMajorDir(sz
);
264 if ( SizeInMinorDir(sz
) > maxMinor
)
265 maxMinor
= SizeInMinorDir(sz
);
269 // This is, of course, not our real minimal size but if we return more
270 // than this it would be impossible to shrink us to one row/column so
271 // we have to pretend that this is all we need for now.
272 m_minSize
= SizeFromMajorMinor(maxMajor
, maxMinor
);
275 void wxWrapSizer::CalcMinFromMajor(int totMajor
)
277 // Algorithm for calculating min size: (assuming horizontal orientation)
278 // This is the simpler case (known major size)
279 // X: Given, totMajor
280 // Y: Based on X, calculate how many lines needed
282 int maxTotalMajor
= 0; // max of rowTotalMajor over all rows
283 int minorSum
= 0; // sum of sizes of all rows in minor direction
284 int maxRowMinor
= 0; // max of item minor sizes in this row
285 int rowTotalMajor
= 0; // sum of major sizes of items in this row
287 // pack the items in each row until we reach totMajor, then start a new row
288 for ( wxSizerItemList::const_iterator i
= m_children
.begin();
289 i
!= m_children
.end();
292 wxSizerItem
* const item
= *i
;
293 if ( !item
->IsShown() )
296 wxSize minItemSize
= item
->CalcMin();
297 const int itemMajor
= SizeInMajorDir(minItemSize
);
298 const int itemMinor
= SizeInMinorDir(minItemSize
);
300 // check if this is the first item in a new row: if so, we have to put
301 // it in it, whether it fits or not, as it would never fit better
304 // otherwise check if we have enough space left for this item here
305 if ( !rowTotalMajor
|| rowTotalMajor
+ itemMajor
<= totMajor
)
308 rowTotalMajor
+= itemMajor
;
309 if ( itemMinor
> maxRowMinor
)
310 maxRowMinor
= itemMinor
;
312 else // start a new row
314 // minor size of the row is the max of minor sizes of its items
315 minorSum
+= maxRowMinor
;
316 if ( rowTotalMajor
> maxTotalMajor
)
317 maxTotalMajor
= rowTotalMajor
;
318 maxRowMinor
= itemMinor
;
319 rowTotalMajor
= itemMajor
;
323 // account for the last (unfinished) row too
324 minorSum
+= maxRowMinor
;
325 if ( rowTotalMajor
> maxTotalMajor
)
326 maxTotalMajor
= rowTotalMajor
;
328 m_minSize
= SizeFromMajorMinor(maxTotalMajor
, minorSum
);
331 // Helper struct for CalcMinFromMinor
334 wxWrapLine() : m_first(NULL
), m_width(0) { }
335 wxSizerItem
*m_first
;
336 int m_width
; // Width of line
339 void wxWrapSizer::CalcMinFromMinor(int totMinor
)
341 // Algorithm for calculating min size:
342 // This is the more complex case (known minor size)
344 // First step, find total sum of all items in primary direction
345 // and max item size in secondary direction, that gives initial
346 // estimate of the minimum number of lines.
348 int totMajor
= 0; // Sum of widths
349 int maxMinor
= 0; // Line height
350 int maxMajor
= 0; // Widest item
352 wxSizerItemList::compatibility_iterator node
= m_children
.GetFirst();
356 wxSizerItem
*item
= node
->GetData();
357 if ( item
->IsShown() )
359 sz
= item
->CalcMin();
360 totMajor
+= SizeInMajorDir(sz
);
361 if ( SizeInMinorDir(sz
)>maxMinor
)
362 maxMinor
= SizeInMinorDir(sz
);
363 if ( SizeInMajorDir(sz
)>maxMinor
)
364 maxMajor
= SizeInMajorDir(sz
);
367 node
= node
->GetNext();
371 if ( !itemCount
|| totMajor
==0 || maxMinor
==0 )
373 m_minSize
= wxSize(0,0);
377 // First attempt, use lines of average size:
378 int nrLines
= totMinor
/ maxMinor
; // Rounding down is right here
381 // Another simple case, everything fits on one line
382 m_minSize
= SizeFromMajorMinor(totMajor
,maxMinor
);
386 int lineSize
= totMajor
/ nrLines
;
387 if ( lineSize
<maxMajor
) // At least as wide as the widest element
390 // The algorithm is as follows (horz case):
391 // 1 - Vertical (minor) size is known.
392 // 2 - We have a reasonable estimated width from above
394 // 3a - Do layout with suggested width
395 // 3b - See how much we spill over in minor dir
396 // 3c - If no spill, we're done
397 // 3d - Otherwise increase width by known smallest item
400 // First algo step: put items on lines of known max width
401 wxVector
<wxWrapLine
*> lines
;
403 int sumMinor
; // Sum of all minor sizes (height of all lines)
405 // While we still have items 'spilling over' extend the tested line width
408 wxWrapLine
*line
= new wxWrapLine
;
409 lines
.push_back( line
);
411 int tailSize
= 0; // Width of what exceeds nrLines
414 for ( node
=m_children
.GetFirst(); node
; node
=node
->GetNext() )
416 wxSizerItem
*item
= node
->GetData();
417 if ( item
->IsShown() )
419 sz
= item
->GetMinSizeWithBorder();
420 if ( line
->m_width
+SizeInMajorDir(sz
)>lineSize
)
422 line
= new wxWrapLine
;
423 lines
.push_back(line
);
424 sumMinor
+= maxMinor
;
427 line
->m_width
+= SizeInMajorDir(sz
);
428 if ( line
->m_width
&& !line
->m_first
)
429 line
->m_first
= item
;
430 if ( SizeInMinorDir(sz
)>maxMinor
)
431 maxMinor
= SizeInMinorDir(sz
);
432 if ( sumMinor
+maxMinor
>totMinor
)
434 // Keep track of widest tail item
435 if ( SizeInMajorDir(sz
)>tailSize
)
436 tailSize
= SizeInMajorDir(sz
);
443 // Now look how much we need to extend our size
444 // We know we must have at least one more line than nrLines
445 // (otherwise no tail size).
446 int bestExtSize
= 0; // Minimum extension width for current tailSize
447 for ( int ix
=0; ix
<nrLines
; ix
++ )
449 // Take what is not used on this line, see how much extension we get
450 // by adding first item on next line.
451 int size
= lineSize
-lines
[ix
]->m_width
; // Left over at end of this line
452 int extSize
= GetSizeInMajorDir(lines
[ix
+1]->m_first
->GetMinSizeWithBorder()) - size
;
453 if ( (extSize
>=tailSize
&& (extSize
<bestExtSize
|| bestExtSize
<tailSize
)) ||
454 (extSize
>bestExtSize
&& bestExtSize
<tailSize
) )
455 bestExtSize
= extSize
;
457 // Have an extension size, ready to redo line layout
458 lineSize
+= bestExtSize
;
461 // Clear helper items
462 for ( wxVector
<wxWrapLine
*>::iterator it
=lines
.begin(); it
<lines
.end(); ++it
)
471 // Now have min size in the opposite direction
472 m_minSize
= SizeFromMajorMinor(lineSize
,sumMinor
);
475 void wxWrapSizer::FinishRow(size_t n
,
476 int rowMajor
, int rowMinor
,
477 wxSizerItem
*itemLast
)
479 // Account for the finished row size.
480 m_minSizeMinor
+= rowMinor
;
481 if ( rowMajor
> m_maxSizeMajor
)
482 m_maxSizeMajor
= rowMajor
;
484 // And adjust proportion of its last item if necessary.
485 AdjustLastRowItemProp(n
, itemLast
);
488 void wxWrapSizer::RecalcSizes()
490 // First restore any proportions we may have changed and remove the old rows
493 if ( m_children
.empty() )
496 // Put all our items into as many row box sizers as needed.
497 const int majorSize
= SizeInMajorDir(m_size
); // max size of each row
498 int rowTotalMajor
= 0; // running row major size
502 m_minItemMajor
= INT_MAX
;
505 // We need at least one row
507 wxSizer
*sizer
= GetRowSizer(nRow
);
509 wxSizerItem
*itemLast
= NULL
, // last item processed in this row
510 *itemSpace
= NULL
; // spacer which we delayed adding
512 // Now put our child items into child sizers instead
513 for ( wxSizerItemList::iterator i
= m_children
.begin();
514 i
!= m_children
.end();
517 wxSizerItem
* const item
= *i
;
518 if ( !item
->IsShown() )
521 wxSize minItemSize
= item
->GetMinSizeWithBorder();
522 const int itemMajor
= SizeInMajorDir(minItemSize
);
523 const int itemMinor
= SizeInMinorDir(minItemSize
);
524 if ( itemMajor
> 0 && itemMajor
< m_minItemMajor
)
525 m_minItemMajor
= itemMajor
;
527 // Is there more space on this line? Notice that if this is the first
528 // item we add it unconditionally as it wouldn't fit in the next line
529 // any better than in this one.
530 if ( !rowTotalMajor
|| rowTotalMajor
+ itemMajor
<= majorSize
)
532 // There is enough space here
533 rowTotalMajor
+= itemMajor
;
534 if ( itemMinor
> maxRowMinor
)
535 maxRowMinor
= itemMinor
;
537 else // Start a new row
539 FinishRow(nRow
, rowTotalMajor
, maxRowMinor
, itemLast
);
541 rowTotalMajor
= itemMajor
;
542 maxRowMinor
= itemMinor
;
544 // Get a new empty sizer to insert into
545 sizer
= GetRowSizer(++nRow
);
551 // Only remove first/last spaces if that flag is set
552 if ( (m_flags
& wxREMOVE_LEADING_SPACES
) && IsSpaceItem(item
) )
554 // Remember space only if we have a first item
560 if ( itemLast
&& itemSpace
)
562 // We had a spacer after a real item and now that we add
563 // another real item to the same row we need to add the spacer
565 sizer
->Add(itemSpace
);
568 // Notice that we reuse a pointer to our own sizer item here, so we
569 // must remember to remove it by calling ClearRows() to avoid
570 // double deletion later
577 // If item is a window, it now has a pointer to the child sizer,
578 // which is wrong. Set it to point to us.
579 if ( wxWindow
*win
= item
->GetWindow() )
580 win
->SetContainingSizer(this);
583 FinishRow(nRow
, rowTotalMajor
, maxRowMinor
, itemLast
);
585 // Now do layout on row sizer
586 m_rows
.SetDimension(m_position
, m_size
);