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 DECLARE_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
180 wxSize szBoundary
; // Keep track of boundary so we don't overflow
181 if ( m_availSize
> 0 )
183 if ( m_dirInform
== m_orient
)
184 szBoundary
= SizeFromMajorMinor(m_availSize
, m_availableOtherDir
);
186 szBoundary
= SizeFromMajorMinor(m_availableOtherDir
, m_availSize
);
191 // Case 1 above: InformFirstDirection() has just been called
194 // There are two different algorithms for finding a useful min size for
195 // a wrap sizer, depending on whether the first reported size component
196 // is the opposite as our own orientation (the simpler case) or the same
197 // one (more complicated).
198 wxSize szMinPrev
= m_minSize
;
199 if ( m_dirInform
== m_orient
)
200 CalcMinFromMajor(m_availSize
);
202 CalcMinFromMinor(m_availSize
);
204 // If overflowing given boundary, go back to previous min size
205 if ( m_minSize
.x
> szBoundary
.x
|| m_minSize
.y
>szBoundary
.y
)
206 m_minSize
= szMinPrev
;
208 else // Case 2 above: not immediately after InformFirstDirection()
210 if ( m_availSize
> 0 )
212 CalcMinFittingSize(szBoundary
);
214 else // Initial calculation, before we have size available to us
216 CalcMaxSingleItemSize();
223 void wxWrapSizer::CalcMinFittingSize(const wxSize
& szBoundary
)
225 // Min size based on current line layout. It is important to
226 // provide a smaller size when possible to allow for resizing with
227 // the help of re-arranging the lines.
228 wxSize sizeMin
= SizeFromMajorMinor(m_maxSizeMajor
, m_minSizeMinor
);
229 if ( m_minSizeMinor
< SizeInMinorDir(m_size
) &&
230 m_maxSizeMajor
< SizeInMajorDir(m_size
) )
236 // Try making it a bit more narrow
238 if ( m_minItemMajor
!= INT_MAX
&& m_maxSizeMajor
> 0 )
240 // We try to present a lower min value by removing an item in
241 // the major direction (and preserving current minor min size).
242 CalcMinFromMajor(m_maxSizeMajor
- m_minItemMajor
);
243 if ( m_minSize
.x
<= szBoundary
.x
&& m_minSize
.y
<= szBoundary
.y
)
245 SizeInMinorDir(m_minSize
) = SizeInMinorDir(sizeMin
);
252 // If failed finding little smaller area, go back to what we had
258 void wxWrapSizer::CalcMaxSingleItemSize()
260 // Find max item size in each direction
261 int maxMajor
= 0; // Widest item
262 int maxMinor
= 0; // Line height
263 for ( wxSizerItemList::const_iterator i
= m_children
.begin();
264 i
!= m_children
.end();
267 wxSizerItem
* const item
= *i
;
268 if ( item
->IsShown() )
270 wxSize sz
= item
->CalcMin();
271 if ( SizeInMajorDir(sz
) > maxMajor
)
272 maxMajor
= SizeInMajorDir(sz
);
273 if ( SizeInMinorDir(sz
) > maxMinor
)
274 maxMinor
= SizeInMinorDir(sz
);
278 // This is, of course, not our real minimal size but if we return more
279 // than this it would be impossible to shrink us to one row/column so
280 // we have to pretend that this is all we need for now.
281 m_minSize
= SizeFromMajorMinor(maxMajor
, maxMinor
);
284 void wxWrapSizer::CalcMinFromMajor(int totMajor
)
286 // Algorithm for calculating min size: (assuming horizontal orientation)
287 // This is the simpler case (known major size)
288 // X: Given, totMajor
289 // Y: Based on X, calculate how many lines needed
291 int maxTotalMajor
= 0; // max of rowTotalMajor over all rows
292 int minorSum
= 0; // sum of sizes of all rows in minor direction
293 int maxRowMinor
= 0; // max of item minor sizes in this row
294 int rowTotalMajor
= 0; // sum of major sizes of items in this row
296 // pack the items in each row until we reach totMajor, then start a new row
297 for ( wxSizerItemList::const_iterator i
= m_children
.begin();
298 i
!= m_children
.end();
301 wxSizerItem
* const item
= *i
;
302 if ( !item
->IsShown() )
305 wxSize minItemSize
= item
->CalcMin();
306 const int itemMajor
= SizeInMajorDir(minItemSize
);
307 const int itemMinor
= SizeInMinorDir(minItemSize
);
309 // check if this is the first item in a new row: if so, we have to put
310 // it in it, whether it fits or not, as it would never fit better
313 // otherwise check if we have enough space left for this item here
314 if ( !rowTotalMajor
|| rowTotalMajor
+ itemMajor
<= totMajor
)
317 rowTotalMajor
+= itemMajor
;
318 if ( itemMinor
> maxRowMinor
)
319 maxRowMinor
= itemMinor
;
321 else // start a new row
323 // minor size of the row is the max of minor sizes of its items
324 minorSum
+= maxRowMinor
;
325 if ( rowTotalMajor
> maxTotalMajor
)
326 maxTotalMajor
= rowTotalMajor
;
327 maxRowMinor
= itemMinor
;
328 rowTotalMajor
= itemMajor
;
332 // account for the last (unfinished) row too
333 minorSum
+= maxRowMinor
;
334 if ( rowTotalMajor
> maxTotalMajor
)
335 maxTotalMajor
= rowTotalMajor
;
337 m_minSize
= SizeFromMajorMinor(maxTotalMajor
, minorSum
);
340 // Helper struct for CalcMinFromMinor
343 wxWrapLine() : m_first(NULL
), m_width(0) { }
344 wxSizerItem
*m_first
;
345 int m_width
; // Width of line
348 void wxWrapSizer::CalcMinFromMinor(int totMinor
)
350 // Algorithm for calculating min size:
351 // This is the more complex case (known minor size)
353 // First step, find total sum of all items in primary direction
354 // and max item size in secondary direction, that gives initial
355 // estimate of the minimum number of lines.
357 int totMajor
= 0; // Sum of widths
358 int maxMinor
= 0; // Line height
359 int maxMajor
= 0; // Widest item
361 wxSizerItemList::compatibility_iterator node
= m_children
.GetFirst();
365 wxSizerItem
*item
= node
->GetData();
366 if ( item
->IsShown() )
368 sz
= item
->CalcMin();
369 totMajor
+= SizeInMajorDir(sz
);
370 if ( SizeInMinorDir(sz
)>maxMinor
)
371 maxMinor
= SizeInMinorDir(sz
);
372 if ( SizeInMajorDir(sz
)>maxMinor
)
373 maxMajor
= SizeInMajorDir(sz
);
376 node
= node
->GetNext();
380 if ( !itemCount
|| totMajor
==0 || maxMinor
==0 )
382 m_minSize
= wxSize(0,0);
386 // First attempt, use lines of average size:
387 int nrLines
= totMinor
/ maxMinor
; // Rounding down is right here
390 // Another simple case, everything fits on one line
391 m_minSize
= SizeFromMajorMinor(totMajor
,maxMinor
);
395 int lineSize
= totMajor
/ nrLines
;
396 if ( lineSize
<maxMajor
) // At least as wide as the widest element
399 // The algorithm is as follows (horz case):
400 // 1 - Vertical (minor) size is known.
401 // 2 - We have a reasonable estimated width from above
403 // 3a - Do layout with suggested width
404 // 3b - See how much we spill over in minor dir
405 // 3c - If no spill, we're done
406 // 3d - Otherwise increase width by known smallest item
409 // First algo step: put items on lines of known max width
410 wxVector
<wxWrapLine
*> lines
;
412 int sumMinor
; // Sum of all minor sizes (height of all lines)
414 // While we still have items 'spilling over' extend the tested line width
417 wxWrapLine
*line
= new wxWrapLine
;
418 lines
.push_back( line
);
420 int tailSize
= 0; // Width of what exceeds nrLines
423 for ( node
=m_children
.GetFirst(); node
; node
=node
->GetNext() )
425 wxSizerItem
*item
= node
->GetData();
426 if ( item
->IsShown() )
428 sz
= item
->GetMinSizeWithBorder();
429 if ( line
->m_width
+SizeInMajorDir(sz
)>lineSize
)
431 line
= new wxWrapLine
;
432 lines
.push_back(line
);
433 sumMinor
+= maxMinor
;
436 line
->m_width
+= SizeInMajorDir(sz
);
437 if ( line
->m_width
&& !line
->m_first
)
438 line
->m_first
= item
;
439 if ( SizeInMinorDir(sz
)>maxMinor
)
440 maxMinor
= SizeInMinorDir(sz
);
441 if ( sumMinor
+maxMinor
>totMinor
)
443 // Keep track of widest tail item
444 if ( SizeInMajorDir(sz
)>tailSize
)
445 tailSize
= SizeInMajorDir(sz
);
452 // Now look how much we need to extend our size
453 // We know we must have at least one more line than nrLines
454 // (otherwise no tail size).
455 int bestExtSize
= 0; // Minimum extension width for current tailSize
456 for ( int ix
=0; ix
<nrLines
; ix
++ )
458 // Take what is not used on this line, see how much extension we get
459 // by adding first item on next line.
460 int size
= lineSize
-lines
[ix
]->m_width
; // Left over at end of this line
461 int extSize
= GetSizeInMajorDir(lines
[ix
+1]->m_first
->GetMinSizeWithBorder()) - size
;
462 if ( (extSize
>=tailSize
&& (extSize
<bestExtSize
|| bestExtSize
<tailSize
)) ||
463 (extSize
>bestExtSize
&& bestExtSize
<tailSize
) )
464 bestExtSize
= extSize
;
466 // Have an extension size, ready to redo line layout
467 lineSize
+= bestExtSize
;
470 // Clear helper items
471 for ( wxVector
<wxWrapLine
*>::iterator it
=lines
.begin(); it
<lines
.end(); ++it
)
480 // Now have min size in the opposite direction
481 m_minSize
= SizeFromMajorMinor(lineSize
,sumMinor
);
484 void wxWrapSizer::FinishRow(size_t n
,
485 int rowMajor
, int rowMinor
,
486 wxSizerItem
*itemLast
)
488 // Account for the finished row size.
489 m_minSizeMinor
+= rowMinor
;
490 if ( rowMajor
> m_maxSizeMajor
)
491 m_maxSizeMajor
= rowMajor
;
493 // And adjust proportion of its last item if necessary.
494 AdjustLastRowItemProp(n
, itemLast
);
497 void wxWrapSizer::RecalcSizes()
499 // First restore any proportions we may have changed and remove the old rows
502 if ( m_children
.empty() )
505 // Put all our items into as many row box sizers as needed.
506 const int majorSize
= SizeInMajorDir(m_size
); // max size of each row
507 int rowTotalMajor
= 0; // running row major size
511 m_minItemMajor
= INT_MAX
;
514 // We need at least one row
516 wxSizer
*sizer
= GetRowSizer(nRow
);
518 wxSizerItem
*itemLast
= NULL
, // last item processed in this row
519 *itemSpace
= NULL
; // spacer which we delayed adding
521 // Now put our child items into child sizers instead
522 for ( wxSizerItemList::iterator i
= m_children
.begin();
523 i
!= m_children
.end();
526 wxSizerItem
* const item
= *i
;
527 if ( !item
->IsShown() )
530 wxSize minItemSize
= item
->GetMinSizeWithBorder();
531 const int itemMajor
= SizeInMajorDir(minItemSize
);
532 const int itemMinor
= SizeInMinorDir(minItemSize
);
533 if ( itemMajor
> 0 && itemMajor
< m_minItemMajor
)
534 m_minItemMajor
= itemMajor
;
536 // Is there more space on this line? Notice that if this is the first
537 // item we add it unconditionally as it wouldn't fit in the next line
538 // any better than in this one.
539 if ( !rowTotalMajor
|| rowTotalMajor
+ itemMajor
<= majorSize
)
541 // There is enough space here
542 rowTotalMajor
+= itemMajor
;
543 if ( itemMinor
> maxRowMinor
)
544 maxRowMinor
= itemMinor
;
546 else // Start a new row
548 FinishRow(nRow
, rowTotalMajor
, maxRowMinor
, itemLast
);
550 rowTotalMajor
= itemMajor
;
551 maxRowMinor
= itemMinor
;
553 // Get a new empty sizer to insert into
554 sizer
= GetRowSizer(++nRow
);
560 // Only remove first/last spaces if that flag is set
561 if ( (m_flags
& wxREMOVE_LEADING_SPACES
) && IsSpaceItem(item
) )
563 // Remember space only if we have a first item
569 if ( itemLast
&& itemSpace
)
571 // We had a spacer after a real item and now that we add
572 // another real item to the same row we need to add the spacer
574 sizer
->Add(itemSpace
);
577 // Notice that we reuse a pointer to our own sizer item here, so we
578 // must remember to remove it by calling ClearRows() to avoid
579 // double deletion later
586 // If item is a window, it now has a pointer to the child sizer,
587 // which is wrong. Set it to point to us.
588 if ( wxWindow
*win
= item
->GetWindow() )
589 win
->SetContainingSizer(this);
592 FinishRow(nRow
, rowTotalMajor
, maxRowMinor
, itemLast
);
594 // Now do layout on row sizer
595 m_rows
.SetDimension(m_position
, m_size
);