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