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