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