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