1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/common/wrapsizer.cpp
3 // Purpose: provides wxWrapSizer class for layout
4 // Author: Arne Steinarson
7 // Copyright: (c) Arne Steinarson
8 // Licence: wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
11 // ============================================================================
13 // ============================================================================
15 // ----------------------------------------------------------------------------
17 // ----------------------------------------------------------------------------
19 // For compilers that support precompilation, includes "wx.h".
20 #include "wx/wxprec.h"
26 #include "wx/wrapsizer.h"
27 #include "wx/vector.h"
32 // ----------------------------------------------------------------------------
33 // helper local classes
34 // ----------------------------------------------------------------------------
36 // This object changes the item proportion to INT_MAX in its ctor and restores
37 // it back in the dtor.
38 class wxPropChanger
: public wxObject
41 wxPropChanger(wxSizer
& sizer
, wxSizerItem
& item
)
44 m_propOld(item
.GetProportion())
46 // ensure that this item expands more than all the other ones
47 item
.SetProportion(INT_MAX
);
52 // check if the sizer still has this item, it could have been removed
53 if ( m_sizer
.GetChildren().Find(&m_item
) )
54 m_item
.SetProportion(m_propOld
);
62 wxDECLARE_NO_COPY_CLASS(wxPropChanger
);
65 } // anonymous namespace
67 // ============================================================================
68 // wxWrapSizer implementation
69 // ============================================================================
71 IMPLEMENT_DYNAMIC_CLASS(wxWrapSizer
, wxBoxSizer
)
73 wxWrapSizer::wxWrapSizer(int orient
, int flags
)
78 m_availableOtherDir(0),
82 m_minItemMajor(INT_MAX
),
83 m_rows(orient
^ wxBOTH
)
87 wxWrapSizer::~wxWrapSizer()
92 void wxWrapSizer::ClearRows()
94 // all elements of the row sizers are also elements of this one (we
95 // directly add pointers to elements of our own m_children list to the row
96 // sizers in RecalcSizes()), so we need to detach them from the row sizer
97 // to avoid double deletion
98 wxSizerItemList
& rows
= m_rows
.GetChildren();
99 for ( wxSizerItemList::iterator i
= rows
.begin(),
104 wxSizerItem
* const item
= *i
;
105 wxSizer
* const row
= item
->GetSizer();
108 wxFAIL_MSG( "all elements of m_rows must be sizers" );
112 row
->GetChildren().clear();
114 wxPropChanger
* const
115 propChanger
= static_cast<wxPropChanger
*>(item
->GetUserData());
118 // this deletes propChanger and so restores the old proportion
119 item
->SetUserData(NULL
);
124 wxSizer
*wxWrapSizer::GetRowSizer(size_t n
)
126 const wxSizerItemList
& rows
= m_rows
.GetChildren();
127 if ( n
< rows
.size() )
128 return rows
[n
]->GetSizer();
130 wxSizer
* const sizer
= new wxBoxSizer(GetOrientation());
131 m_rows
.Add(sizer
, wxSizerFlags().Expand());
135 bool wxWrapSizer::InformFirstDirection(int direction
,
137 int availableOtherDir
)
142 // Store the values for later use
144 m_availableOtherDir
= availableOtherDir
+
145 (direction
== wxHORIZONTAL
? m_minSize
.y
147 m_dirInform
= direction
;
153 void wxWrapSizer::AdjustLastRowItemProp(size_t n
, wxSizerItem
*itemLast
)
155 if ( !itemLast
|| !(m_flags
& wxEXTEND_LAST_ON_EACH_LINE
) )
161 wxSizerItem
* const item
= m_rows
.GetItem(n
);
162 wxCHECK_RET( item
, "invalid sizer item" );
164 // store the item we modified and its original proportion
165 item
->SetUserData(new wxPropChanger(*this, *itemLast
));
168 wxSize
wxWrapSizer::CalcMin()
170 if ( m_children
.empty() )
173 // We come here to calculate min size in two different situations:
174 // 1 - Immediately after InformFirstDirection, then we find a min size that
175 // uses one dimension maximally and the other direction minimally.
176 // 2 - Ordinary case, get a sensible min size value using the current line
177 // layout, trying to maintain the possibility to re-arrange lines by
182 // Case 1 above: InformFirstDirection() has just been called
185 // There are two different algorithms for finding a useful min size for
186 // a wrap sizer, depending on whether the first reported size component
187 // is the opposite as our own orientation (the simpler case) or the same
188 // one (more complicated).
189 if ( m_dirInform
== m_orient
)
190 CalcMinFromMajor(m_availSize
);
192 CalcMinFromMinor(m_availSize
);
194 else // Case 2 above: not immediately after InformFirstDirection()
196 if ( m_availSize
> 0 )
198 wxSize szAvail
; // Keep track of boundary so we don't overflow
199 if ( m_dirInform
== m_orient
)
200 szAvail
= SizeFromMajorMinor(m_availSize
, m_availableOtherDir
);
202 szAvail
= SizeFromMajorMinor(m_availableOtherDir
, m_availSize
);
204 CalcMinFittingSize(szAvail
);
206 else // Initial calculation, before we have size available to us
208 CalcMaxSingleItemSize();
215 void wxWrapSizer::CalcMinFittingSize(const wxSize
& szBoundary
)
217 // Min size based on current line layout. It is important to
218 // provide a smaller size when possible to allow for resizing with
219 // the help of re-arranging the lines.
220 wxSize sizeMin
= SizeFromMajorMinor(m_maxSizeMajor
, m_minSizeMinor
);
221 if ( m_minSizeMinor
< SizeInMinorDir(m_size
) &&
222 m_maxSizeMajor
< SizeInMajorDir(m_size
) )
228 // Try making it a bit more narrow
230 if ( m_minItemMajor
!= INT_MAX
&& m_maxSizeMajor
> 0 )
232 // We try to present a lower min value by removing an item in
233 // the major direction (and preserving current minor min size).
234 CalcMinFromMajor(m_maxSizeMajor
- m_minItemMajor
);
235 if ( m_minSize
.x
<= szBoundary
.x
&& m_minSize
.y
<= szBoundary
.y
)
237 SizeInMinorDir(m_minSize
) = SizeInMinorDir(sizeMin
);
244 // If failed finding little smaller area, go back to what we had
250 void wxWrapSizer::CalcMaxSingleItemSize()
252 // Find max item size in each direction
253 int maxMajor
= 0; // Widest item
254 int maxMinor
= 0; // Line height
255 for ( wxSizerItemList::const_iterator i
= m_children
.begin();
256 i
!= m_children
.end();
259 wxSizerItem
* const item
= *i
;
260 if ( item
->IsShown() )
262 wxSize sz
= item
->CalcMin();
263 if ( SizeInMajorDir(sz
) > maxMajor
)
264 maxMajor
= SizeInMajorDir(sz
);
265 if ( SizeInMinorDir(sz
) > maxMinor
)
266 maxMinor
= SizeInMinorDir(sz
);
270 // This is, of course, not our real minimal size but if we return more
271 // than this it would be impossible to shrink us to one row/column so
272 // we have to pretend that this is all we need for now.
273 m_minSize
= SizeFromMajorMinor(maxMajor
, maxMinor
);
276 void wxWrapSizer::CalcMinFromMajor(int totMajor
)
278 // Algorithm for calculating min size: (assuming horizontal orientation)
279 // This is the simpler case (known major size)
280 // X: Given, totMajor
281 // Y: Based on X, calculate how many lines needed
283 int maxTotalMajor
= 0; // max of rowTotalMajor over all rows
284 int minorSum
= 0; // sum of sizes of all rows in minor direction
285 int maxRowMinor
= 0; // max of item minor sizes in this row
286 int rowTotalMajor
= 0; // sum of major sizes of items in this row
288 // pack the items in each row until we reach totMajor, then start a new row
289 for ( wxSizerItemList::const_iterator i
= m_children
.begin();
290 i
!= m_children
.end();
293 wxSizerItem
* const item
= *i
;
294 if ( !item
->IsShown() )
297 wxSize minItemSize
= item
->CalcMin();
298 const int itemMajor
= SizeInMajorDir(minItemSize
);
299 const int itemMinor
= SizeInMinorDir(minItemSize
);
301 // check if this is the first item in a new row: if so, we have to put
302 // it in it, whether it fits or not, as it would never fit better
305 // otherwise check if we have enough space left for this item here
306 if ( !rowTotalMajor
|| rowTotalMajor
+ itemMajor
<= totMajor
)
309 rowTotalMajor
+= itemMajor
;
310 if ( itemMinor
> maxRowMinor
)
311 maxRowMinor
= itemMinor
;
313 else // start a new row
315 // minor size of the row is the max of minor sizes of its items
316 minorSum
+= maxRowMinor
;
317 if ( rowTotalMajor
> maxTotalMajor
)
318 maxTotalMajor
= rowTotalMajor
;
319 maxRowMinor
= itemMinor
;
320 rowTotalMajor
= itemMajor
;
324 // account for the last (unfinished) row too
325 minorSum
+= maxRowMinor
;
326 if ( rowTotalMajor
> maxTotalMajor
)
327 maxTotalMajor
= rowTotalMajor
;
329 m_minSize
= SizeFromMajorMinor(maxTotalMajor
, minorSum
);
332 // Helper struct for CalcMinFromMinor
335 wxWrapLine() : m_first(NULL
), m_width(0) { }
336 wxSizerItem
*m_first
;
337 int m_width
; // Width of line
340 void wxWrapSizer::CalcMinFromMinor(int totMinor
)
342 // Algorithm for calculating min size:
343 // This is the more complex case (known minor size)
345 // First step, find total sum of all items in primary direction
346 // and max item size in secondary direction, that gives initial
347 // estimate of the minimum number of lines.
349 int totMajor
= 0; // Sum of widths
350 int maxMinor
= 0; // Line height
351 int maxMajor
= 0; // Widest item
353 wxSizerItemList::compatibility_iterator node
= m_children
.GetFirst();
357 wxSizerItem
*item
= node
->GetData();
358 if ( item
->IsShown() )
360 sz
= item
->CalcMin();
361 totMajor
+= SizeInMajorDir(sz
);
362 if ( SizeInMinorDir(sz
)>maxMinor
)
363 maxMinor
= SizeInMinorDir(sz
);
364 if ( SizeInMajorDir(sz
)>maxMinor
)
365 maxMajor
= SizeInMajorDir(sz
);
368 node
= node
->GetNext();
372 if ( !itemCount
|| totMajor
==0 || maxMinor
==0 )
374 m_minSize
= wxSize(0,0);
378 // First attempt, use lines of average size:
379 int nrLines
= totMinor
/ maxMinor
; // Rounding down is right here
382 // Another simple case, everything fits on one line
383 m_minSize
= SizeFromMajorMinor(totMajor
,maxMinor
);
387 int lineSize
= totMajor
/ nrLines
;
388 if ( lineSize
<maxMajor
) // At least as wide as the widest element
391 // The algorithm is as follows (horz case):
392 // 1 - Vertical (minor) size is known.
393 // 2 - We have a reasonable estimated width from above
395 // 3a - Do layout with suggested width
396 // 3b - See how much we spill over in minor dir
397 // 3c - If no spill, we're done
398 // 3d - Otherwise increase width by known smallest item
401 // First algo step: put items on lines of known max width
402 wxVector
<wxWrapLine
*> lines
;
404 int sumMinor
; // Sum of all minor sizes (height of all lines)
406 // While we still have items 'spilling over' extend the tested line width
409 wxWrapLine
*line
= new wxWrapLine
;
410 lines
.push_back( line
);
412 int tailSize
= 0; // Width of what exceeds nrLines
415 for ( node
=m_children
.GetFirst(); node
; node
=node
->GetNext() )
417 wxSizerItem
*item
= node
->GetData();
418 if ( item
->IsShown() )
420 sz
= item
->GetMinSizeWithBorder();
421 if ( line
->m_width
+SizeInMajorDir(sz
)>lineSize
)
423 line
= new wxWrapLine
;
424 lines
.push_back(line
);
425 sumMinor
+= maxMinor
;
428 line
->m_width
+= SizeInMajorDir(sz
);
429 if ( line
->m_width
&& !line
->m_first
)
430 line
->m_first
= item
;
431 if ( SizeInMinorDir(sz
)>maxMinor
)
432 maxMinor
= SizeInMinorDir(sz
);
433 if ( sumMinor
+maxMinor
>totMinor
)
435 // Keep track of widest tail item
436 if ( SizeInMajorDir(sz
)>tailSize
)
437 tailSize
= SizeInMajorDir(sz
);
444 // Now look how much we need to extend our size
445 // We know we must have at least one more line than nrLines
446 // (otherwise no tail size).
447 int bestExtSize
= 0; // Minimum extension width for current tailSize
448 for ( int ix
=0; ix
<nrLines
; ix
++ )
450 // Take what is not used on this line, see how much extension we get
451 // by adding first item on next line.
452 int size
= lineSize
-lines
[ix
]->m_width
; // Left over at end of this line
453 int extSize
= GetSizeInMajorDir(lines
[ix
+1]->m_first
->GetMinSizeWithBorder()) - size
;
454 if ( (extSize
>=tailSize
&& (extSize
<bestExtSize
|| bestExtSize
<tailSize
)) ||
455 (extSize
>bestExtSize
&& bestExtSize
<tailSize
) )
456 bestExtSize
= extSize
;
458 // Have an extension size, ready to redo line layout
459 lineSize
+= bestExtSize
;
462 // Clear helper items
463 for ( wxVector
<wxWrapLine
*>::iterator it
=lines
.begin(); it
<lines
.end(); ++it
)
472 // Now have min size in the opposite direction
473 m_minSize
= SizeFromMajorMinor(lineSize
,sumMinor
);
476 void wxWrapSizer::FinishRow(size_t n
,
477 int rowMajor
, int rowMinor
,
478 wxSizerItem
*itemLast
)
480 // Account for the finished row size.
481 m_minSizeMinor
+= rowMinor
;
482 if ( rowMajor
> m_maxSizeMajor
)
483 m_maxSizeMajor
= rowMajor
;
485 // And adjust proportion of its last item if necessary.
486 AdjustLastRowItemProp(n
, itemLast
);
489 void wxWrapSizer::RecalcSizes()
491 // First restore any proportions we may have changed and remove the old rows
494 if ( m_children
.empty() )
497 // Put all our items into as many row box sizers as needed.
498 const int majorSize
= SizeInMajorDir(m_size
); // max size of each row
499 int rowTotalMajor
= 0; // running row major size
503 m_minItemMajor
= INT_MAX
;
506 // We need at least one row
508 wxSizer
*sizer
= GetRowSizer(nRow
);
510 wxSizerItem
*itemLast
= NULL
, // last item processed in this row
511 *itemSpace
= NULL
; // spacer which we delayed adding
513 // Now put our child items into child sizers instead
514 for ( wxSizerItemList::iterator i
= m_children
.begin();
515 i
!= m_children
.end();
518 wxSizerItem
* const item
= *i
;
519 if ( !item
->IsShown() )
522 wxSize minItemSize
= item
->GetMinSizeWithBorder();
523 const int itemMajor
= SizeInMajorDir(minItemSize
);
524 const int itemMinor
= SizeInMinorDir(minItemSize
);
525 if ( itemMajor
> 0 && itemMajor
< m_minItemMajor
)
526 m_minItemMajor
= itemMajor
;
528 // Is there more space on this line? Notice that if this is the first
529 // item we add it unconditionally as it wouldn't fit in the next line
530 // any better than in this one.
531 if ( !rowTotalMajor
|| rowTotalMajor
+ itemMajor
<= majorSize
)
533 // There is enough space here
534 rowTotalMajor
+= itemMajor
;
535 if ( itemMinor
> maxRowMinor
)
536 maxRowMinor
= itemMinor
;
538 else // Start a new row
540 FinishRow(nRow
, rowTotalMajor
, maxRowMinor
, itemLast
);
542 rowTotalMajor
= itemMajor
;
543 maxRowMinor
= itemMinor
;
545 // Get a new empty sizer to insert into
546 sizer
= GetRowSizer(++nRow
);
552 // Only remove first/last spaces if that flag is set
553 if ( (m_flags
& wxREMOVE_LEADING_SPACES
) && IsSpaceItem(item
) )
555 // Remember space only if we have a first item
561 if ( itemLast
&& itemSpace
)
563 // We had a spacer after a real item and now that we add
564 // another real item to the same row we need to add the spacer
566 sizer
->Add(itemSpace
);
569 // Notice that we reuse a pointer to our own sizer item here, so we
570 // must remember to remove it by calling ClearRows() to avoid
571 // double deletion later
578 // If item is a window, it now has a pointer to the child sizer,
579 // which is wrong. Set it to point to us.
580 if ( wxWindow
*win
= item
->GetWindow() )
581 win
->SetContainingSizer(this);
584 FinishRow(nRow
, rowTotalMajor
, maxRowMinor
, itemLast
);
586 // Now do layout on row sizer
587 m_rows
.SetDimension(m_position
, m_size
);