xref: /haiku/src/kits/interface/TwoDimensionalLayout.cpp (revision 4b3b81da9e459443d75329cfd08bc9a57ad02653)
1 /*
2  * Copyright 2006, Ingo Weinhold <bonefish@cs.tu-berlin.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)
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 
453 // _CurrentLayoutContext
454 BLayoutContext*
455 BTwoDimensionalLayout::_CurrentLayoutContext()
456 {
457 	BView* view = View();
458 	return (view ? view->LayoutContext() : NULL);
459 }
460 
461 
462 // #pragma mark - CompoundLayouter
463 
464 // constructor
465 BTwoDimensionalLayout::CompoundLayouter::CompoundLayouter(
466 	enum orientation orientation)
467 	: fLayouter(NULL),
468 	  fLayoutInfo(NULL),
469 	  fOrientation(orientation),
470 	  fLocalLayouters(10),
471 	  fLayoutContext(NULL),
472 	  fLastLayoutSize(-1)
473 {
474 }
475 
476 // destructor
477 BTwoDimensionalLayout::CompoundLayouter::~CompoundLayouter()
478 {
479 }
480 
481 // Orientation
482 orientation
483 BTwoDimensionalLayout::CompoundLayouter::Orientation()
484 {
485 	return fOrientation;
486 }
487 
488 // GetLayouter
489 Layouter*
490 BTwoDimensionalLayout::CompoundLayouter::GetLayouter(bool minMax)
491 {
492 	return fLayouter;
493 }
494 
495 // GetLayoutInfo
496 LayoutInfo*
497 BTwoDimensionalLayout::CompoundLayouter::GetLayoutInfo()
498 {
499 	return fLayoutInfo;
500 }
501 
502 // AddLocalLayouter
503 void
504 BTwoDimensionalLayout::CompoundLayouter::AddLocalLayouter(
505 	LocalLayouter* localLayouter)
506 {
507 	if (localLayouter) {
508 		if (!fLocalLayouters.HasItem(localLayouter)) {
509 			fLocalLayouters.AddItem(localLayouter);
510 			InvalidateLayout();
511 		}
512 	}
513 }
514 
515 // RemoveLocalLayouter
516 void
517 BTwoDimensionalLayout::CompoundLayouter::RemoveLocalLayouter(
518 	LocalLayouter* localLayouter)
519 {
520 	if (fLocalLayouters.RemoveItem(localLayouter))
521 		InvalidateLayout();
522 }
523 
524 // AbsorbCompoundLayouter
525 void
526 BTwoDimensionalLayout::CompoundLayouter::AbsorbCompoundLayouter(
527 	CompoundLayouter* other)
528 {
529 	if (other == this)
530 		return;
531 
532 	int32 count = other->fLocalLayouters.CountItems();
533 	for (int32 i = 0; i < count; i++) {
534 		LocalLayouter* layouter
535 			= (LocalLayouter*)other->fLocalLayouters.ItemAt(i);
536 		AddLocalLayouter(layouter);
537 		layouter->SetCompoundLayouter(this, fOrientation);
538 	}
539 
540 	InvalidateLayout();
541 }
542 
543 // InvalidateLayout
544 void
545 BTwoDimensionalLayout::CompoundLayouter::InvalidateLayout()
546 {
547 	if (!fLayouter)
548 		return;
549 
550 	delete fLayouter;
551 	delete fLayoutInfo;
552 
553 	fLayouter = NULL;
554 	fLayoutInfo = NULL;
555 	fLayoutContext = NULL;
556 
557 	// notify all local layouters to invalidate the respective views
558 	int32 count = fLocalLayouters.CountItems();
559 	for (int32 i = 0; i < count; i++) {
560 		LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i);
561 		layouter->InternalInvalidateLayout(this);
562 	}
563 }
564 
565 // IsMinMaxValid
566 bool
567 BTwoDimensionalLayout::CompoundLayouter::IsMinMaxValid()
568 {
569 	return (fLayouter != NULL);
570 }
571 
572 // ValidateMinMax
573 void
574 BTwoDimensionalLayout::CompoundLayouter::ValidateMinMax()
575 {
576 	if (IsMinMaxValid())
577 		return;
578 
579 	fLastLayoutSize = -1;
580 
581 	// create the layouter
582 	_PrepareItems();
583 
584 	int elementCount = _CountElements();
585 
586 	if (elementCount <= 1)
587 		fLayouter = new OneElementLayouter();
588 	else if (_HasMultiElementItems())
589 		fLayouter = new ComplexLayouter(elementCount, _Spacing());
590 	else
591 		fLayouter = new SimpleLayouter(elementCount, _Spacing());
592 
593 	// tell the layouter about our constraints
594 	// TODO: We should probably ignore local layouters whose view is hidden. It's a bit tricky to find
595 	// out, whether the view is hidden, though, since this doesn't necessarily mean only hidden
596 	// relative to the parent, but hidden relative to a common parent.
597 	_AddConstraints(fLayouter);
598 
599 	fLayoutInfo = fLayouter->CreateLayoutInfo();
600 }
601 
602 // Layout
603 void
604 BTwoDimensionalLayout::CompoundLayouter::Layout(float size,
605 	LocalLayouter* localLayouter, BLayoutContext* context)
606 {
607 	ValidateMinMax();
608 
609 	if (context != fLayoutContext || fLastLayoutSize != size) {
610 		DoLayout(size, localLayouter, context);
611 		fLayoutContext = context;
612 		fLastLayoutSize = size;
613 	}
614 }
615 
616 // DoLayout
617 void
618 BTwoDimensionalLayout::CompoundLayouter::DoLayout(float size,
619 	LocalLayouter* localLayouter, BLayoutContext* context)
620 {
621 	fLayouter->Layout(fLayoutInfo, size);
622 }
623 
624 // _PrepareItems
625 void
626 BTwoDimensionalLayout::CompoundLayouter::_PrepareItems()
627 {
628 	int32 count = fLocalLayouters.CountItems();
629 	for (int32 i = 0; i < count; i++) {
630 		LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i);
631 		layouter->PrepareItems(this);
632 	}
633 }
634 
635 // _CountElements
636 int32
637 BTwoDimensionalLayout::CompoundLayouter::_CountElements()
638 {
639 	int32 elementCount = 0;
640 	int32 count = fLocalLayouters.CountItems();
641 	for (int32 i = 0; i < count; i++) {
642 		LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i);
643 		int32 layouterCount = layouter->CountElements(this);
644 		elementCount = max_c(elementCount, layouterCount);
645 	}
646 
647 	return elementCount;
648 }
649 
650 // _HasMultiElementItems
651 bool
652 BTwoDimensionalLayout::CompoundLayouter::_HasMultiElementItems()
653 {
654 	int32 count = fLocalLayouters.CountItems();
655 	for (int32 i = 0; i < count; i++) {
656 		LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i);
657 		if (layouter->HasMultiElementItems(this))
658 			return true;
659 	}
660 
661 	return false;
662 }
663 
664 // _AddConstraints
665 void
666 BTwoDimensionalLayout::CompoundLayouter::_AddConstraints(Layouter* layouter)
667 {
668 	int32 count = fLocalLayouters.CountItems();
669 	for (int32 i = 0; i < count; i++) {
670 		LocalLayouter* localLayouter = (LocalLayouter*)fLocalLayouters.ItemAt(i);
671 		localLayouter->AddConstraints(this, layouter);
672 	}
673 }
674 
675 // _Spacing
676 float
677 BTwoDimensionalLayout::CompoundLayouter::_Spacing()
678 {
679 	if (!fLocalLayouters.IsEmpty())
680 		return ((LocalLayouter*)fLocalLayouters.ItemAt(0))->Spacing(this);
681 	return 0;
682 }
683 
684 
685 // #pragma mark - VerticalCompoundLayouter
686 
687 
688 // constructor
689 BTwoDimensionalLayout::VerticalCompoundLayouter::VerticalCompoundLayouter()
690 	: CompoundLayouter(B_VERTICAL),
691 	  fHeightForWidthLayouter(NULL),
692 	  fCachedMinHeightForWidth(0),
693 	  fCachedMaxHeightForWidth(0),
694 	  fCachedPreferredHeightForWidth(0),
695 	  fHeightForWidthLayoutContext(NULL)
696 {
697 }
698 
699 // GetLayouter
700 Layouter*
701 BTwoDimensionalLayout::VerticalCompoundLayouter::GetLayouter(bool minMax)
702 {
703 	return (minMax || !_HasHeightForWidth()
704 		? fLayouter : fHeightForWidthLayouter);
705 }
706 
707 // InvalidateLayout
708 void
709 BTwoDimensionalLayout::VerticalCompoundLayouter::InvalidateLayout()
710 {
711 	CompoundLayouter::InvalidateLayout();
712 
713 	InvalidateHeightForWidth();
714 }
715 
716 // InvalidateHeightForWidth
717 void
718 BTwoDimensionalLayout::VerticalCompoundLayouter::InvalidateHeightForWidth()
719 {
720 	if (fHeightForWidthLayouter != NULL) {
721 		delete fHeightForWidthLayouter;
722 		fHeightForWidthLayouter = NULL;
723 
724 		// also make sure we're not reusing the old layout info
725 		fLastLayoutSize = -1;
726 
727 		int32 count = fLocalLayouters.CountItems();
728 		for (int32 i = 0; i < count; i++) {
729 			LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i);
730 			layouter->SetHeightForWidthConstraintsAdded(false);
731 		}
732 	}
733 }
734 
735 // InternalGetHeightForWidth
736 void
737 BTwoDimensionalLayout::VerticalCompoundLayouter::InternalGetHeightForWidth(
738 	LocalLayouter* localLayouter, BLayoutContext* context, bool realLayout,
739 	float* minHeight, float* maxHeight, float* preferredHeight)
740 {
741 	bool updateCachedInfo = false;
742 
743 	if (_SetHeightForWidthLayoutContext(context)
744 		|| fHeightForWidthLayouter == NULL) {
745 		// Either the layout context changed or we haven't initialized the
746 		// height for width layouter yet. We create it and init it now.
747 
748 		// clone the vertical layouter
749 		delete fHeightForWidthLayouter;
750 		delete fLayoutInfo;
751 		fHeightForWidthLayouter = fLayouter->CloneLayouter();
752 		fLayoutInfo = fHeightForWidthLayouter->CreateLayoutInfo();
753 
754 		// add the children's height for width constraints
755 		int32 count = fLocalLayouters.CountItems();
756 		for (int32 i = 0; i < count; i++) {
757 			LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i);
758 			if (layouter->HasHeightForWidth()) {
759 				layouter->AddHeightForWidthConstraints(this,
760 					fHeightForWidthLayouter, context);
761 			}
762 		}
763 
764 		updateCachedInfo = true;
765 
766 		// get the height for width info
767 		fCachedMinHeightForWidth = fHeightForWidthLayouter->MinSize();
768 		fCachedMaxHeightForWidth = fHeightForWidthLayouter->MaxSize();
769 		fCachedPreferredHeightForWidth
770 			= fHeightForWidthLayouter->PreferredSize();
771 
772 	} else if (localLayouter->HasHeightForWidth()) {
773 		// There is a height for width layouter and it has been initialized
774 		// in the current layout context. So we just add the height for width
775 		// constraints of the calling local layouter, if they haven't been
776 		// added yet.
777 		updateCachedInfo = localLayouter->AddHeightForWidthConstraints(this,
778 			fHeightForWidthLayouter, context);
779 	}
780 
781 	// update cached height for width info, if something changed
782 	if (updateCachedInfo) {
783 		// get the height for width info
784 		fCachedMinHeightForWidth = fHeightForWidthLayouter->MinSize();
785 		fCachedMaxHeightForWidth = fHeightForWidthLayouter->MaxSize();
786 		fCachedPreferredHeightForWidth
787 			= fHeightForWidthLayouter->PreferredSize();
788 	}
789 
790 	if (minHeight)
791 		*minHeight = fCachedMinHeightForWidth;
792 	if (maxHeight)
793 		*maxHeight = fCachedMaxHeightForWidth;
794 	if (preferredHeight)
795 		*preferredHeight = fCachedPreferredHeightForWidth;
796 }
797 
798 // DoLayout
799 void
800 BTwoDimensionalLayout::VerticalCompoundLayouter::DoLayout(float size,
801 	LocalLayouter* localLayouter, BLayoutContext* context)
802 {
803 	Layouter* layouter;
804 	if (_HasHeightForWidth()) {
805 		float minHeight, maxHeight, preferredHeight;
806 		InternalGetHeightForWidth(localLayouter, context, true, &minHeight,
807 			&maxHeight, &preferredHeight);
808 		size = max_c(size, minHeight);
809 		layouter = fHeightForWidthLayouter;
810 	} else
811 		layouter = fLayouter;
812 
813 	layouter->Layout(fLayoutInfo, size);
814 }
815 
816 // _HasHeightForWidth
817 bool
818 BTwoDimensionalLayout::VerticalCompoundLayouter::_HasHeightForWidth()
819 {
820 	int32 count = fLocalLayouters.CountItems();
821 	for (int32 i = 0; i < count; i++) {
822 		LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i);
823 		if (layouter->HasHeightForWidth())
824 			return true;
825 	}
826 
827 	return false;
828 }
829 
830 // _SetHeightForWidthLayoutContext
831 bool
832 BTwoDimensionalLayout::VerticalCompoundLayouter
833 	::_SetHeightForWidthLayoutContext(BLayoutContext* context)
834 {
835 	if (context == fHeightForWidthLayoutContext)
836 		return false;
837 
838 	if (fHeightForWidthLayoutContext != NULL) {
839 		fHeightForWidthLayoutContext->RemoveListener(this);
840 		fHeightForWidthLayoutContext = NULL;
841 	}
842 
843 	// We can ignore the whole context business, if we have no more than one
844 	// local layouter. We use the layout context only to recognize when calls
845 	// of different local layouters belong to the same context.
846 	if (fLocalLayouters.CountItems() <= 1)
847 		return false;
848 
849 	fHeightForWidthLayoutContext = context;
850 
851 	if (fHeightForWidthLayoutContext != NULL)
852 		fHeightForWidthLayoutContext->AddListener(this);
853 
854 	InvalidateHeightForWidth();
855 
856 	return true;
857 }
858 
859 // LayoutContextLeft
860 void
861 BTwoDimensionalLayout::VerticalCompoundLayouter::LayoutContextLeft(
862 	BLayoutContext* context)
863 {
864 	fHeightForWidthLayoutContext = NULL;
865 }
866 
867 
868 // #pragma mark - LocalLayouter
869 
870 
871 // constructor
872 BTwoDimensionalLayout::LocalLayouter::LocalLayouter(
873 		BTwoDimensionalLayout* layout)
874 	: fLayout(layout),
875 	  fHLayouter(new CompoundLayouter(B_HORIZONTAL)),
876 	  fVLayouter(new VerticalCompoundLayouter),
877 	  fHeightForWidthItems(),
878 	  fHorizontalLayoutContext(NULL),
879 	  fHorizontalLayoutWidth(0),
880 	  fHeightForWidthConstraintsAdded(false)
881 {
882 	fHLayouter->AddLocalLayouter(this);
883 	fVLayouter->AddLocalLayouter(this);
884 }
885 
886 // MinSize
887 BSize
888 BTwoDimensionalLayout::LocalLayouter::MinSize()
889 {
890 	return BSize(fHLayouter->GetLayouter(true)->MinSize(),
891 		fVLayouter->GetLayouter(true)->MinSize());
892 }
893 
894 // MaxSize
895 BSize
896 BTwoDimensionalLayout::LocalLayouter::MaxSize()
897 {
898 	return BSize(fHLayouter->GetLayouter(true)->MaxSize(),
899 		fVLayouter->GetLayouter(true)->MaxSize());
900 }
901 
902 // PreferredSize
903 BSize
904 BTwoDimensionalLayout::LocalLayouter::PreferredSize()
905 {
906 	return BSize(fHLayouter->GetLayouter(true)->PreferredSize(),
907 		fVLayouter->GetLayouter(true)->PreferredSize());
908 }
909 
910 // InvalidateLayout
911 void
912 BTwoDimensionalLayout::LocalLayouter::InvalidateLayout()
913 {
914 	fHLayouter->InvalidateLayout();
915 	fVLayouter->InvalidateLayout();
916 }
917 
918 // Layout
919 void
920 BTwoDimensionalLayout::LocalLayouter::Layout(BSize size)
921 {
922 	DoHorizontalLayout(size.width);
923 	fVLayouter->Layout(size.height, this, fLayout->_CurrentLayoutContext());
924 }
925 
926 // ItemFrame
927 BRect
928 BTwoDimensionalLayout::LocalLayouter::ItemFrame(Dimensions itemDimensions)
929 {
930 	LayoutInfo* hLayoutInfo = fHLayouter->GetLayoutInfo();
931 	LayoutInfo* vLayoutInfo = fVLayouter->GetLayoutInfo();
932 	float x = hLayoutInfo->ElementLocation(itemDimensions.x);
933 	float y = vLayoutInfo->ElementLocation(itemDimensions.y);
934 	float width = hLayoutInfo->ElementRangeSize(itemDimensions.x,
935 		itemDimensions.width);
936 	float height = vLayoutInfo->ElementRangeSize(itemDimensions.y,
937 		itemDimensions.height);
938 	return BRect(x, y, x + width, y + height);
939 }
940 
941 // ValidateMinMax
942 void
943 BTwoDimensionalLayout::LocalLayouter::ValidateMinMax()
944 {
945 	if (fHLayouter->IsMinMaxValid() && fVLayouter->IsMinMaxValid())
946 		return;
947 
948 	if (!fHLayouter->IsMinMaxValid())
949 		fHeightForWidthItems.MakeEmpty();
950 
951 	_SetHorizontalLayoutContext(NULL, -1);
952 
953 	fHLayouter->ValidateMinMax();
954 	fVLayouter->ValidateMinMax();
955 }
956 
957 // DoHorizontalLayout
958 void
959 BTwoDimensionalLayout::LocalLayouter::DoHorizontalLayout(float width)
960 {
961 	BLayoutContext* context = fLayout->_CurrentLayoutContext();
962 	if (fHorizontalLayoutContext != context
963 			|| width != fHorizontalLayoutWidth) {
964 		_SetHorizontalLayoutContext(context, width);
965 		fHLayouter->Layout(width, this, context);
966 		fVLayouter->InvalidateHeightForWidth();
967 	}
968 }
969 
970 // InternalGetHeightForWidth
971 void
972 BTwoDimensionalLayout::LocalLayouter::InternalGetHeightForWidth(float width,
973 	float* minHeight, float* maxHeight, float* preferredHeight)
974 {
975 	DoHorizontalLayout(width);
976 	fVLayouter->InternalGetHeightForWidth(this, fHorizontalLayoutContext, false,
977 		minHeight, maxHeight, preferredHeight);
978 }
979 
980 // AlignWith
981 void
982 BTwoDimensionalLayout::LocalLayouter::AlignWith(LocalLayouter* other,
983 	enum orientation orientation)
984 {
985 	if (orientation == B_HORIZONTAL)
986 		other->fHLayouter->AbsorbCompoundLayouter(fHLayouter);
987 	else
988 		other->fVLayouter->AbsorbCompoundLayouter(fVLayouter);
989 }
990 
991 // PrepareItems
992 void
993 BTwoDimensionalLayout::LocalLayouter::PrepareItems(
994 	CompoundLayouter* compoundLayouter)
995 {
996 	fLayout->PrepareItems(compoundLayouter->Orientation());
997 }
998 
999 // CountElements
1000 int32
1001 BTwoDimensionalLayout::LocalLayouter::CountElements(
1002 	CompoundLayouter* compoundLayouter)
1003 {
1004 	if (compoundLayouter->Orientation() == B_HORIZONTAL)
1005 		return fLayout->InternalCountColumns();
1006 	else
1007 		return fLayout->InternalCountRows();
1008 }
1009 
1010 // HasMultiElementItems
1011 bool
1012 BTwoDimensionalLayout::LocalLayouter::HasMultiElementItems(
1013 	CompoundLayouter* compoundLayouter)
1014 {
1015 	if (compoundLayouter->Orientation() == B_HORIZONTAL)
1016 		return fLayout->HasMultiColumnItems();
1017 	else
1018 		return fLayout->HasMultiRowItems();
1019 }
1020 
1021 // AddConstraints
1022 void
1023 BTwoDimensionalLayout::LocalLayouter::AddConstraints(
1024 	CompoundLayouter* compoundLayouter, Layouter* layouter)
1025 {
1026 	enum orientation orientation = compoundLayouter->Orientation();
1027 	int itemCount = fLayout->CountItems();
1028 	if (itemCount > 0) {
1029 		for (int i = 0; i < itemCount; i++) {
1030 			BLayoutItem* item = fLayout->ItemAt(i);
1031 			if (item->IsVisible()) {
1032 				Dimensions itemDimensions;
1033 				fLayout->GetItemDimensions(item, &itemDimensions);
1034 
1035 				BSize min = item->MinSize();
1036 				BSize max = item->MaxSize();
1037 				BSize preferred = item->PreferredSize();
1038 
1039 				if (orientation == B_HORIZONTAL) {
1040 					layouter->AddConstraints(
1041 						itemDimensions.x,
1042 						itemDimensions.width,
1043 						min.width,
1044 						max.width,
1045 						preferred.width);
1046 
1047 					if (item->HasHeightForWidth())
1048 						fHeightForWidthItems.AddItem(item);
1049 
1050 				} else {
1051 					layouter->AddConstraints(
1052 						itemDimensions.y,
1053 						itemDimensions.height,
1054 						min.height,
1055 						max.height,
1056 						preferred.height);
1057 				}
1058 			}
1059 		}
1060 
1061 		// add column/row constraints
1062 		ColumnRowConstraints constraints;
1063 		int elementCount = CountElements(compoundLayouter);
1064 		for (int element = 0; element < elementCount; element++) {
1065 			fLayout->GetColumnRowConstraints(orientation, element,
1066 				&constraints);
1067 			layouter->SetWeight(element, constraints.weight);
1068 			layouter->AddConstraints(element, 1, constraints.min,
1069 				constraints.max, constraints.min);
1070 		}
1071 	}
1072 }
1073 
1074 // Spacing
1075 float
1076 BTwoDimensionalLayout::LocalLayouter::Spacing(
1077 	CompoundLayouter* compoundLayouter)
1078 {
1079 	return (compoundLayouter->Orientation() == B_HORIZONTAL
1080 		? fLayout->fHSpacing : fLayout->fVSpacing);
1081 }
1082 
1083 // HasHeightForWidth
1084 bool
1085 BTwoDimensionalLayout::LocalLayouter::HasHeightForWidth()
1086 {
1087 	return !fHeightForWidthItems.IsEmpty();
1088 }
1089 
1090 // AddHeightForWidthConstraints
1091 bool
1092 BTwoDimensionalLayout::LocalLayouter::AddHeightForWidthConstraints(
1093 	VerticalCompoundLayouter* compoundLayouter, Layouter* layouter,
1094 	BLayoutContext* context)
1095 {
1096 	if (context != fHorizontalLayoutContext)
1097 		return false;
1098 
1099 	if (fHeightForWidthConstraintsAdded)
1100 		return false;
1101 
1102 	LayoutInfo* hLayoutInfo = fHLayouter->GetLayoutInfo();
1103 
1104 	// add the children's height for width constraints
1105 	int32 itemCount = fHeightForWidthItems.CountItems();
1106 	for (int32 i = 0; i < itemCount; i++) {
1107 		BLayoutItem* item = (BLayoutItem*)fHeightForWidthItems.ItemAt(i);
1108 		Dimensions itemDimensions;
1109 		fLayout->GetItemDimensions(item, &itemDimensions);
1110 
1111 		float minHeight, maxHeight, preferredHeight;
1112 		item->GetHeightForWidth(
1113 			hLayoutInfo->ElementRangeSize(itemDimensions.x,
1114 				itemDimensions.width),
1115 			&minHeight, &maxHeight, &preferredHeight);
1116 		layouter->AddConstraints(
1117 			itemDimensions.y,
1118 			itemDimensions.height,
1119 			minHeight,
1120 			maxHeight,
1121 			preferredHeight);
1122 	}
1123 
1124 	SetHeightForWidthConstraintsAdded(true);
1125 
1126 	return true;
1127 }
1128 
1129 // SetHeightForWidthConstraintsAdded
1130 void
1131 BTwoDimensionalLayout::LocalLayouter::SetHeightForWidthConstraintsAdded(
1132 	bool added)
1133 {
1134 	fHeightForWidthConstraintsAdded = added;
1135 }
1136 
1137 // SetCompoundLayouter
1138 void
1139 BTwoDimensionalLayout::LocalLayouter::SetCompoundLayouter(
1140 	CompoundLayouter* compoundLayouter, enum orientation orientation)
1141 {
1142 	if (orientation == B_HORIZONTAL)
1143 		fHLayouter = compoundLayouter;
1144 	else
1145 		fVLayouter = (VerticalCompoundLayouter*)compoundLayouter;
1146 
1147 	InternalInvalidateLayout(compoundLayouter);
1148 }
1149 
1150 // InternalInvalidateLayout
1151 void
1152 BTwoDimensionalLayout::LocalLayouter::InternalInvalidateLayout(
1153 	CompoundLayouter* compoundLayouter)
1154 {
1155 	_SetHorizontalLayoutContext(NULL, -1);
1156 
1157 	fLayout->BLayout::InvalidateLayout();
1158 }
1159 
1160 // _SetHorizontalLayoutContext
1161 void
1162 BTwoDimensionalLayout::LocalLayouter::_SetHorizontalLayoutContext(
1163 	BLayoutContext* context, float width)
1164 {
1165 	if (context != fHorizontalLayoutContext) {
1166 		if (fHorizontalLayoutContext != NULL)
1167 			fHorizontalLayoutContext->RemoveListener(this);
1168 
1169 		fHorizontalLayoutContext = context;
1170 
1171 		if (fHorizontalLayoutContext != NULL)
1172 			fHorizontalLayoutContext->AddListener(this);
1173 	}
1174 
1175 	fHorizontalLayoutWidth = width;
1176 }
1177 
1178 // LayoutContextLeft
1179 void
1180 BTwoDimensionalLayout::LocalLayouter::LayoutContextLeft(BLayoutContext* context)
1181 {
1182 	fHorizontalLayoutContext = NULL;
1183 	fHorizontalLayoutWidth = -1;
1184 }
1185