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