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