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