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