1 /*
2 * Copyright 2006-2009, Ingo Weinhold <ingo_weinhold@gmx.de>.
3 * Copyright 2015, Rene Gollent, rene@gollent.com.
4 * All rights reserved. Distributed under the terms of the MIT License.
5 */
6
7
8 #include "SplitLayout.h"
9
10 #include <new>
11 #include <stdio.h>
12
13 #include <ControlLook.h>
14 #include <LayoutItem.h>
15 #include <LayoutUtils.h>
16 #include <Message.h>
17 #include <View.h>
18
19 #include "OneElementLayouter.h"
20 #include "SimpleLayouter.h"
21
22
23 using std::nothrow;
24
25
26 // archivng constants
27 namespace {
28 const char* const kItemCollapsibleField = "BSplitLayout:item:collapsible";
29 const char* const kItemWeightField = "BSplitLayout:item:weight";
30 const char* const kSpacingField = "BSplitLayout:spacing";
31 const char* const kSplitterSizeField = "BSplitLayout:splitterSize";
32 const char* const kIsVerticalField = "BSplitLayout:vertical";
33 const char* const kInsetsField = "BSplitLayout:insets";
34 }
35
36
37 class BSplitLayout::ItemLayoutInfo {
38 public:
39 float weight;
40 BRect layoutFrame;
41 BSize min;
42 BSize max;
43 bool isVisible;
44 bool isCollapsible;
45
ItemLayoutInfo()46 ItemLayoutInfo()
47 :
48 weight(1.0f),
49 layoutFrame(0, 0, -1, -1),
50 min(),
51 max(),
52 isVisible(true),
53 isCollapsible(true)
54 {
55 }
56 };
57
58
59 class BSplitLayout::ValueRange {
60 public:
61 int32 sumValue; // including spacing
62 int32 previousMin;
63 int32 previousMax;
64 int32 previousSize;
65 int32 nextMin;
66 int32 nextMax;
67 int32 nextSize;
68 };
69
70
71 class BSplitLayout::SplitterItem : public BLayoutItem {
72 public:
SplitterItem(BSplitLayout * layout)73 SplitterItem(BSplitLayout* layout)
74 :
75 fLayout(layout),
76 fFrame()
77 {
78 }
79
80
MinSize()81 virtual BSize MinSize()
82 {
83 if (fLayout->Orientation() == B_HORIZONTAL)
84 return BSize(fLayout->SplitterSize() - 1, -1);
85 else
86 return BSize(-1, fLayout->SplitterSize() - 1);
87 }
88
MaxSize()89 virtual BSize MaxSize()
90 {
91 if (fLayout->Orientation() == B_HORIZONTAL)
92 return BSize(fLayout->SplitterSize() - 1, B_SIZE_UNLIMITED);
93 else
94 return BSize(B_SIZE_UNLIMITED, fLayout->SplitterSize() - 1);
95 }
96
PreferredSize()97 virtual BSize PreferredSize()
98 {
99 return MinSize();
100 }
101
Alignment()102 virtual BAlignment Alignment()
103 {
104 return BAlignment(B_ALIGN_HORIZONTAL_CENTER, B_ALIGN_VERTICAL_CENTER);
105 }
106
SetExplicitMinSize(BSize size)107 virtual void SetExplicitMinSize(BSize size)
108 {
109 // not allowed
110 }
111
SetExplicitMaxSize(BSize size)112 virtual void SetExplicitMaxSize(BSize size)
113 {
114 // not allowed
115 }
116
SetExplicitPreferredSize(BSize size)117 virtual void SetExplicitPreferredSize(BSize size)
118 {
119 // not allowed
120 }
121
SetExplicitAlignment(BAlignment alignment)122 virtual void SetExplicitAlignment(BAlignment alignment)
123 {
124 // not allowed
125 }
126
IsVisible()127 virtual bool IsVisible()
128 {
129 return true;
130 }
131
SetVisible(bool visible)132 virtual void SetVisible(bool visible)
133 {
134 // not allowed
135 }
136
137
Frame()138 virtual BRect Frame()
139 {
140 return fFrame;
141 }
142
SetFrame(BRect frame)143 virtual void SetFrame(BRect frame)
144 {
145 fFrame = frame;
146 }
147
148 private:
149 BSplitLayout* fLayout;
150 BRect fFrame;
151 };
152
153
154 // #pragma mark -
155
156
BSplitLayout(orientation orientation,float spacing)157 BSplitLayout::BSplitLayout(orientation orientation, float spacing)
158 :
159 fOrientation(orientation),
160 fLeftInset(0),
161 fRightInset(0),
162 fTopInset(0),
163 fBottomInset(0),
164 fSplitterSize(6),
165 fSpacing(BControlLook::ComposeSpacing(spacing)),
166
167 fSplitterItems(),
168 fVisibleItems(),
169 fMin(),
170 fMax(),
171 fPreferred(),
172
173 fHorizontalLayouter(NULL),
174 fVerticalLayouter(NULL),
175 fHorizontalLayoutInfo(NULL),
176 fVerticalLayoutInfo(NULL),
177
178 fHeightForWidthItems(),
179 fHeightForWidthVerticalLayouter(NULL),
180 fHeightForWidthHorizontalLayoutInfo(NULL),
181
182 fLayoutValid(false),
183
184 fCachedHeightForWidthWidth(-2),
185 fHeightForWidthVerticalLayouterWidth(-2),
186 fCachedMinHeightForWidth(-1),
187 fCachedMaxHeightForWidth(-1),
188 fCachedPreferredHeightForWidth(-1),
189
190 fDraggingStartPoint(),
191 fDraggingStartValue(0),
192 fDraggingCurrentValue(0),
193 fDraggingSplitterIndex(-1)
194 {
195 }
196
197
BSplitLayout(BMessage * from)198 BSplitLayout::BSplitLayout(BMessage* from)
199 :
200 BAbstractLayout(BUnarchiver::PrepareArchive(from)),
201 fOrientation(B_HORIZONTAL),
202 fLeftInset(0),
203 fRightInset(0),
204 fTopInset(0),
205 fBottomInset(0),
206 fSplitterSize(6),
207 fSpacing(be_control_look->DefaultItemSpacing()),
208
209 fSplitterItems(),
210 fVisibleItems(),
211 fMin(),
212 fMax(),
213 fPreferred(),
214
215 fHorizontalLayouter(NULL),
216 fVerticalLayouter(NULL),
217 fHorizontalLayoutInfo(NULL),
218 fVerticalLayoutInfo(NULL),
219
220 fHeightForWidthItems(),
221 fHeightForWidthVerticalLayouter(NULL),
222 fHeightForWidthHorizontalLayoutInfo(NULL),
223
224 fLayoutValid(false),
225
226 fCachedHeightForWidthWidth(-2),
227 fHeightForWidthVerticalLayouterWidth(-2),
228 fCachedMinHeightForWidth(-1),
229 fCachedMaxHeightForWidth(-1),
230 fCachedPreferredHeightForWidth(-1),
231
232 fDraggingStartPoint(),
233 fDraggingStartValue(0),
234 fDraggingCurrentValue(0),
235 fDraggingSplitterIndex(-1)
236 {
237 BUnarchiver unarchiver(from);
238
239 bool isVertical;
240 status_t err = from->FindBool(kIsVerticalField, &isVertical);
241 if (err != B_OK) {
242 unarchiver.Finish(err);
243 return;
244 }
245 fOrientation = (isVertical) ? B_VERTICAL : B_HORIZONTAL ;
246
247 BRect insets;
248 err = from->FindRect(kInsetsField, &insets);
249 if (err != B_OK) {
250 unarchiver.Finish(err);
251 return;
252 }
253 SetInsets(insets.left, insets.top, insets.right, insets.bottom);
254
255 err = from->FindFloat(kSplitterSizeField, &fSplitterSize);
256 if (err == B_OK)
257 err = from->FindFloat(kSpacingField, &fSpacing);
258
259 unarchiver.Finish(err);
260 }
261
262
~BSplitLayout()263 BSplitLayout::~BSplitLayout()
264 {
265 }
266
267
268 void
SetInsets(float left,float top,float right,float bottom)269 BSplitLayout::SetInsets(float left, float top, float right, float bottom)
270 {
271 fLeftInset = left;
272 fTopInset = top;
273 fRightInset = right;
274 fBottomInset = bottom;
275
276 InvalidateLayout();
277 }
278
279
280 void
GetInsets(float * left,float * top,float * right,float * bottom) const281 BSplitLayout::GetInsets(float* left, float* top, float* right,
282 float* bottom) const
283 {
284 if (left)
285 *left = fLeftInset;
286 if (top)
287 *top = fTopInset;
288 if (right)
289 *right = fRightInset;
290 if (bottom)
291 *bottom = fBottomInset;
292 }
293
294
295 float
Spacing() const296 BSplitLayout::Spacing() const
297 {
298 return fSpacing;
299 }
300
301
302 void
SetSpacing(float spacing)303 BSplitLayout::SetSpacing(float spacing)
304 {
305 spacing = BControlLook::ComposeSpacing(spacing);
306 if (spacing != fSpacing) {
307 fSpacing = spacing;
308
309 InvalidateLayout();
310 }
311 }
312
313
314 orientation
Orientation() const315 BSplitLayout::Orientation() const
316 {
317 return fOrientation;
318 }
319
320
321 void
SetOrientation(orientation orientation)322 BSplitLayout::SetOrientation(orientation orientation)
323 {
324 if (orientation != fOrientation) {
325 fOrientation = orientation;
326
327 InvalidateLayout();
328 }
329 }
330
331
332 float
SplitterSize() const333 BSplitLayout::SplitterSize() const
334 {
335 return fSplitterSize;
336 }
337
338
339 void
SetSplitterSize(float size)340 BSplitLayout::SetSplitterSize(float size)
341 {
342 if (size != fSplitterSize) {
343 fSplitterSize = size;
344
345 InvalidateLayout();
346 }
347 }
348
349
350 BLayoutItem*
AddView(BView * child)351 BSplitLayout::AddView(BView* child)
352 {
353 return BAbstractLayout::AddView(child);
354 }
355
356
357 BLayoutItem*
AddView(int32 index,BView * child)358 BSplitLayout::AddView(int32 index, BView* child)
359 {
360 return BAbstractLayout::AddView(index, child);
361 }
362
363
364 BLayoutItem*
AddView(BView * child,float weight)365 BSplitLayout::AddView(BView* child, float weight)
366 {
367 return AddView(-1, child, weight);
368 }
369
370
371 BLayoutItem*
AddView(int32 index,BView * child,float weight)372 BSplitLayout::AddView(int32 index, BView* child, float weight)
373 {
374 BLayoutItem* item = AddView(index, child);
375 if (item)
376 SetItemWeight(item, weight);
377
378 return item;
379 }
380
381
382 bool
AddItem(BLayoutItem * item)383 BSplitLayout::AddItem(BLayoutItem* item)
384 {
385 return BAbstractLayout::AddItem(item);
386 }
387
388
389 bool
AddItem(int32 index,BLayoutItem * item)390 BSplitLayout::AddItem(int32 index, BLayoutItem* item)
391 {
392 return BAbstractLayout::AddItem(index, item);
393 }
394
395
396 bool
AddItem(BLayoutItem * item,float weight)397 BSplitLayout::AddItem(BLayoutItem* item, float weight)
398 {
399 return AddItem(-1, item, weight);
400 }
401
402
403 bool
AddItem(int32 index,BLayoutItem * item,float weight)404 BSplitLayout::AddItem(int32 index, BLayoutItem* item, float weight)
405 {
406 bool success = AddItem(index, item);
407 if (success)
408 SetItemWeight(item, weight);
409
410 return success;
411 }
412
413
414 float
ItemWeight(int32 index) const415 BSplitLayout::ItemWeight(int32 index) const
416 {
417 if (index < 0 || index >= CountItems())
418 return 0;
419
420 return ItemWeight(ItemAt(index));
421 }
422
423
424 float
ItemWeight(BLayoutItem * item) const425 BSplitLayout::ItemWeight(BLayoutItem* item) const
426 {
427 if (ItemLayoutInfo* info = _ItemLayoutInfo(item))
428 return info->weight;
429 return 0;
430 }
431
432
433 void
SetItemWeight(int32 index,float weight,bool invalidateLayout)434 BSplitLayout::SetItemWeight(int32 index, float weight, bool invalidateLayout)
435 {
436 if (index < 0 || index >= CountItems())
437 return;
438
439 BLayoutItem* item = ItemAt(index);
440 SetItemWeight(item, weight);
441
442 if (fHorizontalLayouter) {
443 int32 visibleIndex = fVisibleItems.IndexOf(item);
444 if (visibleIndex >= 0) {
445 if (fOrientation == B_HORIZONTAL)
446 fHorizontalLayouter->SetWeight(visibleIndex, weight);
447 else
448 fVerticalLayouter->SetWeight(visibleIndex, weight);
449 }
450 }
451
452 if (invalidateLayout)
453 InvalidateLayout();
454 }
455
456
457 void
SetItemWeight(BLayoutItem * item,float weight)458 BSplitLayout::SetItemWeight(BLayoutItem* item, float weight)
459 {
460 if (ItemLayoutInfo* info = _ItemLayoutInfo(item))
461 info->weight = weight;
462 }
463
464
465 bool
IsCollapsible(int32 index) const466 BSplitLayout::IsCollapsible(int32 index) const
467 {
468 return _ItemLayoutInfo(ItemAt(index))->isCollapsible;
469 }
470
471
472 void
SetCollapsible(bool collapsible)473 BSplitLayout::SetCollapsible(bool collapsible)
474 {
475 SetCollapsible(0, CountItems() - 1, collapsible);
476 }
477
478
479 void
SetCollapsible(int32 index,bool collapsible)480 BSplitLayout::SetCollapsible(int32 index, bool collapsible)
481 {
482 SetCollapsible(index, index, collapsible);
483 }
484
485
486 void
SetCollapsible(int32 first,int32 last,bool collapsible)487 BSplitLayout::SetCollapsible(int32 first, int32 last, bool collapsible)
488 {
489 for (int32 i = first; i <= last; i++)
490 _ItemLayoutInfo(ItemAt(i))->isCollapsible = collapsible;
491 }
492
493
494 bool
IsItemCollapsed(int32 index) const495 BSplitLayout::IsItemCollapsed(int32 index) const
496 {
497 return !_ItemLayoutInfo(ItemAt(index))->isVisible;
498 }
499
500
501 void
SetItemCollapsed(int32 index,bool collapsed)502 BSplitLayout::SetItemCollapsed(int32 index, bool collapsed)
503 {
504 ItemAt(index)->SetVisible(!collapsed);
505
506 InvalidateLayout(true);
507 }
508
509
510 BSize
BaseMinSize()511 BSplitLayout::BaseMinSize()
512 {
513 _ValidateMinMax();
514
515 return _AddInsets(fMin);
516 }
517
518
519 BSize
BaseMaxSize()520 BSplitLayout::BaseMaxSize()
521 {
522 _ValidateMinMax();
523
524 return _AddInsets(fMax);
525 }
526
527
528 BSize
BasePreferredSize()529 BSplitLayout::BasePreferredSize()
530 {
531 _ValidateMinMax();
532
533 return _AddInsets(fPreferred);
534 }
535
536
537 BAlignment
BaseAlignment()538 BSplitLayout::BaseAlignment()
539 {
540 return BAbstractLayout::BaseAlignment();
541 }
542
543
544 bool
HasHeightForWidth()545 BSplitLayout::HasHeightForWidth()
546 {
547 _ValidateMinMax();
548
549 return !fHeightForWidthItems.IsEmpty();
550 }
551
552
553 void
GetHeightForWidth(float width,float * min,float * max,float * preferred)554 BSplitLayout::GetHeightForWidth(float width, float* min, float* max,
555 float* preferred)
556 {
557 if (!HasHeightForWidth())
558 return;
559
560 float innerWidth = _SubtractInsets(BSize(width, 0)).width;
561 _InternalGetHeightForWidth(innerWidth, false, min, max, preferred);
562 _AddInsets(min, max, preferred);
563 }
564
565
566 void
LayoutInvalidated(bool children)567 BSplitLayout::LayoutInvalidated(bool children)
568 {
569 delete fHorizontalLayouter;
570 delete fVerticalLayouter;
571 delete fHorizontalLayoutInfo;
572 delete fVerticalLayoutInfo;
573
574 fHorizontalLayouter = NULL;
575 fVerticalLayouter = NULL;
576 fHorizontalLayoutInfo = NULL;
577 fVerticalLayoutInfo = NULL;
578
579 _InvalidateCachedHeightForWidth();
580
581 fLayoutValid = false;
582 }
583
584
585 void
DoLayout()586 BSplitLayout::DoLayout()
587 {
588 _ValidateMinMax();
589
590 // layout the elements
591 BSize size = _SubtractInsets(LayoutArea().Size());
592 fHorizontalLayouter->Layout(fHorizontalLayoutInfo, size.width);
593
594 Layouter* verticalLayouter;
595 if (HasHeightForWidth()) {
596 float minHeight, maxHeight, preferredHeight;
597 _InternalGetHeightForWidth(size.width, true, &minHeight, &maxHeight,
598 &preferredHeight);
599 size.height = max_c(size.height, minHeight);
600 verticalLayouter = fHeightForWidthVerticalLayouter;
601 } else
602 verticalLayouter = fVerticalLayouter;
603
604 verticalLayouter->Layout(fVerticalLayoutInfo, size.height);
605
606 float xOffset = fLeftInset;
607 float yOffset = fTopInset;
608 float splitterWidth = 0; // pixel counts, no distances
609 float splitterHeight = 0; //
610 float xSpacing = 0;
611 float ySpacing = 0;
612 if (fOrientation == B_HORIZONTAL) {
613 splitterWidth = fSplitterSize;
614 splitterHeight = size.height + 1;
615 xSpacing = fSpacing;
616 } else {
617 splitterWidth = size.width + 1;
618 splitterHeight = fSplitterSize;
619 ySpacing = fSpacing;
620 }
621
622 int itemCount = CountItems();
623 for (int i = 0; i < itemCount; i++) {
624 // layout the splitter
625 if (i > 0) {
626 SplitterItem* splitterItem = _SplitterItemAt(i - 1);
627
628 _LayoutItem(splitterItem, BRect(xOffset, yOffset,
629 xOffset + splitterWidth - 1, yOffset + splitterHeight - 1),
630 true);
631
632 if (fOrientation == B_HORIZONTAL)
633 xOffset += splitterWidth + xSpacing;
634 else
635 yOffset += splitterHeight + ySpacing;
636 }
637
638 // layout the item
639 BLayoutItem* item = ItemAt(i);
640 int32 visibleIndex = fVisibleItems.IndexOf(item);
641 if (visibleIndex < 0) {
642 _LayoutItem(item, BRect(), false);
643 continue;
644 }
645
646 // get the dimensions of the item
647 float width = fHorizontalLayoutInfo->ElementSize(visibleIndex);
648 float height = fVerticalLayoutInfo->ElementSize(visibleIndex);
649
650 // place the component
651 _LayoutItem(item, BRect(xOffset, yOffset, xOffset + width,
652 yOffset + height), true);
653
654 if (fOrientation == B_HORIZONTAL)
655 xOffset += width + xSpacing + 1;
656 else
657 yOffset += height + ySpacing + 1;
658 }
659
660 fLayoutValid = true;
661 }
662
663
664 BRect
SplitterItemFrame(int32 index) const665 BSplitLayout::SplitterItemFrame(int32 index) const
666 {
667 if (SplitterItem* item = _SplitterItemAt(index))
668 return item->Frame();
669 return BRect();
670 }
671
672
673 bool
IsAboveSplitter(const BPoint & point) const674 BSplitLayout::IsAboveSplitter(const BPoint& point) const
675 {
676 return _SplitterItemAt(point) != NULL;
677 }
678
679
680 bool
StartDraggingSplitter(BPoint point)681 BSplitLayout::StartDraggingSplitter(BPoint point)
682 {
683 StopDraggingSplitter();
684
685 // Layout must be valid. Bail out, if it isn't.
686 if (!fLayoutValid)
687 return false;
688
689 // Things shouldn't be draggable, if we have a >= max layout.
690 BSize size = _SubtractInsets(LayoutArea().Size());
691 if ((fOrientation == B_HORIZONTAL && size.width >= fMax.width)
692 || (fOrientation == B_VERTICAL && size.height >= fMax.height)) {
693 return false;
694 }
695
696 int32 index = -1;
697 if (_SplitterItemAt(point, &index) != NULL) {
698 fDraggingStartPoint = Owner()->ConvertToScreen(point);
699 fDraggingStartValue = _SplitterValue(index);
700 fDraggingCurrentValue = fDraggingStartValue;
701 fDraggingSplitterIndex = index;
702
703 return true;
704 }
705
706 return false;
707 }
708
709
710 bool
DragSplitter(BPoint point)711 BSplitLayout::DragSplitter(BPoint point)
712 {
713 if (fDraggingSplitterIndex < 0)
714 return false;
715
716 point = Owner()->ConvertToScreen(point);
717
718 int32 valueDiff;
719 if (fOrientation == B_HORIZONTAL)
720 valueDiff = int32(point.x - fDraggingStartPoint.x);
721 else
722 valueDiff = int32(point.y - fDraggingStartPoint.y);
723
724 return _SetSplitterValue(fDraggingSplitterIndex,
725 fDraggingStartValue + valueDiff);
726 }
727
728
729 bool
StopDraggingSplitter()730 BSplitLayout::StopDraggingSplitter()
731 {
732 if (fDraggingSplitterIndex < 0)
733 return false;
734
735 // update the item weights
736 _UpdateSplitterWeights();
737
738 fDraggingSplitterIndex = -1;
739
740 return true;
741 }
742
743
744 int32
DraggedSplitter() const745 BSplitLayout::DraggedSplitter() const
746 {
747 return fDraggingSplitterIndex;
748 }
749
750
751 status_t
Archive(BMessage * into,bool deep) const752 BSplitLayout::Archive(BMessage* into, bool deep) const
753 {
754 BArchiver archiver(into);
755 status_t err = BAbstractLayout::Archive(into, deep);
756
757 if (err == B_OK)
758 err = into->AddBool(kIsVerticalField, fOrientation == B_VERTICAL);
759
760 if (err == B_OK) {
761 BRect insets(fLeftInset, fTopInset, fRightInset, fBottomInset);
762 err = into->AddRect(kInsetsField, insets);
763 }
764
765 if (err == B_OK)
766 err = into->AddFloat(kSplitterSizeField, fSplitterSize);
767
768 if (err == B_OK)
769 err = into->AddFloat(kSpacingField, fSpacing);
770
771 return archiver.Finish(err);
772 }
773
774
775 BArchivable*
Instantiate(BMessage * from)776 BSplitLayout::Instantiate(BMessage* from)
777 {
778 if (validate_instantiation(from, "BSplitLayout"))
779 return new(std::nothrow) BSplitLayout(from);
780 return NULL;
781 }
782
783
784 status_t
ItemArchived(BMessage * into,BLayoutItem * item,int32 index) const785 BSplitLayout::ItemArchived(BMessage* into, BLayoutItem* item, int32 index) const
786 {
787 ItemLayoutInfo* info = _ItemLayoutInfo(item);
788
789 status_t err = into->AddFloat(kItemWeightField, info->weight);
790 if (err == B_OK)
791 err = into->AddBool(kItemCollapsibleField, info->isCollapsible);
792
793 return err;
794 }
795
796
797 status_t
ItemUnarchived(const BMessage * from,BLayoutItem * item,int32 index)798 BSplitLayout::ItemUnarchived(const BMessage* from,
799 BLayoutItem* item, int32 index)
800 {
801 ItemLayoutInfo* info = _ItemLayoutInfo(item);
802 status_t err = from->FindFloat(kItemWeightField, index, &info->weight);
803
804 if (err == B_OK) {
805 bool* collapsible = &info->isCollapsible;
806 err = from->FindBool(kItemCollapsibleField, index, collapsible);
807 }
808 return err;
809 }
810
811
812 bool
ItemAdded(BLayoutItem * item,int32 atIndex)813 BSplitLayout::ItemAdded(BLayoutItem* item, int32 atIndex)
814 {
815 ItemLayoutInfo* itemInfo = new(nothrow) ItemLayoutInfo();
816 if (!itemInfo)
817 return false;
818
819 if (CountItems() > 1) {
820 SplitterItem* splitter = new(nothrow) SplitterItem(this);
821 ItemLayoutInfo* splitterInfo = new(nothrow) ItemLayoutInfo();
822 if (!splitter || !splitterInfo || !fSplitterItems.AddItem(splitter)) {
823 delete itemInfo;
824 delete splitter;
825 delete splitterInfo;
826 return false;
827 }
828 splitter->SetLayoutData(splitterInfo);
829 SetItemWeight(splitter, 0);
830 }
831
832 item->SetLayoutData(itemInfo);
833 SetItemWeight(item, 1);
834 return true;
835 }
836
837
838 void
ItemRemoved(BLayoutItem * item,int32 atIndex)839 BSplitLayout::ItemRemoved(BLayoutItem* item, int32 atIndex)
840 {
841 if (fSplitterItems.CountItems() > 0) {
842 SplitterItem* splitterItem = (SplitterItem*)fSplitterItems.RemoveItem(
843 fSplitterItems.CountItems() - 1);
844 delete _ItemLayoutInfo(splitterItem);
845 delete splitterItem;
846 }
847
848 delete _ItemLayoutInfo(item);
849 item->SetLayoutData(NULL);
850 }
851
852
853 void
_InvalidateCachedHeightForWidth()854 BSplitLayout::_InvalidateCachedHeightForWidth()
855 {
856 delete fHeightForWidthVerticalLayouter;
857 delete fHeightForWidthHorizontalLayoutInfo;
858
859 fHeightForWidthVerticalLayouter = NULL;
860 fHeightForWidthHorizontalLayoutInfo = NULL;
861
862 fCachedHeightForWidthWidth = -2;
863 fHeightForWidthVerticalLayouterWidth = -2;
864 }
865
866
867 BSplitLayout::SplitterItem*
_SplitterItemAt(const BPoint & point,int32 * index) const868 BSplitLayout::_SplitterItemAt(const BPoint& point, int32* index) const
869 {
870 int32 splitterCount = fSplitterItems.CountItems();
871 for (int32 i = 0; i < splitterCount; i++) {
872 SplitterItem* splitItem = _SplitterItemAt(i);
873 BRect frame = splitItem->Frame();
874 if (frame.Contains(point)) {
875 if (index != NULL)
876 *index = i;
877 return splitItem;
878 }
879 }
880 return NULL;
881 }
882
883
884 BSplitLayout::SplitterItem*
_SplitterItemAt(int32 index) const885 BSplitLayout::_SplitterItemAt(int32 index) const
886 {
887 return (SplitterItem*)fSplitterItems.ItemAt(index);
888 }
889
890
891 void
_GetSplitterValueRange(int32 index,ValueRange & range)892 BSplitLayout::_GetSplitterValueRange(int32 index, ValueRange& range)
893 {
894 ItemLayoutInfo* previousInfo = _ItemLayoutInfo(ItemAt(index));
895 ItemLayoutInfo* nextInfo = _ItemLayoutInfo(ItemAt(index + 1));
896 if (fOrientation == B_HORIZONTAL) {
897 range.previousMin = (int32)previousInfo->min.width + 1;
898 range.previousMax = (int32)previousInfo->max.width + 1;
899 range.previousSize = previousInfo->layoutFrame.IntegerWidth() + 1;
900 range.nextMin = (int32)nextInfo->min.width + 1;
901 range.nextMax = (int32)nextInfo->max.width + 1;
902 range.nextSize = nextInfo->layoutFrame.IntegerWidth() + 1;
903 } else {
904 range.previousMin = (int32)previousInfo->min.height + 1;
905 range.previousMax = (int32)previousInfo->max.height + 1;
906 range.previousSize = previousInfo->layoutFrame.IntegerHeight() + 1;
907 range.nextMin = (int32)nextInfo->min.height + 1;
908 range.nextMax = (int32)nextInfo->max.height + 1;
909 range.nextSize = (int32)nextInfo->layoutFrame.IntegerHeight() + 1;
910 }
911
912 range.sumValue = range.previousSize + range.nextSize;
913 if (previousInfo->isVisible)
914 range.sumValue += (int32)fSpacing;
915 if (nextInfo->isVisible)
916 range.sumValue += (int32)fSpacing;
917 }
918
919
920 int32
_SplitterValue(int32 index) const921 BSplitLayout::_SplitterValue(int32 index) const
922 {
923 ItemLayoutInfo* info = _ItemLayoutInfo(ItemAt(index));
924 if (info && info->isVisible) {
925 if (fOrientation == B_HORIZONTAL)
926 return info->layoutFrame.IntegerWidth() + 1 + (int32)fSpacing;
927 else
928 return info->layoutFrame.IntegerHeight() + 1 + (int32)fSpacing;
929 } else
930 return 0;
931 }
932
933
934 void
_LayoutItem(BLayoutItem * item,BRect frame,bool visible)935 BSplitLayout::_LayoutItem(BLayoutItem* item, BRect frame, bool visible)
936 {
937 // update the layout frame
938 ItemLayoutInfo* info = _ItemLayoutInfo(item);
939 info->isVisible = visible;
940 if (visible)
941 info->layoutFrame = frame;
942 else
943 info->layoutFrame = BRect(0, 0, -1, -1);
944
945 // update min/max
946 info->min = item->MinSize();
947 info->max = item->MaxSize();
948
949 if (item->HasHeightForWidth()) {
950 BSize size = _SubtractInsets(LayoutArea().Size());
951 float minHeight, maxHeight;
952 item->GetHeightForWidth(size.width, &minHeight, &maxHeight, NULL);
953 info->min.height = max_c(info->min.height, minHeight);
954 info->max.height = min_c(info->max.height, maxHeight);
955 }
956
957 // layout the item
958 if (visible)
959 item->AlignInFrame(frame);
960 }
961
962
963 void
_LayoutItem(BLayoutItem * item,ItemLayoutInfo * info)964 BSplitLayout::_LayoutItem(BLayoutItem* item, ItemLayoutInfo* info)
965 {
966 // update the visibility of the item
967 bool isVisible = item->IsVisible();
968 bool visibilityChanged = (info->isVisible != isVisible);
969 if (visibilityChanged)
970 item->SetVisible(info->isVisible);
971
972 // nothing more to do, if the item is not visible
973 if (!info->isVisible)
974 return;
975
976 item->AlignInFrame(info->layoutFrame);
977
978 // if the item became visible, we need to update its internal layout
979 if (visibilityChanged &&
980 (fOrientation != B_HORIZONTAL || !HasHeightForWidth())) {
981 item->Relayout(true);
982 }
983 }
984
985
986 bool
_SetSplitterValue(int32 index,int32 value)987 BSplitLayout::_SetSplitterValue(int32 index, int32 value)
988 {
989 // if both items are collapsed, nothing can be dragged
990 BLayoutItem* previousItem = ItemAt(index);
991 BLayoutItem* nextItem = ItemAt(index + 1);
992 ItemLayoutInfo* previousInfo = _ItemLayoutInfo(previousItem);
993 ItemLayoutInfo* nextInfo = _ItemLayoutInfo(nextItem);
994 ItemLayoutInfo* splitterInfo = _ItemLayoutInfo(_SplitterItemAt(index));
995 bool previousVisible = previousInfo->isVisible;
996 bool nextVisible = nextInfo->isVisible;
997 if (!previousVisible && !nextVisible)
998 return false;
999
1000 ValueRange range;
1001 _GetSplitterValueRange(index, range);
1002
1003 value = max_c(min_c(value, range.sumValue), -(int32)fSpacing);
1004
1005 int32 previousSize = value - (int32)fSpacing;
1006 int32 nextSize = range.sumValue - value - (int32)fSpacing;
1007
1008 // Note: While this collapsed-check is mathmatically correct (i.e. we
1009 // collapse an item, if it would become smaller than half its minimum
1010 // size), we might want to change it, since for the user it looks like
1011 // collapsing happens earlier. The reason being that the only visual mark
1012 // the user has is the mouse cursor which indeed hasn't crossed the middle
1013 // of the item yet.
1014 bool previousCollapsed = (previousSize <= range.previousMin / 2)
1015 && previousInfo->isCollapsible;
1016 bool nextCollapsed = (nextSize <= range.nextMin / 2)
1017 && nextInfo->isCollapsible;
1018 if (previousCollapsed && nextCollapsed) {
1019 // we cannot collapse both items; we have to decide for one
1020 if (previousSize < nextSize) {
1021 // collapse previous
1022 nextCollapsed = false;
1023 nextSize = range.sumValue - (int32)fSpacing;
1024 } else {
1025 // collapse next
1026 previousCollapsed = false;
1027 previousSize = range.sumValue - (int32)fSpacing;
1028 }
1029 }
1030
1031 if (previousCollapsed || nextCollapsed) {
1032 // one collapsed item -- check whether that violates the constraints
1033 // of the other one
1034 int32 availableSpace = range.sumValue - (int32)fSpacing;
1035 if (previousCollapsed) {
1036 if (availableSpace < range.nextMin
1037 || availableSpace > range.nextMax) {
1038 // we cannot collapse the previous item
1039 previousCollapsed = false;
1040 }
1041 } else {
1042 if (availableSpace < range.previousMin
1043 || availableSpace > range.previousMax) {
1044 // we cannot collapse the next item
1045 nextCollapsed = false;
1046 }
1047 }
1048 }
1049
1050 if (!(previousCollapsed || nextCollapsed)) {
1051 // no collapsed item -- check whether there is a close solution
1052 previousSize = value - (int32)fSpacing;
1053 nextSize = range.sumValue - value - (int32)fSpacing;
1054
1055 if (range.previousMin + range.nextMin + 2 * fSpacing > range.sumValue) {
1056 // we don't have enough space to uncollapse both items
1057 int32 availableSpace = range.sumValue - (int32)fSpacing;
1058 if (previousSize < nextSize && availableSpace >= range.nextMin
1059 && availableSpace <= range.nextMax
1060 && previousInfo->isCollapsible) {
1061 previousCollapsed = true;
1062 } else if (availableSpace >= range.previousMin
1063 && availableSpace <= range.previousMax
1064 && nextInfo->isCollapsible) {
1065 nextCollapsed = true;
1066 } else if (availableSpace >= range.nextMin
1067 && availableSpace <= range.nextMax
1068 && previousInfo->isCollapsible) {
1069 previousCollapsed = true;
1070 } else {
1071 if (previousSize < nextSize && previousInfo->isCollapsible) {
1072 previousCollapsed = true;
1073 } else if (nextInfo->isCollapsible) {
1074 nextCollapsed = true;
1075 } else {
1076 // Neither item is collapsible although there's not enough
1077 // space: Give them both their minimum size.
1078 previousSize = range.previousMin;
1079 nextSize = range.nextMin;
1080 }
1081 }
1082
1083 } else {
1084 // there is enough space for both items
1085 // make sure the min constraints are satisfied
1086 if (previousSize < range.previousMin) {
1087 previousSize = range.previousMin;
1088 nextSize = range.sumValue - previousSize - 2 * (int32)fSpacing;
1089 } else if (nextSize < range.nextMin) {
1090 nextSize = range.nextMin;
1091 previousSize = range.sumValue - nextSize - 2 * (int32)fSpacing;
1092 }
1093
1094 // if we can, also satisfy the max constraints
1095 if (range.previousMax + range.nextMax + 2 * (int32)fSpacing
1096 >= range.sumValue) {
1097 if (previousSize > range.previousMax) {
1098 previousSize = range.previousMax;
1099 nextSize = range.sumValue - previousSize
1100 - 2 * (int32)fSpacing;
1101 } else if (nextSize > range.nextMax) {
1102 nextSize = range.nextMax;
1103 previousSize = range.sumValue - nextSize
1104 - 2 * (int32)fSpacing;
1105 }
1106 }
1107 }
1108 }
1109
1110 // compute the size for one collapsed item; for none collapsed item we
1111 // already have correct values
1112 if (previousCollapsed || nextCollapsed) {
1113 int32 availableSpace = range.sumValue - (int32)fSpacing;
1114 if (previousCollapsed) {
1115 previousSize = 0;
1116 nextSize = availableSpace;
1117 } else {
1118 previousSize = availableSpace;
1119 nextSize = 0;
1120 }
1121 }
1122
1123 int32 newValue = previousSize + (previousCollapsed ? 0 : (int32)fSpacing);
1124 if (newValue == fDraggingCurrentValue) {
1125 // nothing changed
1126 return false;
1127 }
1128
1129 // something changed: we need to recompute the layout
1130 int32 baseOffset = -fDraggingCurrentValue;
1131 // offset to the current splitter position
1132 int32 splitterOffset = baseOffset + newValue;
1133 int32 nextOffset = splitterOffset + (int32)fSplitterSize + (int32)fSpacing;
1134
1135 BRect splitterFrame(splitterInfo->layoutFrame);
1136 if (fOrientation == B_HORIZONTAL) {
1137 // horizontal layout
1138 // previous item
1139 float left = splitterFrame.left + baseOffset;
1140 previousInfo->layoutFrame.Set(
1141 left,
1142 splitterFrame.top,
1143 left + previousSize - 1,
1144 splitterFrame.bottom);
1145
1146 // next item
1147 left = splitterFrame.left + nextOffset;
1148 nextInfo->layoutFrame.Set(
1149 left,
1150 splitterFrame.top,
1151 left + nextSize - 1,
1152 splitterFrame.bottom);
1153
1154 // splitter
1155 splitterInfo->layoutFrame.left += splitterOffset;
1156 splitterInfo->layoutFrame.right += splitterOffset;
1157 } else {
1158 // vertical layout
1159 // previous item
1160 float top = splitterFrame.top + baseOffset;
1161 previousInfo->layoutFrame.Set(
1162 splitterFrame.left,
1163 top,
1164 splitterFrame.right,
1165 top + previousSize - 1);
1166
1167 // next item
1168 top = splitterFrame.top + nextOffset;
1169 nextInfo->layoutFrame.Set(
1170 splitterFrame.left,
1171 top,
1172 splitterFrame.right,
1173 top + nextSize - 1);
1174
1175 // splitter
1176 splitterInfo->layoutFrame.top += splitterOffset;
1177 splitterInfo->layoutFrame.bottom += splitterOffset;
1178 }
1179
1180 previousInfo->isVisible = !previousCollapsed;
1181 nextInfo->isVisible = !nextCollapsed;
1182
1183 bool heightForWidth = (fOrientation == B_HORIZONTAL && HasHeightForWidth());
1184
1185 // If the item visibility is to be changed, we need to update the splitter
1186 // values now, since the visibility change will cause an invalidation.
1187 if (previousVisible != previousInfo->isVisible
1188 || nextVisible != nextInfo->isVisible || heightForWidth) {
1189 _UpdateSplitterWeights();
1190 }
1191
1192 // If we have height for width items, we need to invalidate the previous
1193 // and the next item. Actually we would only need to invalidate height for
1194 // width items, but since non height for width items might be aligned with
1195 // height for width items, we need to trigger a layout that creates a
1196 // context that spans all aligned items.
1197 // We invalidate already here, so that changing the items' size won't cause
1198 // an immediate relayout.
1199 if (heightForWidth) {
1200 previousItem->InvalidateLayout();
1201 nextItem->InvalidateLayout();
1202 }
1203
1204 // do the layout
1205 _LayoutItem(previousItem, previousInfo);
1206 _LayoutItem(_SplitterItemAt(index), splitterInfo);
1207 _LayoutItem(nextItem, nextInfo);
1208
1209 fDraggingCurrentValue = newValue;
1210
1211 return true;
1212 }
1213
1214
1215 BSplitLayout::ItemLayoutInfo*
_ItemLayoutInfo(BLayoutItem * item) const1216 BSplitLayout::_ItemLayoutInfo(BLayoutItem* item) const
1217 {
1218 return (ItemLayoutInfo*)item->LayoutData();
1219 }
1220
1221
1222 void
_UpdateSplitterWeights()1223 BSplitLayout::_UpdateSplitterWeights()
1224 {
1225 int32 count = CountItems();
1226 for (int32 i = 0; i < count; i++) {
1227 float weight;
1228 if (fOrientation == B_HORIZONTAL)
1229 weight = _ItemLayoutInfo(ItemAt(i))->layoutFrame.Width() + 1;
1230 else
1231 weight = _ItemLayoutInfo(ItemAt(i))->layoutFrame.Height() + 1;
1232
1233 SetItemWeight(i, weight, false);
1234 }
1235
1236 // Just updating the splitter weights is fine in principle. The next
1237 // LayoutItems() will use the correct values. But, if our orientation is
1238 // vertical, the cached height for width info needs to be flushed, or the
1239 // obsolete cached values will be used.
1240 if (fOrientation == B_VERTICAL)
1241 _InvalidateCachedHeightForWidth();
1242 }
1243
1244
1245 void
_ValidateMinMax()1246 BSplitLayout::_ValidateMinMax()
1247 {
1248 if (fHorizontalLayouter != NULL)
1249 return;
1250
1251 fLayoutValid = false;
1252
1253 fVisibleItems.MakeEmpty();
1254 fHeightForWidthItems.MakeEmpty();
1255
1256 _InvalidateCachedHeightForWidth();
1257
1258 // filter the visible items
1259 int32 itemCount = CountItems();
1260 for (int32 i = 0; i < itemCount; i++) {
1261 BLayoutItem* item = ItemAt(i);
1262 if (item->IsVisible())
1263 fVisibleItems.AddItem(item);
1264
1265 // Add "height for width" items even, if they aren't visible. Otherwise
1266 // we may get our parent into trouble, since we could change from
1267 // "height for width" to "not height for width".
1268 if (item->HasHeightForWidth())
1269 fHeightForWidthItems.AddItem(item);
1270 }
1271 itemCount = fVisibleItems.CountItems();
1272
1273 // create the layouters
1274 Layouter* itemLayouter = new SimpleLayouter(itemCount, 0);
1275
1276 if (fOrientation == B_HORIZONTAL) {
1277 fHorizontalLayouter = itemLayouter;
1278 fVerticalLayouter = new OneElementLayouter();
1279 } else {
1280 fHorizontalLayouter = new OneElementLayouter();
1281 fVerticalLayouter = itemLayouter;
1282 }
1283
1284 // tell the layouters about our constraints
1285 if (itemCount > 0) {
1286 for (int32 i = 0; i < itemCount; i++) {
1287 BLayoutItem* item = (BLayoutItem*)fVisibleItems.ItemAt(i);
1288 BSize min = item->MinSize();
1289 BSize max = item->MaxSize();
1290 BSize preferred = item->PreferredSize();
1291
1292 fHorizontalLayouter->AddConstraints(i, 1, min.width, max.width,
1293 preferred.width);
1294 fVerticalLayouter->AddConstraints(i, 1, min.height, max.height,
1295 preferred.height);
1296
1297 float weight = ItemWeight(item);
1298 fHorizontalLayouter->SetWeight(i, weight);
1299 fVerticalLayouter->SetWeight(i, weight);
1300 }
1301 }
1302
1303 fMin.width = fHorizontalLayouter->MinSize();
1304 fMin.height = fVerticalLayouter->MinSize();
1305 fMax.width = fHorizontalLayouter->MaxSize();
1306 fMax.height = fVerticalLayouter->MaxSize();
1307 fPreferred.width = fHorizontalLayouter->PreferredSize();
1308 fPreferred.height = fVerticalLayouter->PreferredSize();
1309
1310 fHorizontalLayoutInfo = fHorizontalLayouter->CreateLayoutInfo();
1311 if (fHeightForWidthItems.IsEmpty())
1312 fVerticalLayoutInfo = fVerticalLayouter->CreateLayoutInfo();
1313
1314 ResetLayoutInvalidation();
1315 }
1316
1317
1318 void
_InternalGetHeightForWidth(float width,bool realLayout,float * minHeight,float * maxHeight,float * preferredHeight)1319 BSplitLayout::_InternalGetHeightForWidth(float width, bool realLayout,
1320 float* minHeight, float* maxHeight, float* preferredHeight)
1321 {
1322 if ((realLayout && fHeightForWidthVerticalLayouterWidth != width)
1323 || (!realLayout && fCachedHeightForWidthWidth != width)) {
1324 // The general strategy is to clone the vertical layouter, which only
1325 // knows the general min/max constraints, do a horizontal layout for the
1326 // given width, and add the children's height for width constraints to
1327 // the cloned vertical layouter. If this method is invoked internally,
1328 // we keep the cloned vertical layouter, for it will be used for doing
1329 // the layout. Otherwise we just drop it after we've got the height for
1330 // width info.
1331
1332 // clone the vertical layouter and get the horizontal layout info to be used
1333 LayoutInfo* horizontalLayoutInfo = NULL;
1334 Layouter* verticalLayouter = fVerticalLayouter->CloneLayouter();
1335 if (realLayout) {
1336 horizontalLayoutInfo = fHorizontalLayoutInfo;
1337 delete fHeightForWidthVerticalLayouter;
1338 fHeightForWidthVerticalLayouter = verticalLayouter;
1339 delete fVerticalLayoutInfo;
1340 fVerticalLayoutInfo = verticalLayouter->CreateLayoutInfo();
1341 fHeightForWidthVerticalLayouterWidth = width;
1342 } else {
1343 if (fHeightForWidthHorizontalLayoutInfo == NULL) {
1344 delete fHeightForWidthHorizontalLayoutInfo;
1345 fHeightForWidthHorizontalLayoutInfo
1346 = fHorizontalLayouter->CreateLayoutInfo();
1347 }
1348 horizontalLayoutInfo = fHeightForWidthHorizontalLayoutInfo;
1349 }
1350
1351 // do the horizontal layout (already done when doing this for the real
1352 // layout)
1353 if (!realLayout)
1354 fHorizontalLayouter->Layout(horizontalLayoutInfo, width);
1355
1356 // add the children's height for width constraints
1357 int32 count = fHeightForWidthItems.CountItems();
1358 for (int32 i = 0; i < count; i++) {
1359 BLayoutItem* item = (BLayoutItem*)fHeightForWidthItems.ItemAt(i);
1360 int32 index = fVisibleItems.IndexOf(item);
1361 if (index >= 0) {
1362 float itemMinHeight, itemMaxHeight, itemPreferredHeight;
1363 item->GetHeightForWidth(
1364 horizontalLayoutInfo->ElementSize(index),
1365 &itemMinHeight, &itemMaxHeight, &itemPreferredHeight);
1366 verticalLayouter->AddConstraints(index, 1, itemMinHeight,
1367 itemMaxHeight, itemPreferredHeight);
1368 }
1369 }
1370
1371 // get the height for width info
1372 fCachedHeightForWidthWidth = width;
1373 fCachedMinHeightForWidth = verticalLayouter->MinSize();
1374 fCachedMaxHeightForWidth = verticalLayouter->MaxSize();
1375 fCachedPreferredHeightForWidth = verticalLayouter->PreferredSize();
1376 }
1377
1378 if (minHeight)
1379 *minHeight = fCachedMinHeightForWidth;
1380 if (maxHeight)
1381 *maxHeight = fCachedMaxHeightForWidth;
1382 if (preferredHeight)
1383 *preferredHeight = fCachedPreferredHeightForWidth;
1384 }
1385
1386
1387 float
_SplitterSpace() const1388 BSplitLayout::_SplitterSpace() const
1389 {
1390 int32 splitters = fSplitterItems.CountItems();
1391 float space = 0;
1392 if (splitters > 0) {
1393 space = (fVisibleItems.CountItems() + splitters - 1) * fSpacing
1394 + splitters * fSplitterSize;
1395 }
1396
1397 return space;
1398 }
1399
1400
1401 BSize
_AddInsets(BSize size)1402 BSplitLayout::_AddInsets(BSize size)
1403 {
1404 size.width = BLayoutUtils::AddDistances(size.width,
1405 fLeftInset + fRightInset - 1);
1406 size.height = BLayoutUtils::AddDistances(size.height,
1407 fTopInset + fBottomInset - 1);
1408
1409 float spacing = _SplitterSpace();
1410 if (fOrientation == B_HORIZONTAL)
1411 size.width = BLayoutUtils::AddDistances(size.width, spacing - 1);
1412 else
1413 size.height = BLayoutUtils::AddDistances(size.height, spacing - 1);
1414
1415 return size;
1416 }
1417
1418
1419 void
_AddInsets(float * minHeight,float * maxHeight,float * preferredHeight)1420 BSplitLayout::_AddInsets(float* minHeight, float* maxHeight,
1421 float* preferredHeight)
1422 {
1423 float insets = fTopInset + fBottomInset - 1;
1424 if (fOrientation == B_VERTICAL)
1425 insets += _SplitterSpace();
1426 if (minHeight)
1427 *minHeight = BLayoutUtils::AddDistances(*minHeight, insets);
1428 if (maxHeight)
1429 *maxHeight = BLayoutUtils::AddDistances(*maxHeight, insets);
1430 if (preferredHeight)
1431 *preferredHeight = BLayoutUtils::AddDistances(*preferredHeight, insets);
1432 }
1433
1434
1435 BSize
_SubtractInsets(BSize size)1436 BSplitLayout::_SubtractInsets(BSize size)
1437 {
1438 size.width = BLayoutUtils::SubtractDistances(size.width,
1439 fLeftInset + fRightInset - 1);
1440 size.height = BLayoutUtils::SubtractDistances(size.height,
1441 fTopInset + fBottomInset - 1);
1442
1443 float spacing = _SplitterSpace();
1444 if (fOrientation == B_HORIZONTAL)
1445 size.width = BLayoutUtils::SubtractDistances(size.width, spacing - 1);
1446 else
1447 size.height = BLayoutUtils::SubtractDistances(size.height, spacing - 1);
1448
1449 return size;
1450 }
1451
1452