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