1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        src/common/wrapsizer.cpp
3 // Purpose:     provides wxWrapSizer class for layout
4 // Author:      Arne Steinarson
5 // Created:     2008-05-08
6 // Copyright:   (c) Arne Steinarson
7 // Licence:     wxWindows licence
8 /////////////////////////////////////////////////////////////////////////////
9 
10 // ============================================================================
11 // declarations
12 // ============================================================================
13 
14 // ----------------------------------------------------------------------------
15 // headers
16 // ----------------------------------------------------------------------------
17 
18 // For compilers that support precompilation, includes "wx.h".
19 #include "wx/wxprec.h"
20 
21 #ifdef __BORLANDC__
22     #pragma hdrstop
23 #endif
24 
25 #include "wx/wrapsizer.h"
26 #include "wx/vector.h"
27 
28 namespace
29 {
30 
31 // ----------------------------------------------------------------------------
32 // helper local classes
33 // ----------------------------------------------------------------------------
34 
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
38 {
39 public:
wxPropChanger(wxSizer & sizer,wxSizerItem & item)40     wxPropChanger(wxSizer& sizer, wxSizerItem& item)
41         : m_sizer(sizer),
42           m_item(item),
43           m_propOld(item.GetProportion())
44     {
45         // ensure that this item expands more than all the other ones
46         item.SetProportion(INT_MAX);
47     }
48 
~wxPropChanger()49     ~wxPropChanger()
50     {
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);
54     }
55 
56 private:
57     wxSizer& m_sizer;
58     wxSizerItem& m_item;
59     const int m_propOld;
60 
61     wxDECLARE_NO_COPY_CLASS(wxPropChanger);
62 };
63 
64 } // anonymous namespace
65 
66 // ============================================================================
67 // wxWrapSizer implementation
68 // ============================================================================
69 
IMPLEMENT_DYNAMIC_CLASS(wxWrapSizer,wxBoxSizer)70 IMPLEMENT_DYNAMIC_CLASS(wxWrapSizer, wxBoxSizer)
71 
72 wxWrapSizer::wxWrapSizer(int orient, int flags)
73            : wxBoxSizer(orient),
74              m_flags(flags),
75              m_dirInform(0),
76              m_availSize(-1),
77              m_availableOtherDir(0),
78              m_lastUsed(true),
79              m_minSizeMinor(0),
80              m_maxSizeMajor(0),
81              m_minItemMajor(INT_MAX),
82              m_rows(orient ^ wxBOTH)
83 {
84 }
85 
~wxWrapSizer()86 wxWrapSizer::~wxWrapSizer()
87 {
88     ClearRows();
89 }
90 
ClearRows()91 void wxWrapSizer::ClearRows()
92 {
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(),
99                                   end = rows.end();
100           i != end;
101           ++i )
102     {
103         wxSizerItem * const item = *i;
104         wxSizer * const row = item->GetSizer();
105         if ( !row )
106         {
107             wxFAIL_MSG( "all elements of m_rows must be sizers" );
108             continue;
109         }
110 
111         row->GetChildren().clear();
112 
113         wxPropChanger * const
114             propChanger = static_cast<wxPropChanger *>(item->GetUserData());
115         if ( propChanger )
116         {
117             // this deletes propChanger and so restores the old proportion
118             item->SetUserData(NULL);
119         }
120     }
121 }
122 
GetRowSizer(size_t n)123 wxSizer *wxWrapSizer::GetRowSizer(size_t n)
124 {
125     const wxSizerItemList& rows = m_rows.GetChildren();
126     if ( n < rows.size() )
127         return rows[n]->GetSizer();
128 
129     wxSizer * const sizer = new wxBoxSizer(GetOrientation());
130     m_rows.Add(sizer, wxSizerFlags().Expand());
131     return sizer;
132 }
133 
InformFirstDirection(int direction,int size,int availableOtherDir)134 bool wxWrapSizer::InformFirstDirection(int direction,
135                                        int size,
136                                        int availableOtherDir)
137 {
138     if ( !direction )
139         return false;
140 
141     // Store the values for later use
142     m_availSize = size;
143     m_availableOtherDir = availableOtherDir +
144                             (direction == wxHORIZONTAL ? m_minSize.y
145                                                        : m_minSize.x);
146     m_dirInform = direction;
147     m_lastUsed = false;
148     return true;
149 }
150 
151 
AdjustLastRowItemProp(size_t n,wxSizerItem * itemLast)152 void wxWrapSizer::AdjustLastRowItemProp(size_t n, wxSizerItem *itemLast)
153 {
154     if ( !itemLast || !(m_flags & wxEXTEND_LAST_ON_EACH_LINE) )
155     {
156         // nothing to do
157         return;
158     }
159 
160     wxSizerItem * const item = m_rows.GetItem(n);
161     wxCHECK_RET( item, "invalid sizer item" );
162 
163     // store the item we modified and its original proportion
164     item->SetUserData(new wxPropChanger(*this, *itemLast));
165 }
166 
CalcMin()167 wxSize wxWrapSizer::CalcMin()
168 {
169     if ( m_children.empty() )
170         return wxSize();
171 
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
177     //     sizing
178 
179     if ( !m_lastUsed )
180     {
181         // Case 1 above: InformFirstDirection() has just been called
182         m_lastUsed = true;
183 
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);
190         else
191             CalcMinFromMinor(m_availSize);
192     }
193     else // Case 2 above: not immediately after InformFirstDirection()
194     {
195         if ( m_availSize > 0 )
196         {
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);
200             else
201                 szAvail = SizeFromMajorMinor(m_availableOtherDir, m_availSize);
202 
203             CalcMinFittingSize(szAvail);
204         }
205         else // Initial calculation, before we have size available to us
206         {
207             CalcMaxSingleItemSize();
208         }
209     }
210 
211     return m_minSize;
212 }
213 
CalcMinFittingSize(const wxSize & szBoundary)214 void wxWrapSizer::CalcMinFittingSize(const wxSize& szBoundary)
215 {
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) )
222     {
223         m_minSize = sizeMin;
224     }
225     else
226     {
227         // Try making it a bit more narrow
228         bool done = false;
229         if ( m_minItemMajor != INT_MAX && m_maxSizeMajor > 0 )
230         {
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 )
235             {
236                 SizeInMinorDir(m_minSize) = SizeInMinorDir(sizeMin);
237                 done = true;
238             }
239         }
240 
241         if ( !done )
242         {
243             // If failed finding little smaller area, go back to what we had
244             m_minSize = sizeMin;
245         }
246     }
247 }
248 
CalcMaxSingleItemSize()249 void wxWrapSizer::CalcMaxSingleItemSize()
250 {
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();
256           ++i )
257     {
258         wxSizerItem * const item = *i;
259         if ( item->IsShown() )
260         {
261             wxSize sz = item->CalcMin();
262             if ( SizeInMajorDir(sz) > maxMajor )
263                 maxMajor = SizeInMajorDir(sz);
264             if ( SizeInMinorDir(sz) > maxMinor )
265                 maxMinor = SizeInMinorDir(sz);
266         }
267     }
268 
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);
273 }
274 
CalcMinFromMajor(int totMajor)275 void wxWrapSizer::CalcMinFromMajor(int totMajor)
276 {
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
281 
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
286 
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();
290           ++i )
291     {
292         wxSizerItem * const item = *i;
293         if ( !item->IsShown() )
294             continue;
295 
296         wxSize minItemSize = item->CalcMin();
297         const int itemMajor = SizeInMajorDir(minItemSize);
298         const int itemMinor = SizeInMinorDir(minItemSize);
299 
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
302         // anyhow
303         //
304         // otherwise check if we have enough space left for this item here
305         if ( !rowTotalMajor || rowTotalMajor + itemMajor <= totMajor )
306         {
307             // continue this row
308             rowTotalMajor += itemMajor;
309             if ( itemMinor > maxRowMinor )
310                 maxRowMinor = itemMinor;
311         }
312         else // start a new row
313         {
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;
320         }
321     }
322 
323     // account for the last (unfinished) row too
324     minorSum += maxRowMinor;
325     if ( rowTotalMajor > maxTotalMajor )
326         maxTotalMajor = rowTotalMajor;
327 
328     m_minSize = SizeFromMajorMinor(maxTotalMajor, minorSum);
329 }
330 
331 // Helper struct for CalcMinFromMinor
332 struct wxWrapLine
333 {
wxWrapLinewxWrapLine334     wxWrapLine() : m_first(NULL), m_width(0) { }
335     wxSizerItem *m_first;
336     int m_width;        // Width of line
337 };
338 
CalcMinFromMinor(int totMinor)339 void wxWrapSizer::CalcMinFromMinor(int totMinor)
340 {
341     // Algorithm for calculating min size:
342     // This is the more complex case (known minor size)
343 
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.
347 
348     int totMajor = 0;    // Sum of widths
349     int maxMinor = 0;    // Line height
350     int maxMajor = 0;    // Widest item
351     int itemCount = 0;
352     wxSizerItemList::compatibility_iterator node = m_children.GetFirst();
353     wxSize sz;
354     while (node)
355     {
356         wxSizerItem *item = node->GetData();
357         if ( item->IsShown() )
358         {
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);
365             itemCount++;
366         }
367         node = node->GetNext();
368     }
369 
370     // The trivial case
371     if ( !itemCount || totMajor==0 || maxMinor==0 )
372     {
373         m_minSize = wxSize(0,0);
374         return;
375     }
376 
377     // First attempt, use lines of average size:
378     int nrLines = totMinor / maxMinor; // Rounding down is right here
379     if ( nrLines<=1 )
380     {
381         // Another simple case, everything fits on one line
382         m_minSize = SizeFromMajorMinor(totMajor,maxMinor);
383         return;
384     }
385 
386     int lineSize = totMajor / nrLines;
387     if ( lineSize<maxMajor )     // At least as wide as the widest element
388         lineSize = maxMajor;
389 
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
393     // 3 - Loop
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
398     //      and redo loop
399 
400     // First algo step: put items on lines of known max width
401     wxVector<wxWrapLine*> lines;
402 
403     int sumMinor;       // Sum of all minor sizes (height of all lines)
404 
405     // While we still have items 'spilling over' extend the tested line width
406     for ( ;; )
407     {
408         wxWrapLine *line = new wxWrapLine;
409         lines.push_back( line );
410 
411         int tailSize = 0;   // Width of what exceeds nrLines
412         maxMinor = 0;
413         sumMinor = 0;
414         for ( node=m_children.GetFirst(); node; node=node->GetNext() )
415         {
416             wxSizerItem *item = node->GetData();
417             if ( item->IsShown() )
418             {
419                 sz = item->GetMinSizeWithBorder();
420                 if ( line->m_width+SizeInMajorDir(sz)>lineSize )
421                 {
422                     line = new wxWrapLine;
423                     lines.push_back(line);
424                     sumMinor += maxMinor;
425                     maxMinor = 0;
426                 }
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 )
433                 {
434                     // Keep track of widest tail item
435                     if ( SizeInMajorDir(sz)>tailSize )
436                         tailSize = SizeInMajorDir(sz);
437                 }
438             }
439         }
440 
441         if ( tailSize )
442         {
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++ )
448             {
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;
456             }
457             // Have an extension size, ready to redo line layout
458             lineSize += bestExtSize;
459         }
460 
461         // Clear helper items
462         for ( wxVector<wxWrapLine*>::iterator it=lines.begin(); it<lines.end(); ++it )
463             delete *it;
464         lines.clear();
465 
466         // No spill over?
467         if ( !tailSize )
468             break;
469     }
470 
471     // Now have min size in the opposite direction
472     m_minSize = SizeFromMajorMinor(lineSize,sumMinor);
473 }
474 
FinishRow(size_t n,int rowMajor,int rowMinor,wxSizerItem * itemLast)475 void wxWrapSizer::FinishRow(size_t n,
476                             int rowMajor, int rowMinor,
477                             wxSizerItem *itemLast)
478 {
479     // Account for the finished row size.
480     m_minSizeMinor += rowMinor;
481     if ( rowMajor > m_maxSizeMajor )
482         m_maxSizeMajor = rowMajor;
483 
484     // And adjust proportion of its last item if necessary.
485     AdjustLastRowItemProp(n, itemLast);
486 }
487 
RecalcSizes()488 void wxWrapSizer::RecalcSizes()
489 {
490     // First restore any proportions we may have changed and remove the old rows
491     ClearRows();
492 
493     if ( m_children.empty() )
494         return;
495 
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
499     int maxRowMinor = 0;
500 
501     m_minSizeMinor = 0;
502     m_minItemMajor = INT_MAX;
503     m_maxSizeMajor = 0;
504 
505     // We need at least one row
506     size_t nRow = 0;
507     wxSizer *sizer = GetRowSizer(nRow);
508 
509     wxSizerItem *itemLast = NULL,   // last item processed in this row
510                 *itemSpace = NULL;  // spacer which we delayed adding
511 
512     // Now put our child items into child sizers instead
513     for ( wxSizerItemList::iterator i = m_children.begin();
514           i != m_children.end();
515           ++i )
516     {
517         wxSizerItem * const item = *i;
518         if ( !item->IsShown() )
519             continue;
520 
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;
526 
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 )
531         {
532             // There is enough space here
533             rowTotalMajor += itemMajor;
534             if ( itemMinor > maxRowMinor )
535                 maxRowMinor = itemMinor;
536         }
537         else // Start a new row
538         {
539             FinishRow(nRow, rowTotalMajor, maxRowMinor, itemLast);
540 
541             rowTotalMajor = itemMajor;
542             maxRowMinor = itemMinor;
543 
544             // Get a new empty sizer to insert into
545             sizer = GetRowSizer(++nRow);
546 
547             itemLast =
548             itemSpace = NULL;
549         }
550 
551         // Only remove first/last spaces if that flag is set
552         if ( (m_flags & wxREMOVE_LEADING_SPACES) && IsSpaceItem(item) )
553         {
554             // Remember space only if we have a first item
555             if ( itemLast )
556                 itemSpace = item;
557         }
558         else // not a space
559         {
560             if ( itemLast && itemSpace )
561             {
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
564                 // between them two.
565                 sizer->Add(itemSpace);
566             }
567 
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
571             sizer->Add(item);
572 
573             itemLast = item;
574             itemSpace = NULL;
575         }
576 
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);
581     }
582 
583     FinishRow(nRow, rowTotalMajor, maxRowMinor, itemLast);
584 
585     // Now do layout on row sizer
586     m_rows.SetDimension(m_position, m_size);
587 }
588 
589 
590