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