Applied #15393 (dghart) fixing wxRichTextTable for percentage widths
[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 // 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:
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
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
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
86 wxWrapSizer::~wxWrapSizer()
87 {
88 ClearRows();
89 }
90
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
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
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
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
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
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
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
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 {
334 wxWrapLine() : m_first(NULL), m_width(0) { }
335 wxSizerItem *m_first;
336 int m_width; // Width of line
337 };
338
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
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
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