1/*
2 * Copyright 2006-2010, Ingo Weinhold <ingo_weinhold@gmx.de>.
3 * All rights reserved. Distributed under the terms of the MIT License.
4 */
5
6
7#include <TwoDimensionalLayout.h>
8
9#include <stdio.h>
10
11#include <ControlLook.h>
12#include <LayoutContext.h>
13#include <LayoutItem.h>
14#include <LayoutUtils.h>
15#include <List.h>
16#include <Message.h>
17#include <View.h>
18
19#include <Referenceable.h>
20
21#include "CollapsingLayouter.h"
22
23
24// Some words of explanation:
25//
26// This class is the base class for BLayouts that organize their items
27// on a grid, with each item covering one or more grid cells (always a
28// rectangular area). The derived classes only need to implement the
29// hooks reporting the constraints for the items and additional constraints
30// for the rows and columns. This class does all the layouting.
31//
32// The basic idea of the layout process is simple. The horizontal and the
33// vertical dimensions are laid out independently and the items are set to the
34// resulting locations and sizes. The "height for width" feature makes the
35// height depend on the width, which makes things a bit more complicated.
36// The horizontal dimension must be laid out first and and the results are
37// fed into the vertical layout process.
38//
39// The AlignLayoutWith() feature, which allows to align layouts for different
40// views with each other, causes the need for the three inner *Layouter classes.
41// For each set of layouts aligned with each other with respect to one
42// dimension that dimension must be laid out together. The class responsible
43// is CompoundLayouter; one instance exists per such set. The derived class
44// VerticalCompoundLayouter is a specialization for the vertical dimension
45// which additionally takes care of the "height for width" feature. Per layout
46// a single LocalLayouter exists, which comprises the required glue layout
47// code and serves as a proxy for the layout, providing service methods
48// needed by the CompoundLayouter.
49
50// TODO: Check for memory leaks!
51
52//#define DEBUG_LAYOUT
53
54// CompoundLayouter
55class BTwoDimensionalLayout::CompoundLayouter : public BReferenceable {
56public:
57								CompoundLayouter(orientation orientation);
58	virtual						~CompoundLayouter();
59
60			orientation			Orientation();
61
62	virtual	Layouter*			GetLayouter(bool minMax);
63
64			LayoutInfo*			GetLayoutInfo();
65
66			void				AddLocalLayouter(LocalLayouter* localLayouter);
67			void				RemoveLocalLayouter(
68									LocalLayouter* localLayouter);
69
70			status_t			AddAlignedLayoutsToArchive(BArchiver* archiver,
71									LocalLayouter* requestedBy);
72
73			void				AbsorbCompoundLayouter(CompoundLayouter* other);
74
75	virtual	void				InvalidateLayout();
76			bool				IsMinMaxValid();
77			void				ValidateMinMax();
78			void				Layout(float size, LocalLayouter* localLayouter,
79									BLayoutContext* context);
80
81protected:
82	virtual	void				DoLayout(float size,
83									LocalLayouter* localLayouter,
84									BLayoutContext* context);
85
86			Layouter*			fLayouter;
87			LayoutInfo*			fLayoutInfo;
88			orientation			fOrientation;
89			BList				fLocalLayouters;
90			BLayoutContext*		fLayoutContext;
91			float				fLastLayoutSize;
92
93			void				_PrepareItems();
94
95			int32				_CountElements();
96			bool				_HasMultiElementItems();
97
98			void				_AddConstraints(Layouter* layouter);
99
100			float				_Spacing();
101};
102
103// VerticalCompoundLayouter
104class BTwoDimensionalLayout::VerticalCompoundLayouter
105	: public CompoundLayouter, private BLayoutContextListener {
106public:
107								VerticalCompoundLayouter();
108
109	virtual	Layouter*			GetLayouter(bool minMax);
110
111	virtual	void				InvalidateLayout();
112
113			void				InvalidateHeightForWidth();
114
115			void				InternalGetHeightForWidth(
116									LocalLayouter* localLayouter,
117									BLayoutContext* context,
118									bool realLayout, float* minHeight,
119									float* maxHeight, float* preferredHeight);
120
121protected:
122	virtual	void				DoLayout(float size,
123									LocalLayouter* localLayouter,
124									BLayoutContext* context);
125
126private:
127			Layouter*			fHeightForWidthLayouter;
128			float				fCachedMinHeightForWidth;
129			float				fCachedMaxHeightForWidth;
130			float				fCachedPreferredHeightForWidth;
131			BLayoutContext*		fHeightForWidthLayoutContext;
132
133			bool				_HasHeightForWidth();
134
135			bool				_SetHeightForWidthLayoutContext(
136									BLayoutContext* context);
137
138	// BLayoutContextListener
139	virtual	void				LayoutContextLeft(BLayoutContext* context);
140};
141
142// LocalLayouter
143class BTwoDimensionalLayout::LocalLayouter : private BLayoutContextListener {
144public:
145								LocalLayouter(BTwoDimensionalLayout* layout);
146								~LocalLayouter();
147
148	// interface for the BTwoDimensionalLayout class
149
150			BSize				MinSize();
151			BSize				MaxSize();
152			BSize				PreferredSize();
153
154			void				InvalidateLayout();
155			void				Layout(BSize size);
156
157			BRect				ItemFrame(Dimensions itemDimensions);
158
159			void				ValidateMinMax();
160
161			void				DoHorizontalLayout(float width);
162
163			void				InternalGetHeightForWidth(float width,
164									float* minHeight, float* maxHeight,
165									float* preferredHeight);
166
167			void				AlignWith(LocalLayouter* other,
168									orientation orientation);
169
170	// Archiving stuff
171			status_t			AddAlignedLayoutsToArchive(BArchiver* archiver);
172			status_t			AddOwnerToArchive(BArchiver* archiver,
173									CompoundLayouter* requestedBy,
174									bool& _wasAvailable);
175			status_t			AlignLayoutsFromArchive(BUnarchiver* unarchiver,
176									orientation posture);
177
178
179	// interface for the compound layout context
180
181			void				PrepareItems(
182									CompoundLayouter* compoundLayouter);
183			int32				CountElements(
184									CompoundLayouter* compoundLayouter);
185			bool				HasMultiElementItems(
186									CompoundLayouter* compoundLayouter);
187
188			void				AddConstraints(
189									CompoundLayouter* compoundLayouter,
190									Layouter* layouter);
191
192			float				Spacing(CompoundLayouter* compoundLayouter);
193
194			bool				HasHeightForWidth();
195
196			bool				AddHeightForWidthConstraints(
197									VerticalCompoundLayouter* compoundLayouter,
198									Layouter* layouter,
199									BLayoutContext* context);
200			void				SetHeightForWidthConstraintsAdded(bool added);
201
202			void				SetCompoundLayouter(
203									CompoundLayouter* compoundLayouter,
204									orientation orientation);
205
206			void				InternalInvalidateLayout(
207									CompoundLayouter* compoundLayouter);
208
209	// implementation private
210private:
211			BTwoDimensionalLayout* fLayout;
212			CompoundLayouter*	fHLayouter;
213			VerticalCompoundLayouter* fVLayouter;
214			BList				fHeightForWidthItems;
215
216	// active layout context when doing last horizontal layout
217			BLayoutContext*		fHorizontalLayoutContext;
218			float				fHorizontalLayoutWidth;
219			bool				fHeightForWidthConstraintsAdded;
220
221			void				_SetHorizontalLayoutContext(
222									BLayoutContext* context, float width);
223
224	// BLayoutContextListener
225	virtual	void				LayoutContextLeft(BLayoutContext* context);
226};
227
228
229// #pragma mark -
230
231// archiving constants
232namespace {
233	const char* const kHAlignedLayoutField = "BTwoDimensionalLayout:"
234		"halignedlayout";
235	const char* const kVAlignedLayoutField = "BTwoDimensionalLayout:"
236		"valignedlayout";
237	const char* const kInsetsField = "BTwoDimensionalLayout:insets";
238	const char* const kSpacingField = "BTwoDimensionalLayout:spacing";
239		// kSpacingField = {fHSpacing, fVSpacing}
240}
241
242
243BTwoDimensionalLayout::BTwoDimensionalLayout()
244	:
245	fLeftInset(0),
246	fRightInset(0),
247	fTopInset(0),
248	fBottomInset(0),
249	fHSpacing(0),
250	fVSpacing(0),
251	fLocalLayouter(new LocalLayouter(this))
252{
253}
254
255
256BTwoDimensionalLayout::BTwoDimensionalLayout(BMessage* from)
257	:
258	BAbstractLayout(from),
259	fLeftInset(0),
260	fRightInset(0),
261	fTopInset(0),
262	fBottomInset(0),
263	fHSpacing(0),
264	fVSpacing(0),
265	fLocalLayouter(new LocalLayouter(this))
266{
267	BRect insets;
268	from->FindRect(kInsetsField, &insets);
269	SetInsets(insets.left, insets.top, insets.right, insets.bottom);
270
271	from->FindFloat(kSpacingField, 0, &fHSpacing);
272	from->FindFloat(kSpacingField, 1, &fVSpacing);
273}
274
275
276BTwoDimensionalLayout::~BTwoDimensionalLayout()
277{
278	delete fLocalLayouter;
279}
280
281
282void
283BTwoDimensionalLayout::SetInsets(float left, float top, float right,
284	float bottom)
285{
286	fLeftInset = BControlLook::ComposeSpacing(left);
287	fTopInset = BControlLook::ComposeSpacing(top);
288	fRightInset = BControlLook::ComposeSpacing(right);
289	fBottomInset = BControlLook::ComposeSpacing(bottom);
290
291	InvalidateLayout();
292}
293
294
295void
296BTwoDimensionalLayout::SetInsets(float horizontal, float vertical)
297{
298	fLeftInset = BControlLook::ComposeSpacing(horizontal);
299	fRightInset = fLeftInset;
300
301	fTopInset = BControlLook::ComposeSpacing(vertical);
302	fBottomInset = fTopInset;
303
304	InvalidateLayout();
305}
306
307
308void
309BTwoDimensionalLayout::SetInsets(float insets)
310{
311	fLeftInset = BControlLook::ComposeSpacing(insets);
312	fRightInset = fLeftInset;
313	fTopInset = fLeftInset;
314	fBottomInset = fLeftInset;
315
316	InvalidateLayout();
317}
318
319
320void
321BTwoDimensionalLayout::GetInsets(float* left, float* top, float* right,
322	float* bottom) const
323{
324	if (left)
325		*left = fLeftInset;
326	if (top)
327		*top = fTopInset;
328	if (right)
329		*right = fRightInset;
330	if (bottom)
331		*bottom = fBottomInset;
332}
333
334
335void
336BTwoDimensionalLayout::AlignLayoutWith(BTwoDimensionalLayout* other,
337	orientation orientation)
338{
339	if (!other || other == this)
340		return;
341
342	fLocalLayouter->AlignWith(other->fLocalLayouter, orientation);
343
344	InvalidateLayout();
345}
346
347
348BSize
349BTwoDimensionalLayout::BaseMinSize()
350{
351	_ValidateMinMax();
352	return AddInsets(fLocalLayouter->MinSize());
353}
354
355
356BSize
357BTwoDimensionalLayout::BaseMaxSize()
358{
359	_ValidateMinMax();
360	return AddInsets(fLocalLayouter->MaxSize());
361}
362
363
364BSize
365BTwoDimensionalLayout::BasePreferredSize()
366{
367	_ValidateMinMax();
368	return AddInsets(fLocalLayouter->PreferredSize());
369}
370
371
372BAlignment
373BTwoDimensionalLayout::BaseAlignment()
374{
375	return BAbstractLayout::BaseAlignment();
376}
377
378
379bool
380BTwoDimensionalLayout::HasHeightForWidth()
381{
382	_ValidateMinMax();
383	return fLocalLayouter->HasHeightForWidth();
384}
385
386
387void
388BTwoDimensionalLayout::GetHeightForWidth(float width, float* min, float* max,
389	float* preferred)
390{
391	if (!HasHeightForWidth())
392		return;
393
394	float outerSpacing = fLeftInset + fRightInset - 1;
395	fLocalLayouter->InternalGetHeightForWidth(BLayoutUtils::SubtractDistances(
396		width, outerSpacing), min, max, preferred);
397	AddInsets(min, max, preferred);
398}
399
400
401void
402BTwoDimensionalLayout::SetFrame(BRect frame)
403{
404	BAbstractLayout::SetFrame(frame);
405}
406
407
408status_t
409BTwoDimensionalLayout::Archive(BMessage* into, bool deep) const
410{
411	BArchiver archiver(into);
412	status_t err = BAbstractLayout::Archive(into, deep);
413
414	if (err == B_OK) {
415		BRect insets(fLeftInset, fTopInset, fRightInset, fBottomInset);
416		err = into->AddRect(kInsetsField, insets);
417	}
418
419	if (err == B_OK)
420		err = into->AddFloat(kSpacingField, fHSpacing);
421
422	if (err == B_OK)
423		err = into->AddFloat(kSpacingField, fVSpacing);
424
425	return archiver.Finish(err);
426}
427
428
429status_t
430BTwoDimensionalLayout::AllArchived(BMessage* into) const
431{
432	BArchiver archiver(into);
433
434	status_t err = BLayout::AllArchived(into);
435	if (err == B_OK)
436		err = fLocalLayouter->AddAlignedLayoutsToArchive(&archiver);
437	return err;
438}
439
440
441status_t
442BTwoDimensionalLayout::AllUnarchived(const BMessage* from)
443{
444	status_t err = BLayout::AllUnarchived(from);
445	if (err != B_OK)
446		return err;
447
448	BUnarchiver unarchiver(from);
449	err = fLocalLayouter->AlignLayoutsFromArchive(&unarchiver, B_HORIZONTAL);
450	if (err == B_OK)
451		err = fLocalLayouter->AlignLayoutsFromArchive(&unarchiver, B_VERTICAL);
452
453	return err;
454}
455
456
457status_t
458BTwoDimensionalLayout::ItemArchived(BMessage* into, BLayoutItem* item,
459	int32 index) const
460{
461	return BAbstractLayout::ItemArchived(into, item, index);
462}
463
464
465status_t
466BTwoDimensionalLayout::ItemUnarchived(const BMessage* from, BLayoutItem* item,
467	int32 index)
468{
469	return BAbstractLayout::ItemUnarchived(from, item, index);
470}
471
472
473
474
475void
476BTwoDimensionalLayout::LayoutInvalidated(bool children)
477{
478	fLocalLayouter->InvalidateLayout();
479}
480
481
482void
483BTwoDimensionalLayout::DoLayout()
484{
485	_ValidateMinMax();
486
487	// layout the horizontal/vertical elements
488	BSize size(SubtractInsets(LayoutArea().Size()));
489
490#ifdef DEBUG_LAYOUT
491printf("BTwoDimensionalLayout::DerivedLayoutItems(): view: %p"
492	" size: (%.1f, %.1f)\n", View(), size.Width(), size.Height());
493#endif
494
495	fLocalLayouter->Layout(size);
496
497	// layout the items
498	BPoint itemOffset(LayoutArea().LeftTop());
499	int itemCount = CountItems();
500	for (int i = 0; i < itemCount; i++) {
501		BLayoutItem* item = ItemAt(i);
502		if (item->IsVisible()) {
503			Dimensions itemDimensions;
504			GetItemDimensions(item, &itemDimensions);
505			BRect frame = fLocalLayouter->ItemFrame(itemDimensions);
506			frame.left += fLeftInset;
507			frame.top += fTopInset;
508			frame.right += fLeftInset;
509			frame.bottom += fTopInset;
510			frame.OffsetBy(itemOffset);
511{
512#ifdef DEBUG_LAYOUT
513printf("  frame for item %2d (view: %p): ", i, item->View());
514frame.PrintToStream();
515#endif
516//BSize min(item->MinSize());
517//BSize max(item->MaxSize());
518//printf("    min: (%.1f, %.1f), max: (%.1f, %.1f)\n", min.width, min.height,
519//	max.width, max.height);
520//if (item->HasHeightForWidth()) {
521//float minHeight, maxHeight, preferredHeight;
522//item->GetHeightForWidth(frame.Width(), &minHeight, &maxHeight,
523//	&preferredHeight);
524//printf("    hfw: min: %.1f, max: %.1f, pref: %.1f\n", minHeight, maxHeight,
525//	preferredHeight);
526//}
527}
528
529			item->AlignInFrame(frame);
530		}
531//else
532//printf("  item %2d not visible", i);
533	}
534}
535
536
537BSize
538BTwoDimensionalLayout::AddInsets(BSize size)
539{
540	size.width = BLayoutUtils::AddDistances(size.width,
541		fLeftInset + fRightInset - 1);
542	size.height = BLayoutUtils::AddDistances(size.height,
543		fTopInset + fBottomInset - 1);
544	return size;
545}
546
547
548void
549BTwoDimensionalLayout::AddInsets(float* minHeight, float* maxHeight,
550	float* preferredHeight)
551{
552	float insets = fTopInset + fBottomInset - 1;
553	if (minHeight)
554		*minHeight = BLayoutUtils::AddDistances(*minHeight, insets);
555	if (maxHeight)
556		*maxHeight = BLayoutUtils::AddDistances(*maxHeight, insets);
557	if (preferredHeight)
558		*preferredHeight = BLayoutUtils::AddDistances(*preferredHeight, insets);
559}
560
561
562BSize
563BTwoDimensionalLayout::SubtractInsets(BSize size)
564{
565	size.width = BLayoutUtils::SubtractDistances(size.width,
566		fLeftInset + fRightInset - 1);
567	size.height = BLayoutUtils::SubtractDistances(size.height,
568		fTopInset + fBottomInset - 1);
569	return size;
570}
571
572
573void
574BTwoDimensionalLayout::PrepareItems(orientation orientation)
575{
576}
577
578
579bool
580BTwoDimensionalLayout::HasMultiColumnItems()
581{
582	return false;
583}
584
585
586bool
587BTwoDimensionalLayout::HasMultiRowItems()
588{
589	return false;
590}
591
592
593void
594BTwoDimensionalLayout::_ValidateMinMax()
595{
596	fLocalLayouter->ValidateMinMax();
597}
598
599
600// #pragma mark - CompoundLayouter
601
602
603BTwoDimensionalLayout::CompoundLayouter::CompoundLayouter(
604	orientation orientation)
605	:
606	fLayouter(NULL),
607	fLayoutInfo(NULL),
608	fOrientation(orientation),
609	fLocalLayouters(10),
610	fLayoutContext(NULL),
611	fLastLayoutSize(-1)
612{
613}
614
615
616BTwoDimensionalLayout::CompoundLayouter::~CompoundLayouter()
617{
618	delete fLayouter;
619	delete fLayoutInfo;
620}
621
622
623orientation
624BTwoDimensionalLayout::CompoundLayouter::Orientation()
625{
626	return fOrientation;
627}
628
629
630Layouter*
631BTwoDimensionalLayout::CompoundLayouter::GetLayouter(bool minMax)
632{
633	return fLayouter;
634}
635
636
637LayoutInfo*
638BTwoDimensionalLayout::CompoundLayouter::GetLayoutInfo()
639{
640	return fLayoutInfo;
641}
642
643
644void
645BTwoDimensionalLayout::CompoundLayouter::AddLocalLayouter(
646	LocalLayouter* localLayouter)
647{
648	if (localLayouter) {
649		if (!fLocalLayouters.HasItem(localLayouter)) {
650			fLocalLayouters.AddItem(localLayouter);
651			InvalidateLayout();
652		}
653	}
654}
655
656
657void
658BTwoDimensionalLayout::CompoundLayouter::RemoveLocalLayouter(
659	LocalLayouter* localLayouter)
660{
661	if (fLocalLayouters.RemoveItem(localLayouter))
662		InvalidateLayout();
663}
664
665
666status_t
667BTwoDimensionalLayout::CompoundLayouter::AddAlignedLayoutsToArchive(
668	BArchiver* archiver, LocalLayouter* requestedBy)
669{
670	// The LocalLayouter* that really owns us is at index 0, layouts
671	// at other indices are aligned to this one.
672	if (requestedBy != fLocalLayouters.ItemAt(0))
673		return B_OK;
674
675	status_t err;
676	for (int32 i = fLocalLayouters.CountItems() - 1; i > 0; i--) {
677		LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i);
678
679		bool wasAvailable;
680		err = layouter->AddOwnerToArchive(archiver, this, wasAvailable);
681		if (err != B_OK && wasAvailable)
682			return err;
683	}
684	return B_OK;
685}
686
687
688void
689BTwoDimensionalLayout::CompoundLayouter::AbsorbCompoundLayouter(
690	CompoundLayouter* other)
691{
692	if (other == this)
693		return;
694
695	int32 count = other->fLocalLayouters.CountItems();
696	for (int32 i = count - 1; i >= 0; i--) {
697		LocalLayouter* layouter
698			= (LocalLayouter*)other->fLocalLayouters.ItemAt(i);
699		AddLocalLayouter(layouter);
700		layouter->SetCompoundLayouter(this, fOrientation);
701	}
702
703	InvalidateLayout();
704}
705
706
707void
708BTwoDimensionalLayout::CompoundLayouter::InvalidateLayout()
709{
710	if (!fLayouter)
711		return;
712
713	delete fLayouter;
714	delete fLayoutInfo;
715
716	fLayouter = NULL;
717	fLayoutInfo = NULL;
718	fLayoutContext = NULL;
719
720	// notify all local layouters to invalidate the respective views
721	int32 count = fLocalLayouters.CountItems();
722	for (int32 i = 0; i < count; i++) {
723		LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i);
724		layouter->InternalInvalidateLayout(this);
725	}
726}
727
728
729bool
730BTwoDimensionalLayout::CompoundLayouter::IsMinMaxValid()
731{
732	return (fLayouter != NULL);
733}
734
735
736void
737BTwoDimensionalLayout::CompoundLayouter::ValidateMinMax()
738{
739	if (IsMinMaxValid())
740		return;
741
742	fLastLayoutSize = -1;
743
744	// create the layouter
745	_PrepareItems();
746
747	int elementCount = _CountElements();
748
749	fLayouter = new CollapsingLayouter(elementCount, _Spacing());
750
751	// tell the layouter about our constraints
752	// TODO: We should probably ignore local layouters whose view is hidden.
753	// It's a bit tricky to find out, whether the view is hidden, though, since
754	// this doesn't necessarily mean only hidden relative to the parent, but
755	// hidden relative to a common parent.
756	_AddConstraints(fLayouter);
757
758	fLayoutInfo = fLayouter->CreateLayoutInfo();
759}
760
761
762void
763BTwoDimensionalLayout::CompoundLayouter::Layout(float size,
764	LocalLayouter* localLayouter, BLayoutContext* context)
765{
766	ValidateMinMax();
767
768	if (context != fLayoutContext || fLastLayoutSize != size) {
769		DoLayout(size, localLayouter, context);
770		fLayoutContext = context;
771		fLastLayoutSize = size;
772	}
773}
774
775
776void
777BTwoDimensionalLayout::CompoundLayouter::DoLayout(float size,
778	LocalLayouter* localLayouter, BLayoutContext* context)
779{
780	fLayouter->Layout(fLayoutInfo, size);
781}
782
783
784void
785BTwoDimensionalLayout::CompoundLayouter::_PrepareItems()
786{
787	int32 count = fLocalLayouters.CountItems();
788	for (int32 i = 0; i < count; i++) {
789		LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i);
790		layouter->PrepareItems(this);
791	}
792}
793
794
795int32
796BTwoDimensionalLayout::CompoundLayouter::_CountElements()
797{
798	int32 elementCount = 0;
799	int32 count = fLocalLayouters.CountItems();
800	for (int32 i = 0; i < count; i++) {
801		LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i);
802		int32 layouterCount = layouter->CountElements(this);
803		elementCount = max_c(elementCount, layouterCount);
804	}
805
806	return elementCount;
807}
808
809
810bool
811BTwoDimensionalLayout::CompoundLayouter::_HasMultiElementItems()
812{
813	int32 count = fLocalLayouters.CountItems();
814	for (int32 i = 0; i < count; i++) {
815		LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i);
816		if (layouter->HasMultiElementItems(this))
817			return true;
818	}
819
820	return false;
821}
822
823
824void
825BTwoDimensionalLayout::CompoundLayouter::_AddConstraints(Layouter* layouter)
826{
827	int32 count = fLocalLayouters.CountItems();
828	for (int32 i = 0; i < count; i++) {
829		LocalLayouter* localLayouter = (LocalLayouter*)fLocalLayouters.ItemAt(i);
830		localLayouter->AddConstraints(this, layouter);
831	}
832}
833
834
835float
836BTwoDimensionalLayout::CompoundLayouter::_Spacing()
837{
838	if (!fLocalLayouters.IsEmpty())
839		return ((LocalLayouter*)fLocalLayouters.ItemAt(0))->Spacing(this);
840	return 0;
841}
842
843
844// #pragma mark - VerticalCompoundLayouter
845
846
847BTwoDimensionalLayout::VerticalCompoundLayouter::VerticalCompoundLayouter()
848	:
849	CompoundLayouter(B_VERTICAL),
850	fHeightForWidthLayouter(NULL),
851	fCachedMinHeightForWidth(0),
852	fCachedMaxHeightForWidth(0),
853	fCachedPreferredHeightForWidth(0),
854	fHeightForWidthLayoutContext(NULL)
855{
856}
857
858
859Layouter*
860BTwoDimensionalLayout::VerticalCompoundLayouter::GetLayouter(bool minMax)
861{
862	return (minMax || !_HasHeightForWidth()
863		? fLayouter : fHeightForWidthLayouter);
864}
865
866
867void
868BTwoDimensionalLayout::VerticalCompoundLayouter::InvalidateLayout()
869{
870	CompoundLayouter::InvalidateLayout();
871
872	InvalidateHeightForWidth();
873}
874
875
876void
877BTwoDimensionalLayout::VerticalCompoundLayouter::InvalidateHeightForWidth()
878{
879	if (fHeightForWidthLayouter != NULL) {
880		delete fHeightForWidthLayouter;
881		fHeightForWidthLayouter = NULL;
882
883		// also make sure we're not reusing the old layout info
884		fLastLayoutSize = -1;
885
886		int32 count = fLocalLayouters.CountItems();
887		for (int32 i = 0; i < count; i++) {
888			LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i);
889			layouter->SetHeightForWidthConstraintsAdded(false);
890		}
891	}
892}
893
894
895void
896BTwoDimensionalLayout::VerticalCompoundLayouter::InternalGetHeightForWidth(
897	LocalLayouter* localLayouter, BLayoutContext* context, bool realLayout,
898	float* minHeight, float* maxHeight, float* preferredHeight)
899{
900	bool updateCachedInfo = false;
901
902	if (_SetHeightForWidthLayoutContext(context)
903		|| fHeightForWidthLayouter == NULL) {
904		// Either the layout context changed or we haven't initialized the
905		// height for width layouter yet. We create it and init it now.
906
907		// clone the vertical layouter
908		delete fHeightForWidthLayouter;
909		delete fLayoutInfo;
910		fHeightForWidthLayouter = fLayouter->CloneLayouter();
911		fLayoutInfo = fHeightForWidthLayouter->CreateLayoutInfo();
912
913		// add the children's height for width constraints
914		int32 count = fLocalLayouters.CountItems();
915		for (int32 i = 0; i < count; i++) {
916			LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i);
917			if (layouter->HasHeightForWidth()) {
918				layouter->AddHeightForWidthConstraints(this,
919					fHeightForWidthLayouter, context);
920			}
921		}
922
923		updateCachedInfo = true;
924	} else if (localLayouter->HasHeightForWidth()) {
925		// There is a height for width layouter and it has been initialized
926		// in the current layout context. So we just add the height for width
927		// constraints of the calling local layouter, if they haven't been
928		// added yet.
929		updateCachedInfo = localLayouter->AddHeightForWidthConstraints(this,
930			fHeightForWidthLayouter, context);
931	}
932
933	// update cached height for width info, if something changed
934	if (updateCachedInfo) {
935		// get the height for width info
936		fCachedMinHeightForWidth = fHeightForWidthLayouter->MinSize();
937		fCachedMaxHeightForWidth = fHeightForWidthLayouter->MaxSize();
938		fCachedPreferredHeightForWidth
939			= fHeightForWidthLayouter->PreferredSize();
940	}
941
942	if (minHeight)
943		*minHeight = fCachedMinHeightForWidth;
944	if (maxHeight)
945		*maxHeight = fCachedMaxHeightForWidth;
946	if (preferredHeight)
947		*preferredHeight = fCachedPreferredHeightForWidth;
948}
949
950
951void
952BTwoDimensionalLayout::VerticalCompoundLayouter::DoLayout(float size,
953	LocalLayouter* localLayouter, BLayoutContext* context)
954{
955	Layouter* layouter;
956	if (_HasHeightForWidth()) {
957		float minHeight, maxHeight, preferredHeight;
958		InternalGetHeightForWidth(localLayouter, context, true, &minHeight,
959			&maxHeight, &preferredHeight);
960		size = max_c(size, minHeight);
961		layouter = fHeightForWidthLayouter;
962	} else
963		layouter = fLayouter;
964
965	layouter->Layout(fLayoutInfo, size);
966}
967
968
969bool
970BTwoDimensionalLayout::VerticalCompoundLayouter::_HasHeightForWidth()
971{
972	int32 count = fLocalLayouters.CountItems();
973	for (int32 i = 0; i < count; i++) {
974		LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i);
975		if (layouter->HasHeightForWidth())
976			return true;
977	}
978
979	return false;
980}
981
982
983bool
984BTwoDimensionalLayout::VerticalCompoundLayouter
985	::_SetHeightForWidthLayoutContext(BLayoutContext* context)
986{
987	if (context == fHeightForWidthLayoutContext)
988		return false;
989
990	if (fHeightForWidthLayoutContext != NULL) {
991		fHeightForWidthLayoutContext->RemoveListener(this);
992		fHeightForWidthLayoutContext = NULL;
993	}
994
995	// We can ignore the whole context business, if we have no more than one
996	// local layouter. We use the layout context only to recognize when calls
997	// of different local layouters belong to the same context.
998	if (fLocalLayouters.CountItems() <= 1)
999		return false;
1000
1001	fHeightForWidthLayoutContext = context;
1002
1003	if (fHeightForWidthLayoutContext != NULL)
1004		fHeightForWidthLayoutContext->AddListener(this);
1005
1006	InvalidateHeightForWidth();
1007
1008	return true;
1009}
1010
1011
1012void
1013BTwoDimensionalLayout::VerticalCompoundLayouter::LayoutContextLeft(
1014	BLayoutContext* context)
1015{
1016	fHeightForWidthLayoutContext = NULL;
1017}
1018
1019
1020// #pragma mark - LocalLayouter
1021
1022
1023BTwoDimensionalLayout::LocalLayouter::LocalLayouter(
1024		BTwoDimensionalLayout* layout)
1025	:
1026	fLayout(layout),
1027	fHLayouter(new CompoundLayouter(B_HORIZONTAL)),
1028	fVLayouter(new VerticalCompoundLayouter),
1029	fHeightForWidthItems(),
1030	fHorizontalLayoutContext(NULL),
1031	fHorizontalLayoutWidth(0),
1032	fHeightForWidthConstraintsAdded(false)
1033{
1034	fHLayouter->AddLocalLayouter(this);
1035	fVLayouter->AddLocalLayouter(this);
1036}
1037
1038
1039BTwoDimensionalLayout::LocalLayouter::~LocalLayouter()
1040{
1041	if (fHLayouter != NULL) {
1042		fHLayouter->RemoveLocalLayouter(this);
1043		fHLayouter->ReleaseReference();
1044	}
1045
1046	if (fVLayouter != NULL) {
1047		fVLayouter->RemoveLocalLayouter(this);
1048		fVLayouter->ReleaseReference();
1049	}
1050}
1051
1052
1053BSize
1054BTwoDimensionalLayout::LocalLayouter::MinSize()
1055{
1056	return BSize(fHLayouter->GetLayouter(true)->MinSize(),
1057		fVLayouter->GetLayouter(true)->MinSize());
1058}
1059
1060
1061BSize
1062BTwoDimensionalLayout::LocalLayouter::MaxSize()
1063{
1064	return BSize(fHLayouter->GetLayouter(true)->MaxSize(),
1065		fVLayouter->GetLayouter(true)->MaxSize());
1066}
1067
1068
1069BSize
1070BTwoDimensionalLayout::LocalLayouter::PreferredSize()
1071{
1072	return BSize(fHLayouter->GetLayouter(true)->PreferredSize(),
1073		fVLayouter->GetLayouter(true)->PreferredSize());
1074}
1075
1076
1077void
1078BTwoDimensionalLayout::LocalLayouter::InvalidateLayout()
1079{
1080	fHLayouter->InvalidateLayout();
1081	fVLayouter->InvalidateLayout();
1082}
1083
1084
1085void
1086BTwoDimensionalLayout::LocalLayouter::Layout(BSize size)
1087{
1088	DoHorizontalLayout(size.width);
1089	fVLayouter->Layout(size.height, this, fLayout->LayoutContext());
1090}
1091
1092
1093BRect
1094BTwoDimensionalLayout::LocalLayouter::ItemFrame(Dimensions itemDimensions)
1095{
1096	LayoutInfo* hLayoutInfo = fHLayouter->GetLayoutInfo();
1097	LayoutInfo* vLayoutInfo = fVLayouter->GetLayoutInfo();
1098	float x = hLayoutInfo->ElementLocation(itemDimensions.x);
1099	float y = vLayoutInfo->ElementLocation(itemDimensions.y);
1100	float width = hLayoutInfo->ElementRangeSize(itemDimensions.x,
1101		itemDimensions.width);
1102	float height = vLayoutInfo->ElementRangeSize(itemDimensions.y,
1103		itemDimensions.height);
1104	return BRect(x, y, x + width, y + height);
1105}
1106
1107
1108void
1109BTwoDimensionalLayout::LocalLayouter::ValidateMinMax()
1110{
1111	if (fHLayouter->IsMinMaxValid() && fVLayouter->IsMinMaxValid())
1112		return;
1113
1114	if (!fHLayouter->IsMinMaxValid())
1115		fHeightForWidthItems.MakeEmpty();
1116
1117	_SetHorizontalLayoutContext(NULL, -1);
1118
1119	fHLayouter->ValidateMinMax();
1120	fVLayouter->ValidateMinMax();
1121	fLayout->ResetLayoutInvalidation();
1122}
1123
1124
1125void
1126BTwoDimensionalLayout::LocalLayouter::DoHorizontalLayout(float width)
1127{
1128	BLayoutContext* context = fLayout->LayoutContext();
1129	if (fHorizontalLayoutContext != context
1130			|| width != fHorizontalLayoutWidth) {
1131		_SetHorizontalLayoutContext(context, width);
1132		fHLayouter->Layout(width, this, context);
1133		fVLayouter->InvalidateHeightForWidth();
1134	}
1135}
1136
1137
1138void
1139BTwoDimensionalLayout::LocalLayouter::InternalGetHeightForWidth(float width,
1140	float* minHeight, float* maxHeight, float* preferredHeight)
1141{
1142	DoHorizontalLayout(width);
1143	fVLayouter->InternalGetHeightForWidth(this, fHorizontalLayoutContext, false,
1144		minHeight, maxHeight, preferredHeight);
1145}
1146
1147
1148void
1149BTwoDimensionalLayout::LocalLayouter::AlignWith(LocalLayouter* other,
1150	orientation orientation)
1151{
1152	if (orientation == B_HORIZONTAL)
1153		other->fHLayouter->AbsorbCompoundLayouter(fHLayouter);
1154	else
1155		other->fVLayouter->AbsorbCompoundLayouter(fVLayouter);
1156}
1157
1158
1159status_t
1160BTwoDimensionalLayout::LocalLayouter::AddAlignedLayoutsToArchive(
1161	BArchiver* archiver)
1162{
1163	status_t err = fHLayouter->AddAlignedLayoutsToArchive(archiver, this);
1164
1165	if (err == B_OK)
1166		err = fVLayouter->AddAlignedLayoutsToArchive(archiver, this);
1167
1168	return err;
1169}
1170
1171
1172status_t
1173BTwoDimensionalLayout::LocalLayouter::AddOwnerToArchive(BArchiver* archiver,
1174	CompoundLayouter* requestedBy, bool& _wasAvailable)
1175{
1176	const char* field = kHAlignedLayoutField;
1177	if (requestedBy == fVLayouter)
1178		field = kVAlignedLayoutField;
1179
1180	if ((_wasAvailable = archiver->IsArchived(fLayout)))
1181		return archiver->AddArchivable(field, fLayout);
1182
1183	return B_NAME_NOT_FOUND;
1184}
1185
1186
1187status_t
1188BTwoDimensionalLayout::LocalLayouter::AlignLayoutsFromArchive(
1189	BUnarchiver* unarchiver, orientation posture)
1190{
1191	const char* field = kHAlignedLayoutField;
1192	if (posture == B_VERTICAL)
1193		field = kVAlignedLayoutField;
1194
1195	int32 count;
1196	status_t err = unarchiver->ArchiveMessage()->GetInfo(field, NULL, &count);
1197	if (err == B_NAME_NOT_FOUND)
1198		return B_OK;
1199
1200	BTwoDimensionalLayout* retriever;
1201	for (int32 i = 0; i < count && err == B_OK; i++) {
1202		err = unarchiver->FindObject(field, i,
1203			BUnarchiver::B_DONT_ASSUME_OWNERSHIP, retriever);
1204
1205		if (err == B_OK)
1206			retriever->AlignLayoutWith(fLayout, posture);
1207	}
1208
1209	return err;
1210}
1211
1212
1213void
1214BTwoDimensionalLayout::LocalLayouter::PrepareItems(
1215	CompoundLayouter* compoundLayouter)
1216{
1217	fLayout->PrepareItems(compoundLayouter->Orientation());
1218}
1219
1220
1221int32
1222BTwoDimensionalLayout::LocalLayouter::CountElements(
1223	CompoundLayouter* compoundLayouter)
1224{
1225	if (compoundLayouter->Orientation() == B_HORIZONTAL)
1226		return fLayout->InternalCountColumns();
1227	else
1228		return fLayout->InternalCountRows();
1229}
1230
1231
1232bool
1233BTwoDimensionalLayout::LocalLayouter::HasMultiElementItems(
1234	CompoundLayouter* compoundLayouter)
1235{
1236	if (compoundLayouter->Orientation() == B_HORIZONTAL)
1237		return fLayout->HasMultiColumnItems();
1238	else
1239		return fLayout->HasMultiRowItems();
1240}
1241
1242
1243void
1244BTwoDimensionalLayout::LocalLayouter::AddConstraints(
1245	CompoundLayouter* compoundLayouter, Layouter* layouter)
1246{
1247	enum orientation orientation = compoundLayouter->Orientation();
1248	int itemCount = fLayout->CountItems();
1249	if (itemCount > 0) {
1250		for (int i = 0; i < itemCount; i++) {
1251			BLayoutItem* item = fLayout->ItemAt(i);
1252			if (item->IsVisible()) {
1253				Dimensions itemDimensions;
1254				fLayout->GetItemDimensions(item, &itemDimensions);
1255
1256				BSize min = item->MinSize();
1257				BSize max = item->MaxSize();
1258				BSize preferred = item->PreferredSize();
1259
1260				if (orientation == B_HORIZONTAL) {
1261					layouter->AddConstraints(
1262						itemDimensions.x,
1263						itemDimensions.width,
1264						min.width,
1265						max.width,
1266						preferred.width);
1267
1268					if (item->HasHeightForWidth())
1269						fHeightForWidthItems.AddItem(item);
1270
1271				} else {
1272					layouter->AddConstraints(
1273						itemDimensions.y,
1274						itemDimensions.height,
1275						min.height,
1276						max.height,
1277						preferred.height);
1278				}
1279			}
1280		}
1281
1282		// add column/row constraints
1283		ColumnRowConstraints constraints;
1284		int elementCount = CountElements(compoundLayouter);
1285		for (int element = 0; element < elementCount; element++) {
1286			fLayout->GetColumnRowConstraints(orientation, element,
1287				&constraints);
1288			layouter->SetWeight(element, constraints.weight);
1289			layouter->AddConstraints(element, 1, constraints.min,
1290				constraints.max, constraints.min);
1291		}
1292	}
1293}
1294
1295
1296float
1297BTwoDimensionalLayout::LocalLayouter::Spacing(
1298	CompoundLayouter* compoundLayouter)
1299{
1300	return (compoundLayouter->Orientation() == B_HORIZONTAL
1301		? fLayout->fHSpacing : fLayout->fVSpacing);
1302}
1303
1304
1305bool
1306BTwoDimensionalLayout::LocalLayouter::HasHeightForWidth()
1307{
1308	return !fHeightForWidthItems.IsEmpty();
1309}
1310
1311
1312bool
1313BTwoDimensionalLayout::LocalLayouter::AddHeightForWidthConstraints(
1314	VerticalCompoundLayouter* compoundLayouter, Layouter* layouter,
1315	BLayoutContext* context)
1316{
1317	if (context != fHorizontalLayoutContext)
1318		return false;
1319
1320	if (fHeightForWidthConstraintsAdded)
1321		return false;
1322
1323	LayoutInfo* hLayoutInfo = fHLayouter->GetLayoutInfo();
1324
1325	// add the children's height for width constraints
1326	int32 itemCount = fHeightForWidthItems.CountItems();
1327	for (int32 i = 0; i < itemCount; i++) {
1328		BLayoutItem* item = (BLayoutItem*)fHeightForWidthItems.ItemAt(i);
1329		Dimensions itemDimensions;
1330		fLayout->GetItemDimensions(item, &itemDimensions);
1331
1332		float minHeight, maxHeight, preferredHeight;
1333		item->GetHeightForWidth(
1334			hLayoutInfo->ElementRangeSize(itemDimensions.x,
1335				itemDimensions.width),
1336			&minHeight, &maxHeight, &preferredHeight);
1337		layouter->AddConstraints(
1338			itemDimensions.y,
1339			itemDimensions.height,
1340			minHeight,
1341			maxHeight,
1342			preferredHeight);
1343	}
1344
1345	SetHeightForWidthConstraintsAdded(true);
1346
1347	return true;
1348}
1349
1350
1351void
1352BTwoDimensionalLayout::LocalLayouter::SetHeightForWidthConstraintsAdded(
1353	bool added)
1354{
1355	fHeightForWidthConstraintsAdded = added;
1356}
1357
1358
1359void
1360BTwoDimensionalLayout::LocalLayouter::SetCompoundLayouter(
1361	CompoundLayouter* compoundLayouter, orientation orientation)
1362{
1363	CompoundLayouter* oldCompoundLayouter;
1364	if (orientation == B_HORIZONTAL) {
1365		oldCompoundLayouter = fHLayouter;
1366		fHLayouter = compoundLayouter;
1367	} else {
1368		oldCompoundLayouter = fVLayouter;
1369		fVLayouter = static_cast<VerticalCompoundLayouter*>(compoundLayouter);
1370	}
1371
1372	if (compoundLayouter == oldCompoundLayouter)
1373		return;
1374
1375	if (oldCompoundLayouter != NULL) {
1376		oldCompoundLayouter->RemoveLocalLayouter(this);
1377		oldCompoundLayouter->ReleaseReference();
1378	}
1379
1380	if (compoundLayouter != NULL)
1381		compoundLayouter->AcquireReference();
1382
1383	InternalInvalidateLayout(compoundLayouter);
1384}
1385
1386
1387void
1388BTwoDimensionalLayout::LocalLayouter::InternalInvalidateLayout(
1389	CompoundLayouter* compoundLayouter)
1390{
1391	_SetHorizontalLayoutContext(NULL, -1);
1392
1393	fLayout->BLayout::InvalidateLayout();
1394}
1395
1396
1397void
1398BTwoDimensionalLayout::LocalLayouter::_SetHorizontalLayoutContext(
1399	BLayoutContext* context, float width)
1400{
1401	if (context != fHorizontalLayoutContext) {
1402		if (fHorizontalLayoutContext != NULL)
1403			fHorizontalLayoutContext->RemoveListener(this);
1404
1405		fHorizontalLayoutContext = context;
1406
1407		if (fHorizontalLayoutContext != NULL)
1408			fHorizontalLayoutContext->AddListener(this);
1409	}
1410
1411	fHorizontalLayoutWidth = width;
1412}
1413
1414
1415void
1416BTwoDimensionalLayout::LocalLayouter::LayoutContextLeft(BLayoutContext* context)
1417{
1418	fHorizontalLayoutContext = NULL;
1419	fHorizontalLayoutWidth = -1;
1420}
1421
1422
1423status_t
1424BTwoDimensionalLayout::Perform(perform_code code, void* _data)
1425{
1426	return BAbstractLayout::Perform(code, _data);
1427}
1428
1429
1430void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout1() {}
1431void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout2() {}
1432void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout3() {}
1433void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout4() {}
1434void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout5() {}
1435void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout6() {}
1436void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout7() {}
1437void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout8() {}
1438void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout9() {}
1439void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout10() {}
1440
1441