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