]> git.saurik.com Git - wxWidgets.git/blob - src/common/wrapsizer.cpp
ff7b0310ec387b06bde7bc79a651028880f3b54e
[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 wxSize szBoundary; // Keep track of boundary so we don't overflow
181 if ( m_availSize > 0 )
182 {
183 if ( m_dirInform == m_orient )
184 szBoundary = SizeFromMajorMinor(m_availSize, m_availableOtherDir);
185 else
186 szBoundary = SizeFromMajorMinor(m_availableOtherDir, m_availSize);
187 }
188
189 if ( !m_lastUsed )
190 {
191 // Case 1 above: InformFirstDirection() has just been called
192 m_lastUsed = true;
193
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);
201 else
202 CalcMinFromMinor(m_availSize);
203
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;
207 }
208 else // Case 2 above: not immediately after InformFirstDirection()
209 {
210 if ( m_availSize > 0 )
211 {
212 CalcMinFittingSize(szBoundary);
213 }
214 else // Initial calculation, before we have size available to us
215 {
216 CalcMaxSingleItemSize();
217 }
218 }
219
220 return m_minSize;
221 }
222
223 void wxWrapSizer::CalcMinFittingSize(const wxSize& szBoundary)
224 {
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) )
231 {
232 m_minSize = sizeMin;
233 }
234 else
235 {
236 // Try making it a bit more narrow
237 bool done = false;
238 if ( m_minItemMajor != INT_MAX && m_maxSizeMajor > 0 )
239 {
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 )
244 {
245 SizeInMinorDir(m_minSize) = SizeInMinorDir(sizeMin);
246 done = true;
247 }
248 }
249
250 if ( !done )
251 {
252 // If failed finding little smaller area, go back to what we had
253 m_minSize = sizeMin;
254 }
255 }
256 }
257
258 void wxWrapSizer::CalcMaxSingleItemSize()
259 {
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();
265 ++i )
266 {
267 wxSizerItem * const item = *i;
268 if ( item->IsShown() )
269 {
270 wxSize sz = item->CalcMin();
271 if ( SizeInMajorDir(sz) > maxMajor )
272 maxMajor = SizeInMajorDir(sz);
273 if ( SizeInMinorDir(sz) > maxMinor )
274 maxMinor = SizeInMinorDir(sz);
275 }
276 }
277
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);
282 }
283
284 void wxWrapSizer::CalcMinFromMajor(int totMajor)
285 {
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
290
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
295
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();
299 ++i )
300 {
301 wxSizerItem * const item = *i;
302 if ( !item->IsShown() )
303 continue;
304
305 wxSize minItemSize = item->CalcMin();
306 const int itemMajor = SizeInMajorDir(minItemSize);
307 const int itemMinor = SizeInMinorDir(minItemSize);
308
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
311 // anyhow
312 //
313 // otherwise check if we have enough space left for this item here
314 if ( !rowTotalMajor || rowTotalMajor + itemMajor <= totMajor )
315 {
316 // continue this row
317 rowTotalMajor += itemMajor;
318 if ( itemMinor > maxRowMinor )
319 maxRowMinor = itemMinor;
320 }
321 else // start a new row
322 {
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;
329 }
330 }
331
332 // account for the last (unfinished) row too
333 minorSum += maxRowMinor;
334 if ( rowTotalMajor > maxTotalMajor )
335 maxTotalMajor = rowTotalMajor;
336
337 m_minSize = SizeFromMajorMinor(maxTotalMajor, minorSum);
338 }
339
340 // Helper struct for CalcMinFromMinor
341 struct wxWrapLine
342 {
343 wxWrapLine() : m_first(NULL), m_width(0) { }
344 wxSizerItem *m_first;
345 int m_width; // Width of line
346 };
347
348 void wxWrapSizer::CalcMinFromMinor(int totMinor)
349 {
350 // Algorithm for calculating min size:
351 // This is the more complex case (known minor size)
352
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.
356
357 int totMajor = 0; // Sum of widths
358 int maxMinor = 0; // Line height
359 int maxMajor = 0; // Widest item
360 int itemCount = 0;
361 wxSizerItemList::compatibility_iterator node = m_children.GetFirst();
362 wxSize sz;
363 while (node)
364 {
365 wxSizerItem *item = node->GetData();
366 if ( item->IsShown() )
367 {
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);
374 itemCount++;
375 }
376 node = node->GetNext();
377 }
378
379 // The trivial case
380 if ( !itemCount || totMajor==0 || maxMinor==0 )
381 {
382 m_minSize = wxSize(0,0);
383 return;
384 }
385
386 // First attempt, use lines of average size:
387 int nrLines = totMinor / maxMinor; // Rounding down is right here
388 if ( nrLines<=1 )
389 {
390 // Another simple case, everything fits on one line
391 m_minSize = SizeFromMajorMinor(totMajor,maxMinor);
392 return;
393 }
394
395 int lineSize = totMajor / nrLines;
396 if ( lineSize<maxMajor ) // At least as wide as the widest element
397 lineSize = maxMajor;
398
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
402 // 3 - Loop
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
407 // and redo loop
408
409 // First algo step: put items on lines of known max width
410 wxVector<wxWrapLine*> lines;
411
412 int sumMinor; // Sum of all minor sizes (height of all lines)
413
414 // While we still have items 'spilling over' extend the tested line width
415 for ( ;; )
416 {
417 wxWrapLine *line = new wxWrapLine;
418 lines.push_back( line );
419
420 int tailSize = 0; // Width of what exceeds nrLines
421 maxMinor = 0;
422 sumMinor = 0;
423 for ( node=m_children.GetFirst(); node; node=node->GetNext() )
424 {
425 wxSizerItem *item = node->GetData();
426 if ( item->IsShown() )
427 {
428 sz = item->GetMinSizeWithBorder();
429 if ( line->m_width+SizeInMajorDir(sz)>lineSize )
430 {
431 line = new wxWrapLine;
432 lines.push_back(line);
433 sumMinor += maxMinor;
434 maxMinor = 0;
435 }
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 )
442 {
443 // Keep track of widest tail item
444 if ( SizeInMajorDir(sz)>tailSize )
445 tailSize = SizeInMajorDir(sz);
446 }
447 }
448 }
449
450 if ( tailSize )
451 {
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++ )
457 {
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;
465 }
466 // Have an extension size, ready to redo line layout
467 lineSize += bestExtSize;
468 }
469
470 // Clear helper items
471 for ( wxVector<wxWrapLine*>::iterator it=lines.begin(); it<lines.end(); ++it )
472 delete *it;
473 lines.clear();
474
475 // No spill over?
476 if ( !tailSize )
477 break;
478 }
479
480 // Now have min size in the opposite direction
481 m_minSize = SizeFromMajorMinor(lineSize,sumMinor);
482 }
483
484 void wxWrapSizer::FinishRow(size_t n,
485 int rowMajor, int rowMinor,
486 wxSizerItem *itemLast)
487 {
488 // Account for the finished row size.
489 m_minSizeMinor += rowMinor;
490 if ( rowMajor > m_maxSizeMajor )
491 m_maxSizeMajor = rowMajor;
492
493 // And adjust proportion of its last item if necessary.
494 AdjustLastRowItemProp(n, itemLast);
495 }
496
497 void wxWrapSizer::RecalcSizes()
498 {
499 // First restore any proportions we may have changed and remove the old rows
500 ClearRows();
501
502 if ( m_children.empty() )
503 return;
504
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
508 int maxRowMinor = 0;
509
510 m_minSizeMinor = 0;
511 m_minItemMajor = INT_MAX;
512 m_maxSizeMajor = 0;
513
514 // We need at least one row
515 size_t nRow = 0;
516 wxSizer *sizer = GetRowSizer(nRow);
517
518 wxSizerItem *itemLast = NULL, // last item processed in this row
519 *itemSpace = NULL; // spacer which we delayed adding
520
521 // Now put our child items into child sizers instead
522 for ( wxSizerItemList::iterator i = m_children.begin();
523 i != m_children.end();
524 ++i )
525 {
526 wxSizerItem * const item = *i;
527 if ( !item->IsShown() )
528 continue;
529
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;
535
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 )
540 {
541 // There is enough space here
542 rowTotalMajor += itemMajor;
543 if ( itemMinor > maxRowMinor )
544 maxRowMinor = itemMinor;
545 }
546 else // Start a new row
547 {
548 FinishRow(nRow, rowTotalMajor, maxRowMinor, itemLast);
549
550 rowTotalMajor = itemMajor;
551 maxRowMinor = itemMinor;
552
553 // Get a new empty sizer to insert into
554 sizer = GetRowSizer(++nRow);
555
556 itemLast =
557 itemSpace = NULL;
558 }
559
560 // Only remove first/last spaces if that flag is set
561 if ( (m_flags & wxREMOVE_LEADING_SPACES) && IsSpaceItem(item) )
562 {
563 // Remember space only if we have a first item
564 if ( itemLast )
565 itemSpace = item;
566 }
567 else // not a space
568 {
569 if ( itemLast && itemSpace )
570 {
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
573 // between them two.
574 sizer->Add(itemSpace);
575 }
576
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
580 sizer->Add(item);
581
582 itemLast = item;
583 itemSpace = NULL;
584 }
585
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);
590 }
591
592 FinishRow(nRow, rowTotalMajor, maxRowMinor, itemLast);
593
594 // Now do layout on row sizer
595 m_rows.SetDimension(m_position, m_size);
596 }
597
598