xref: /haiku/src/kits/interface/SplitLayout.cpp (revision 3333f968888ae8017be5c01149e619a533b0aba1)
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