xref: /haiku/src/kits/interface/SplitLayout.cpp (revision b617a7b410c05275effb95f4b2f5608359d9b7b9)
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 "SplitLayout.h"
8 
9 #include <new>
10 #include <stdio.h>
11 
12 #include <ControlLook.h>
13 #include <LayoutItem.h>
14 #include <LayoutUtils.h>
15 #include <Message.h>
16 #include <View.h>
17 
18 #include "OneElementLayouter.h"
19 #include "SimpleLayouter.h"
20 
21 
22 using std::nothrow;
23 
24 
25 // archivng constants
26 namespace {
27 	const char* const kItemCollapsibleField = "BSplitLayout:item:collapsible";
28 	const char* const kItemWeightField = "BSplitLayout:item:weight";
29 	const char* const kSpacingField = "BSplitLayout:spacing";
30 	const char* const kSplitterSizeField = "BSplitLayout:splitterSize";
31 	const char* const kIsVerticalField = "BSplitLayout:vertical";
32 	const char* const kInsetsField = "BSplitLayout:insets";
33 }
34 
35 
36 class BSplitLayout::ItemLayoutInfo {
37 public:
38 	float		weight;
39 	BRect		layoutFrame;
40 	BSize		min;
41 	BSize		max;
42 	bool		isVisible;
43 	bool		isCollapsible;
44 
45 	ItemLayoutInfo()
46 		:
47 		weight(1.0f),
48 		layoutFrame(0, 0, -1, -1),
49 		min(),
50 		max(),
51 		isVisible(true),
52 		isCollapsible(true)
53 	{
54 	}
55 };
56 
57 
58 class BSplitLayout::ValueRange {
59 public:
60 	int32 sumValue;	// including spacing
61 	int32 previousMin;
62 	int32 previousMax;
63 	int32 previousSize;
64 	int32 nextMin;
65 	int32 nextMax;
66 	int32 nextSize;
67 };
68 
69 
70 class BSplitLayout::SplitterItem : public BLayoutItem {
71 public:
72 	SplitterItem(BSplitLayout* layout)
73 		:
74 		fLayout(layout),
75 		fFrame()
76 	{
77 	}
78 
79 
80 	virtual BSize MinSize()
81 	{
82 		if (fLayout->Orientation() == B_HORIZONTAL)
83 			return BSize(fLayout->SplitterSize() - 1, -1);
84 		else
85 			return BSize(-1, fLayout->SplitterSize() - 1);
86 	}
87 
88 	virtual BSize MaxSize()
89 	{
90 		if (fLayout->Orientation() == B_HORIZONTAL)
91 			return BSize(fLayout->SplitterSize() - 1, B_SIZE_UNLIMITED);
92 		else
93 			return BSize(B_SIZE_UNLIMITED, fLayout->SplitterSize() - 1);
94 	}
95 
96 	virtual BSize PreferredSize()
97 	{
98 		return MinSize();
99 	}
100 
101 	virtual BAlignment Alignment()
102 	{
103 		return BAlignment(B_ALIGN_HORIZONTAL_CENTER, B_ALIGN_VERTICAL_CENTER);
104 	}
105 
106 	virtual void SetExplicitMinSize(BSize size)
107 	{
108 		// not allowed
109 	}
110 
111 	virtual void SetExplicitMaxSize(BSize size)
112 	{
113 		// not allowed
114 	}
115 
116 	virtual void SetExplicitPreferredSize(BSize size)
117 	{
118 		// not allowed
119 	}
120 
121 	virtual void SetExplicitAlignment(BAlignment alignment)
122 	{
123 		// not allowed
124 	}
125 
126 	virtual bool IsVisible()
127 	{
128 		return true;
129 	}
130 
131 	virtual void SetVisible(bool visible)
132 	{
133 		// not allowed
134 	}
135 
136 
137 	virtual BRect Frame()
138 	{
139 		return fFrame;
140 	}
141 
142 	virtual void SetFrame(BRect frame)
143 	{
144 		fFrame = frame;
145 	}
146 
147 private:
148 	BSplitLayout*	fLayout;
149 	BRect			fFrame;
150 };
151 
152 
153 // #pragma mark -
154 
155 
156 BSplitLayout::BSplitLayout(orientation orientation, float spacing)
157 	:
158 	fOrientation(orientation),
159 	fLeftInset(0),
160 	fRightInset(0),
161 	fTopInset(0),
162 	fBottomInset(0),
163 	fSplitterSize(6),
164 	fSpacing(BControlLook::ComposeSpacing(spacing)),
165 
166 	fSplitterItems(),
167 	fVisibleItems(),
168 	fMin(),
169 	fMax(),
170 	fPreferred(),
171 
172 	fHorizontalLayouter(NULL),
173 	fVerticalLayouter(NULL),
174 	fHorizontalLayoutInfo(NULL),
175 	fVerticalLayoutInfo(NULL),
176 
177 	fHeightForWidthItems(),
178 	fHeightForWidthVerticalLayouter(NULL),
179 	fHeightForWidthHorizontalLayoutInfo(NULL),
180 
181 	fLayoutValid(false),
182 
183 	fCachedHeightForWidthWidth(-2),
184 	fHeightForWidthVerticalLayouterWidth(-2),
185 	fCachedMinHeightForWidth(-1),
186 	fCachedMaxHeightForWidth(-1),
187 	fCachedPreferredHeightForWidth(-1),
188 
189 	fDraggingStartPoint(),
190 	fDraggingStartValue(0),
191 	fDraggingCurrentValue(0),
192 	fDraggingSplitterIndex(-1)
193 {
194 }
195 
196 
197 BSplitLayout::BSplitLayout(BMessage* from)
198 	:
199 	BAbstractLayout(BUnarchiver::PrepareArchive(from)),
200 	fOrientation(B_HORIZONTAL),
201 	fLeftInset(0),
202 	fRightInset(0),
203 	fTopInset(0),
204 	fBottomInset(0),
205 	fSplitterSize(6),
206 	fSpacing(be_control_look->DefaultItemSpacing()),
207 
208 	fSplitterItems(),
209 	fVisibleItems(),
210 	fMin(),
211 	fMax(),
212 	fPreferred(),
213 
214 	fHorizontalLayouter(NULL),
215 	fVerticalLayouter(NULL),
216 	fHorizontalLayoutInfo(NULL),
217 	fVerticalLayoutInfo(NULL),
218 
219 	fHeightForWidthItems(),
220 	fHeightForWidthVerticalLayouter(NULL),
221 	fHeightForWidthHorizontalLayoutInfo(NULL),
222 
223 	fLayoutValid(false),
224 
225 	fCachedHeightForWidthWidth(-2),
226 	fHeightForWidthVerticalLayouterWidth(-2),
227 	fCachedMinHeightForWidth(-1),
228 	fCachedMaxHeightForWidth(-1),
229 	fCachedPreferredHeightForWidth(-1),
230 
231 	fDraggingStartPoint(),
232 	fDraggingStartValue(0),
233 	fDraggingCurrentValue(0),
234 	fDraggingSplitterIndex(-1)
235 {
236 	BUnarchiver unarchiver(from);
237 
238 	bool isVertical;
239 	status_t err = from->FindBool(kIsVerticalField, &isVertical);
240 	if (err != B_OK) {
241 		unarchiver.Finish(err);
242 		return;
243 	}
244 	fOrientation = (isVertical) ? B_VERTICAL : B_HORIZONTAL ;
245 
246 	BRect insets;
247 	err = from->FindRect(kInsetsField, &insets);
248 	if (err != B_OK) {
249 		unarchiver.Finish(err);
250 		return;
251 	}
252 	SetInsets(insets.left, insets.top, insets.right, insets.bottom);
253 
254 	err = from->FindFloat(kSplitterSizeField, &fSplitterSize);
255 	if (err == B_OK)
256 		err = from->FindFloat(kSpacingField, &fSpacing);
257 
258 	unarchiver.Finish(err);
259 }
260 
261 
262 BSplitLayout::~BSplitLayout()
263 {
264 }
265 
266 
267 void
268 BSplitLayout::SetInsets(float left, float top, float right, float bottom)
269 {
270 	fLeftInset = left;
271 	fTopInset = top;
272 	fRightInset = right;
273 	fBottomInset = bottom;
274 
275 	InvalidateLayout();
276 }
277 
278 
279 void
280 BSplitLayout::GetInsets(float* left, float* top, float* right,
281 	float* bottom) const
282 {
283 	if (left)
284 		*left = fLeftInset;
285 	if (top)
286 		*top = fTopInset;
287 	if (right)
288 		*right = fRightInset;
289 	if (bottom)
290 		*bottom = fBottomInset;
291 }
292 
293 
294 float
295 BSplitLayout::Spacing() const
296 {
297 	return fSpacing;
298 }
299 
300 
301 void
302 BSplitLayout::SetSpacing(float spacing)
303 {
304 	spacing = BControlLook::ComposeSpacing(spacing);
305 	if (spacing != fSpacing) {
306 		fSpacing = spacing;
307 
308 		InvalidateLayout();
309 	}
310 }
311 
312 
313 orientation
314 BSplitLayout::Orientation() const
315 {
316 	return fOrientation;
317 }
318 
319 
320 void
321 BSplitLayout::SetOrientation(orientation orientation)
322 {
323 	if (orientation != fOrientation) {
324 		fOrientation = orientation;
325 
326 		InvalidateLayout();
327 	}
328 }
329 
330 
331 float
332 BSplitLayout::SplitterSize() const
333 {
334 	return fSplitterSize;
335 }
336 
337 
338 void
339 BSplitLayout::SetSplitterSize(float size)
340 {
341 	if (size != fSplitterSize) {
342 		fSplitterSize = size;
343 
344 		InvalidateLayout();
345 	}
346 }
347 
348 
349 BLayoutItem*
350 BSplitLayout::AddView(BView* child)
351 {
352 	return BAbstractLayout::AddView(child);
353 }
354 
355 
356 BLayoutItem*
357 BSplitLayout::AddView(int32 index, BView* child)
358 {
359 	return BAbstractLayout::AddView(index, child);
360 }
361 
362 
363 BLayoutItem*
364 BSplitLayout::AddView(BView* child, float weight)
365 {
366 	return AddView(-1, child, weight);
367 }
368 
369 
370 BLayoutItem*
371 BSplitLayout::AddView(int32 index, BView* child, float weight)
372 {
373 	BLayoutItem* item = AddView(index, child);
374 	if (item)
375 		SetItemWeight(item, weight);
376 
377 	return item;
378 }
379 
380 
381 bool
382 BSplitLayout::AddItem(BLayoutItem* item)
383 {
384 	return BAbstractLayout::AddItem(item);
385 }
386 
387 
388 bool
389 BSplitLayout::AddItem(int32 index, BLayoutItem* item)
390 {
391 	return BAbstractLayout::AddItem(index, item);
392 }
393 
394 
395 bool
396 BSplitLayout::AddItem(BLayoutItem* item, float weight)
397 {
398 	return AddItem(-1, item, weight);
399 }
400 
401 
402 bool
403 BSplitLayout::AddItem(int32 index, BLayoutItem* item, float weight)
404 {
405 	bool success = AddItem(index, item);
406 	if (success)
407 		SetItemWeight(item, weight);
408 
409 	return success;
410 }
411 
412 
413 float
414 BSplitLayout::ItemWeight(int32 index) const
415 {
416 	if (index < 0 || index >= CountItems())
417 		return 0;
418 
419 	return ItemWeight(ItemAt(index));
420 }
421 
422 
423 float
424 BSplitLayout::ItemWeight(BLayoutItem* item) const
425 {
426 	if (ItemLayoutInfo* info = _ItemLayoutInfo(item))
427 		return info->weight;
428 	return 0;
429 }
430 
431 
432 void
433 BSplitLayout::SetItemWeight(int32 index, float weight, bool invalidateLayout)
434 {
435 	if (index < 0 || index >= CountItems())
436 		return;
437 
438 	BLayoutItem* item = ItemAt(index);
439 	SetItemWeight(item, weight);
440 
441 	if (fHorizontalLayouter) {
442 		int32 visibleIndex = fVisibleItems.IndexOf(item);
443 		if (visibleIndex >= 0) {
444 			if (fOrientation == B_HORIZONTAL)
445 				fHorizontalLayouter->SetWeight(visibleIndex, weight);
446 			else
447 				fVerticalLayouter->SetWeight(visibleIndex, weight);
448 		}
449 	}
450 
451 	if (invalidateLayout)
452 		InvalidateLayout();
453 }
454 
455 
456 void
457 BSplitLayout::SetItemWeight(BLayoutItem* item, float weight)
458 {
459 	if (ItemLayoutInfo* info = _ItemLayoutInfo(item))
460 		info->weight = weight;
461 }
462 
463 
464 bool
465 BSplitLayout::IsCollapsible(int32 index) const
466 {
467 	return _ItemLayoutInfo(ItemAt(index))->isCollapsible;
468 }
469 
470 
471 void
472 BSplitLayout::SetCollapsible(bool collapsible)
473 {
474 	SetCollapsible(0, CountItems() - 1, collapsible);
475 }
476 
477 
478 void
479 BSplitLayout::SetCollapsible(int32 index, bool collapsible)
480 {
481 	SetCollapsible(index, index, collapsible);
482 }
483 
484 
485 void
486 BSplitLayout::SetCollapsible(int32 first, int32 last, bool collapsible)
487 {
488 	for (int32 i = first; i <= last; i++)
489 		_ItemLayoutInfo(ItemAt(i))->isCollapsible = collapsible;
490 }
491 
492 
493 bool
494 BSplitLayout::IsItemCollapsed(int32 index) const
495 {
496 	return _ItemLayoutInfo(ItemAt(index))->isVisible;
497 }
498 
499 
500 void
501 BSplitLayout::SetItemCollapsed(int32 index, bool collapsed)
502 {
503 	ItemAt(index)->SetVisible(collapsed);
504 
505 	InvalidateLayout(true);
506 }
507 
508 
509 BSize
510 BSplitLayout::BaseMinSize()
511 {
512 	_ValidateMinMax();
513 
514 	return _AddInsets(fMin);
515 }
516 
517 
518 BSize
519 BSplitLayout::BaseMaxSize()
520 {
521 	_ValidateMinMax();
522 
523 	return _AddInsets(fMax);
524 }
525 
526 
527 BSize
528 BSplitLayout::BasePreferredSize()
529 {
530 	_ValidateMinMax();
531 
532 	return _AddInsets(fPreferred);
533 }
534 
535 
536 BAlignment
537 BSplitLayout::BaseAlignment()
538 {
539 	return BAbstractLayout::BaseAlignment();
540 }
541 
542 
543 bool
544 BSplitLayout::HasHeightForWidth()
545 {
546 	_ValidateMinMax();
547 
548 	return !fHeightForWidthItems.IsEmpty();
549 }
550 
551 
552 void
553 BSplitLayout::GetHeightForWidth(float width, float* min, float* max,
554 	float* preferred)
555 {
556 	if (!HasHeightForWidth())
557 		return;
558 
559 	float innerWidth = _SubtractInsets(BSize(width, 0)).width;
560 	_InternalGetHeightForWidth(innerWidth, false, min, max, preferred);
561 	_AddInsets(min, max, preferred);
562 }
563 
564 
565 void
566 BSplitLayout::LayoutInvalidated(bool children)
567 {
568 	delete fHorizontalLayouter;
569 	delete fVerticalLayouter;
570 	delete fHorizontalLayoutInfo;
571 	delete fVerticalLayoutInfo;
572 
573 	fHorizontalLayouter = NULL;
574 	fVerticalLayouter = NULL;
575 	fHorizontalLayoutInfo = NULL;
576 	fVerticalLayoutInfo = NULL;
577 
578 	_InvalidateCachedHeightForWidth();
579 
580 	fLayoutValid = false;
581 }
582 
583 
584 void
585 BSplitLayout::DoLayout()
586 {
587 	_ValidateMinMax();
588 
589 	// layout the elements
590 	BSize size = _SubtractInsets(LayoutArea().Size());
591 	fHorizontalLayouter->Layout(fHorizontalLayoutInfo, size.width);
592 
593 	Layouter* verticalLayouter;
594 	if (HasHeightForWidth()) {
595 		float minHeight, maxHeight, preferredHeight;
596 		_InternalGetHeightForWidth(size.width, true, &minHeight, &maxHeight,
597 			&preferredHeight);
598 		size.height = max_c(size.height, minHeight);
599 		verticalLayouter = fHeightForWidthVerticalLayouter;
600 	} else
601 		verticalLayouter = fVerticalLayouter;
602 
603 	verticalLayouter->Layout(fVerticalLayoutInfo, size.height);
604 
605 	float xOffset = fLeftInset;
606 	float yOffset = fTopInset;
607 	float splitterWidth = 0;	// pixel counts, no distances
608 	float splitterHeight = 0;	//
609 	float xSpacing = 0;
610 	float ySpacing = 0;
611 	if (fOrientation == B_HORIZONTAL) {
612 		splitterWidth = fSplitterSize;
613 		splitterHeight = size.height + 1;
614 		xSpacing = fSpacing;
615 	} else {
616 		splitterWidth = size.width + 1;
617 		splitterHeight = fSplitterSize;
618 		ySpacing = fSpacing;
619 	}
620 
621 	int itemCount = CountItems();
622 	for (int i = 0; i < itemCount; i++) {
623 		// layout the splitter
624 		if (i > 0) {
625 			SplitterItem* splitterItem = _SplitterItemAt(i - 1);
626 
627 			_LayoutItem(splitterItem, BRect(xOffset, yOffset,
628 				xOffset + splitterWidth - 1, yOffset + splitterHeight - 1),
629 				true);
630 
631 			if (fOrientation == B_HORIZONTAL)
632 				xOffset += splitterWidth + xSpacing;
633 			else
634 				yOffset += splitterHeight + ySpacing;
635 		}
636 
637 		// layout the item
638 		BLayoutItem* item = ItemAt(i);
639 		int32 visibleIndex = fVisibleItems.IndexOf(item);
640 		if (visibleIndex < 0) {
641 			_LayoutItem(item, BRect(), false);
642 			continue;
643 		}
644 
645 		// get the dimensions of the item
646 		float width = fHorizontalLayoutInfo->ElementSize(visibleIndex);
647 		float height = fVerticalLayoutInfo->ElementSize(visibleIndex);
648 
649 		// place the component
650 		_LayoutItem(item, BRect(xOffset, yOffset, xOffset + width,
651 			yOffset + height), true);
652 
653 		if (fOrientation == B_HORIZONTAL)
654 			xOffset += width + xSpacing + 1;
655 		else
656 			yOffset += height + ySpacing + 1;
657 	}
658 
659 	fLayoutValid = true;
660 }
661 
662 
663 BRect
664 BSplitLayout::SplitterItemFrame(int32 index) const
665 {
666 	if (SplitterItem* item = _SplitterItemAt(index))
667 		return item->Frame();
668 	return BRect();
669 }
670 
671 
672 bool
673 BSplitLayout::IsAboveSplitter(const BPoint& point) const
674 {
675 	return _SplitterItemAt(point) != NULL;
676 }
677 
678 
679 bool
680 BSplitLayout::StartDraggingSplitter(BPoint point)
681 {
682 	StopDraggingSplitter();
683 
684 	// Layout must be valid. Bail out, if it isn't.
685 	if (!fLayoutValid)
686 		return false;
687 
688 	// Things shouldn't be draggable, if we have a >= max layout.
689 	BSize size = _SubtractInsets(LayoutArea().Size());
690 	if ((fOrientation == B_HORIZONTAL && size.width >= fMax.width)
691 		|| (fOrientation == B_VERTICAL && size.height >= fMax.height)) {
692 		return false;
693 	}
694 
695 	int32 index = -1;
696 	if (_SplitterItemAt(point, &index) != NULL) {
697 		fDraggingStartPoint = Owner()->ConvertToScreen(point);
698 		fDraggingStartValue = _SplitterValue(index);
699 		fDraggingCurrentValue = fDraggingStartValue;
700 		fDraggingSplitterIndex = index;
701 
702 		return true;
703 	}
704 
705 	return false;
706 }
707 
708 
709 bool
710 BSplitLayout::DragSplitter(BPoint point)
711 {
712 	if (fDraggingSplitterIndex < 0)
713 		return false;
714 
715 	point = Owner()->ConvertToScreen(point);
716 
717 	int32 valueDiff;
718 	if (fOrientation == B_HORIZONTAL)
719 		valueDiff = int32(point.x - fDraggingStartPoint.x);
720 	else
721 		valueDiff = int32(point.y - fDraggingStartPoint.y);
722 
723 	return _SetSplitterValue(fDraggingSplitterIndex,
724 		fDraggingStartValue + valueDiff);
725 }
726 
727 
728 bool
729 BSplitLayout::StopDraggingSplitter()
730 {
731 	if (fDraggingSplitterIndex < 0)
732 		return false;
733 
734 	// update the item weights
735 	_UpdateSplitterWeights();
736 
737 	fDraggingSplitterIndex = -1;
738 
739 	return true;
740 }
741 
742 
743 int32
744 BSplitLayout::DraggedSplitter() const
745 {
746 	return fDraggingSplitterIndex;
747 }
748 
749 
750 status_t
751 BSplitLayout::Archive(BMessage* into, bool deep) const
752 {
753 	BArchiver archiver(into);
754 	status_t err = BAbstractLayout::Archive(into, deep);
755 
756 	if (err == B_OK)
757 		err = into->AddBool(kIsVerticalField, fOrientation == B_VERTICAL);
758 
759 	if (err == B_OK) {
760 		BRect insets(fLeftInset, fTopInset, fRightInset, fBottomInset);
761 		err = into->AddRect(kInsetsField, insets);
762 	}
763 
764 	if (err == B_OK)
765 		err = into->AddFloat(kSplitterSizeField, fSplitterSize);
766 
767 	if (err == B_OK)
768 		err = into->AddFloat(kSpacingField, fSpacing);
769 
770 	return archiver.Finish(err);
771 }
772 
773 
774 BArchivable*
775 BSplitLayout::Instantiate(BMessage* from)
776 {
777 	if (validate_instantiation(from, "BSplitLayout"))
778 		return new(std::nothrow) BSplitLayout(from);
779 	return NULL;
780 }
781 
782 
783 status_t
784 BSplitLayout::ItemArchived(BMessage* into, BLayoutItem* item, int32 index) const
785 {
786 	ItemLayoutInfo* info = _ItemLayoutInfo(item);
787 
788 	status_t err = into->AddFloat(kItemWeightField, info->weight);
789 	if (err == B_OK)
790 		err = into->AddBool(kItemCollapsibleField, info->isCollapsible);
791 
792 	return err;
793 }
794 
795 
796 status_t
797 BSplitLayout::ItemUnarchived(const BMessage* from,
798 	BLayoutItem* item, int32 index)
799 {
800 	ItemLayoutInfo* info = _ItemLayoutInfo(item);
801 	status_t err = from->FindFloat(kItemWeightField, index, &info->weight);
802 
803 	if (err == B_OK) {
804 		bool* collapsible = &info->isCollapsible;
805 		err = from->FindBool(kItemCollapsibleField, index, collapsible);
806 	}
807 	return err;
808 }
809 
810 
811 bool
812 BSplitLayout::ItemAdded(BLayoutItem* item, int32 atIndex)
813 {
814 	ItemLayoutInfo* itemInfo = new(nothrow) ItemLayoutInfo();
815 	if (!itemInfo)
816 		return false;
817 
818 	if (CountItems() > 1) {
819 		SplitterItem* splitter = new(nothrow) SplitterItem(this);
820 		ItemLayoutInfo* splitterInfo = new(nothrow) ItemLayoutInfo();
821 		if (!splitter || !splitterInfo || !fSplitterItems.AddItem(splitter)) {
822 			delete itemInfo;
823 			delete splitter;
824 			delete splitterInfo;
825 			return false;
826 		}
827 		splitter->SetLayoutData(splitterInfo);
828 		SetItemWeight(splitter, 0);
829 	}
830 
831 	item->SetLayoutData(itemInfo);
832 	SetItemWeight(item, 1);
833 	return true;
834 }
835 
836 
837 void
838 BSplitLayout::ItemRemoved(BLayoutItem* item, int32 atIndex)
839 {
840 	if (fSplitterItems.CountItems() > 0) {
841 		SplitterItem* splitterItem = (SplitterItem*)fSplitterItems.RemoveItem(
842 			fSplitterItems.CountItems() - 1);
843 		delete _ItemLayoutInfo(splitterItem);
844 		delete splitterItem;
845 	}
846 
847 	delete _ItemLayoutInfo(item);
848 	item->SetLayoutData(NULL);
849 }
850 
851 
852 void
853 BSplitLayout::_InvalidateCachedHeightForWidth()
854 {
855 	delete fHeightForWidthVerticalLayouter;
856 	delete fHeightForWidthHorizontalLayoutInfo;
857 
858 	fHeightForWidthVerticalLayouter = NULL;
859 	fHeightForWidthHorizontalLayoutInfo = NULL;
860 
861 	fCachedHeightForWidthWidth = -2;
862 	fHeightForWidthVerticalLayouterWidth = -2;
863 }
864 
865 
866 BSplitLayout::SplitterItem*
867 BSplitLayout::_SplitterItemAt(const BPoint& point, int32* index) const
868 {
869 	int32 splitterCount = fSplitterItems.CountItems();
870 	for (int32 i = 0; i < splitterCount; i++) {
871 		SplitterItem* splitItem = _SplitterItemAt(i);
872 		BRect frame = splitItem->Frame();
873 		if (frame.Contains(point)) {
874 			if (index != NULL)
875 				*index = i;
876 			return splitItem;
877 		}
878 	}
879 	return NULL;
880 }
881 
882 
883 BSplitLayout::SplitterItem*
884 BSplitLayout::_SplitterItemAt(int32 index) const
885 {
886 	return (SplitterItem*)fSplitterItems.ItemAt(index);
887 }
888 
889 
890 void
891 BSplitLayout::_GetSplitterValueRange(int32 index, ValueRange& range)
892 {
893 	ItemLayoutInfo* previousInfo = _ItemLayoutInfo(ItemAt(index));
894 	ItemLayoutInfo* nextInfo = _ItemLayoutInfo(ItemAt(index + 1));
895 	if (fOrientation == B_HORIZONTAL) {
896 		range.previousMin = (int32)previousInfo->min.width + 1;
897 		range.previousMax = (int32)previousInfo->max.width + 1;
898 		range.previousSize = previousInfo->layoutFrame.IntegerWidth() + 1;
899 		range.nextMin = (int32)nextInfo->min.width + 1;
900 		range.nextMax = (int32)nextInfo->max.width + 1;
901 		range.nextSize = nextInfo->layoutFrame.IntegerWidth() + 1;
902 	} else {
903 		range.previousMin = (int32)previousInfo->min.height + 1;
904 		range.previousMax = (int32)previousInfo->max.height + 1;
905 		range.previousSize = previousInfo->layoutFrame.IntegerHeight() + 1;
906 		range.nextMin = (int32)nextInfo->min.height + 1;
907 		range.nextMax = (int32)nextInfo->max.height + 1;
908 		range.nextSize = (int32)nextInfo->layoutFrame.IntegerHeight() + 1;
909 	}
910 
911 	range.sumValue = range.previousSize + range.nextSize;
912 	if (previousInfo->isVisible)
913 		range.sumValue += (int32)fSpacing;
914 	if (nextInfo->isVisible)
915 		range.sumValue += (int32)fSpacing;
916 }
917 
918 
919 int32
920 BSplitLayout::_SplitterValue(int32 index) const
921 {
922 	ItemLayoutInfo* info = _ItemLayoutInfo(ItemAt(index));
923 	if (info && info->isVisible) {
924 		if (fOrientation == B_HORIZONTAL)
925 			return info->layoutFrame.IntegerWidth() + 1 + (int32)fSpacing;
926 		else
927 			return info->layoutFrame.IntegerHeight() + 1 + (int32)fSpacing;
928 	} else
929 		return 0;
930 }
931 
932 
933 void
934 BSplitLayout::_LayoutItem(BLayoutItem* item, BRect frame, bool visible)
935 {
936 	// update the layout frame
937 	ItemLayoutInfo* info = _ItemLayoutInfo(item);
938 	info->isVisible = visible;
939 	if (visible)
940 		info->layoutFrame = frame;
941 	else
942 		info->layoutFrame = BRect(0, 0, -1, -1);
943 
944 	// update min/max
945 	info->min = item->MinSize();
946 	info->max = item->MaxSize();
947 
948 	if (item->HasHeightForWidth()) {
949 		BSize size = _SubtractInsets(LayoutArea().Size());
950 		float minHeight, maxHeight;
951 		item->GetHeightForWidth(size.width, &minHeight, &maxHeight, NULL);
952 		info->min.height = max_c(info->min.height, minHeight);
953 		info->max.height = min_c(info->max.height, maxHeight);
954 	}
955 
956 	// layout the item
957 	if (visible)
958 		item->AlignInFrame(frame);
959 }
960 
961 
962 void
963 BSplitLayout::_LayoutItem(BLayoutItem* item, ItemLayoutInfo* info)
964 {
965 	// update the visibility of the item
966 	bool isVisible = item->IsVisible();
967 	bool visibilityChanged = (info->isVisible != isVisible);
968 	if (visibilityChanged)
969 		item->SetVisible(info->isVisible);
970 
971 	// nothing more to do, if the item is not visible
972 	if (!info->isVisible)
973 		return;
974 
975 	item->AlignInFrame(info->layoutFrame);
976 
977 	// if the item became visible, we need to update its internal layout
978 	if (visibilityChanged &&
979 		(fOrientation != B_HORIZONTAL || !HasHeightForWidth())) {
980 		item->Relayout(true);
981 	}
982 }
983 
984 
985 bool
986 BSplitLayout::_SetSplitterValue(int32 index, int32 value)
987 {
988 	// if both items are collapsed, nothing can be dragged
989 	BLayoutItem* previousItem = ItemAt(index);
990 	BLayoutItem* nextItem = ItemAt(index + 1);
991 	ItemLayoutInfo* previousInfo = _ItemLayoutInfo(previousItem);
992 	ItemLayoutInfo* nextInfo = _ItemLayoutInfo(nextItem);
993 	ItemLayoutInfo* splitterInfo = _ItemLayoutInfo(_SplitterItemAt(index));
994 	bool previousVisible = previousInfo->isVisible;
995 	bool nextVisible = nextInfo->isVisible;
996 	if (!previousVisible && !nextVisible)
997 		return false;
998 
999 	ValueRange range;
1000 	_GetSplitterValueRange(index, range);
1001 
1002 	value = max_c(min_c(value, range.sumValue), -(int32)fSpacing);
1003 
1004 	int32 previousSize = value - (int32)fSpacing;
1005 	int32 nextSize = range.sumValue - value - (int32)fSpacing;
1006 
1007 	// Note: While this collapsed-check is mathmatically correct (i.e. we
1008 	// collapse an item, if it would become smaller than half its minimum
1009 	// size), we might want to change it, since for the user it looks like
1010 	// collapsing happens earlier. The reason being that the only visual mark
1011 	// the user has is the mouse cursor which indeed hasn't crossed the middle
1012 	// of the item yet.
1013 	bool previousCollapsed = (previousSize <= range.previousMin / 2)
1014 		&& previousInfo->isCollapsible;
1015 	bool nextCollapsed = (nextSize <= range.nextMin / 2)
1016 		&& nextInfo->isCollapsible;
1017 	if (previousCollapsed && nextCollapsed) {
1018 		// we cannot collapse both items; we have to decide for one
1019 		if (previousSize < nextSize) {
1020 			// collapse previous
1021 			nextCollapsed = false;
1022 			nextSize = range.sumValue - (int32)fSpacing;
1023 		} else {
1024 			// collapse next
1025 			previousCollapsed = false;
1026 			previousSize = range.sumValue - (int32)fSpacing;
1027 		}
1028 	}
1029 
1030 	if (previousCollapsed || nextCollapsed) {
1031 		// one collapsed item -- check whether that violates the constraints
1032 		// of the other one
1033 		int32 availableSpace = range.sumValue - (int32)fSpacing;
1034 		if (previousCollapsed) {
1035 			if (availableSpace < range.nextMin
1036 				|| availableSpace > range.nextMax) {
1037 				// we cannot collapse the previous item
1038 				previousCollapsed = false;
1039 			}
1040 		} else {
1041 			if (availableSpace < range.previousMin
1042 				|| availableSpace > range.previousMax) {
1043 				// we cannot collapse the next item
1044 				nextCollapsed = false;
1045 			}
1046 		}
1047 	}
1048 
1049 	if (!(previousCollapsed || nextCollapsed)) {
1050 		// no collapsed item -- check whether there is a close solution
1051 		previousSize = value - (int32)fSpacing;
1052 		nextSize = range.sumValue - value - (int32)fSpacing;
1053 
1054 		if (range.previousMin + range.nextMin + 2 * fSpacing > range.sumValue) {
1055 			// we don't have enough space to uncollapse both items
1056 			int32 availableSpace = range.sumValue - (int32)fSpacing;
1057 			if (previousSize < nextSize && availableSpace >= range.nextMin
1058 				&& availableSpace <= range.nextMax
1059 				&& previousInfo->isCollapsible) {
1060 				previousCollapsed = true;
1061 			} else if (availableSpace >= range.previousMin
1062 				&& availableSpace <= range.previousMax
1063 				&& nextInfo->isCollapsible) {
1064 				nextCollapsed = true;
1065 			} else if (availableSpace >= range.nextMin
1066 				&& availableSpace <= range.nextMax
1067 				&& previousInfo->isCollapsible) {
1068 				previousCollapsed = true;
1069 			} else {
1070 				if (previousSize < nextSize && previousInfo->isCollapsible) {
1071 					previousCollapsed = true;
1072 				} else if (nextInfo->isCollapsible) {
1073 					nextCollapsed = true;
1074 				} else {
1075 					// Neither item is collapsible although there's not enough
1076 					// space: Give them both their minimum size.
1077 					previousSize = range.previousMin;
1078 					nextSize = range.nextMin;
1079 				}
1080 			}
1081 
1082 		} else {
1083 			// there is enough space for both items
1084 			// make sure the min constraints are satisfied
1085 			if (previousSize < range.previousMin) {
1086 				previousSize = range.previousMin;
1087 				nextSize = range.sumValue - previousSize - 2 * (int32)fSpacing;
1088 			} else if (nextSize < range.nextMin) {
1089 				nextSize = range.nextMin;
1090 				previousSize = range.sumValue - nextSize - 2 * (int32)fSpacing;
1091 			}
1092 
1093 			// if we can, also satisfy the max constraints
1094 			if (range.previousMax + range.nextMax + 2 * (int32)fSpacing
1095 					>= range.sumValue) {
1096 				if (previousSize > range.previousMax) {
1097 					previousSize = range.previousMax;
1098 					nextSize = range.sumValue - previousSize
1099 						- 2 * (int32)fSpacing;
1100 				} else if (nextSize > range.nextMax) {
1101 					nextSize = range.nextMax;
1102 					previousSize = range.sumValue - nextSize
1103 						- 2 * (int32)fSpacing;
1104 				}
1105 			}
1106 		}
1107 	}
1108 
1109 	// compute the size for one collapsed item; for none collapsed item we
1110 	// already have correct values
1111 	if (previousCollapsed || nextCollapsed) {
1112 		int32 availableSpace = range.sumValue - (int32)fSpacing;
1113 		if (previousCollapsed) {
1114 			previousSize = 0;
1115 			nextSize = availableSpace;
1116 		} else {
1117 			previousSize = availableSpace;
1118 			nextSize = 0;
1119 		}
1120 	}
1121 
1122 	int32 newValue = previousSize + (previousCollapsed ? 0 : (int32)fSpacing);
1123 	if (newValue == fDraggingCurrentValue) {
1124 		// nothing changed
1125 		return false;
1126 	}
1127 
1128 	// something changed: we need to recompute the layout
1129 	int32 baseOffset = -fDraggingCurrentValue;
1130 		// offset to the current splitter position
1131 	int32 splitterOffset = baseOffset + newValue;
1132 	int32 nextOffset = splitterOffset + (int32)fSplitterSize + (int32)fSpacing;
1133 
1134 	BRect splitterFrame(splitterInfo->layoutFrame);
1135 	if (fOrientation == B_HORIZONTAL) {
1136 		// horizontal layout
1137 		// previous item
1138 		float left = splitterFrame.left + baseOffset;
1139 		previousInfo->layoutFrame.Set(
1140 			left,
1141 			splitterFrame.top,
1142 			left + previousSize - 1,
1143 			splitterFrame.bottom);
1144 
1145 		// next item
1146 		left = splitterFrame.left + nextOffset;
1147 		nextInfo->layoutFrame.Set(
1148 			left,
1149 			splitterFrame.top,
1150 			left + nextSize - 1,
1151 			splitterFrame.bottom);
1152 
1153 		// splitter
1154 		splitterInfo->layoutFrame.left += splitterOffset;
1155 		splitterInfo->layoutFrame.right += splitterOffset;
1156 	} else {
1157 		// vertical layout
1158 		// previous item
1159 		float top = splitterFrame.top + baseOffset;
1160 		previousInfo->layoutFrame.Set(
1161 			splitterFrame.left,
1162 			top,
1163 			splitterFrame.right,
1164 			top + previousSize - 1);
1165 
1166 		// next item
1167 		top = splitterFrame.top + nextOffset;
1168 		nextInfo->layoutFrame.Set(
1169 			splitterFrame.left,
1170 			top,
1171 			splitterFrame.right,
1172 			top + nextSize - 1);
1173 
1174 		// splitter
1175 		splitterInfo->layoutFrame.top += splitterOffset;
1176 		splitterInfo->layoutFrame.bottom += splitterOffset;
1177 	}
1178 
1179 	previousInfo->isVisible = !previousCollapsed;
1180 	nextInfo->isVisible = !nextCollapsed;
1181 
1182 	bool heightForWidth = (fOrientation == B_HORIZONTAL && HasHeightForWidth());
1183 
1184 	// If the item visibility is to be changed, we need to update the splitter
1185 	// values now, since the visibility change will cause an invalidation.
1186 	if (previousVisible != previousInfo->isVisible
1187 		|| nextVisible != nextInfo->isVisible || heightForWidth) {
1188 		_UpdateSplitterWeights();
1189 	}
1190 
1191 	// If we have height for width items, we need to invalidate the previous
1192 	// and the next item. Actually we would only need to invalidate height for
1193 	// width items, but since non height for width items might be aligned with
1194 	// height for width items, we need to trigger a layout that creates a
1195 	// context that spans all aligned items.
1196 	// We invalidate already here, so that changing the items' size won't cause
1197 	// an immediate relayout.
1198 	if (heightForWidth) {
1199 		previousItem->InvalidateLayout();
1200 		nextItem->InvalidateLayout();
1201 	}
1202 
1203 	// do the layout
1204 	_LayoutItem(previousItem, previousInfo);
1205 	_LayoutItem(_SplitterItemAt(index), splitterInfo);
1206 	_LayoutItem(nextItem, nextInfo);
1207 
1208 	fDraggingCurrentValue = newValue;
1209 
1210 	return true;
1211 }
1212 
1213 
1214 BSplitLayout::ItemLayoutInfo*
1215 BSplitLayout::_ItemLayoutInfo(BLayoutItem* item) const
1216 {
1217 	return (ItemLayoutInfo*)item->LayoutData();
1218 }
1219 
1220 
1221 void
1222 BSplitLayout::_UpdateSplitterWeights()
1223 {
1224 	int32 count = CountItems();
1225 	for (int32 i = 0; i < count; i++) {
1226 		float weight;
1227 		if (fOrientation == B_HORIZONTAL)
1228 			weight = _ItemLayoutInfo(ItemAt(i))->layoutFrame.Width() + 1;
1229 		else
1230 			weight = _ItemLayoutInfo(ItemAt(i))->layoutFrame.Height() + 1;
1231 
1232 		SetItemWeight(i, weight, false);
1233 	}
1234 
1235 	// Just updating the splitter weights is fine in principle. The next
1236 	// LayoutItems() will use the correct values. But, if our orientation is
1237 	// vertical, the cached height for width info needs to be flushed, or the
1238 	// obsolete cached values will be used.
1239 	if (fOrientation == B_VERTICAL)
1240 		_InvalidateCachedHeightForWidth();
1241 }
1242 
1243 
1244 void
1245 BSplitLayout::_ValidateMinMax()
1246 {
1247 	if (fHorizontalLayouter != NULL)
1248 		return;
1249 
1250 	fLayoutValid = false;
1251 
1252 	fVisibleItems.MakeEmpty();
1253 	fHeightForWidthItems.MakeEmpty();
1254 
1255 	_InvalidateCachedHeightForWidth();
1256 
1257 	// filter the visible items
1258 	int32 itemCount = CountItems();
1259 	for (int32 i = 0; i < itemCount; i++) {
1260 		BLayoutItem* item = ItemAt(i);
1261 		if (item->IsVisible())
1262 			fVisibleItems.AddItem(item);
1263 
1264 		// Add "height for width" items even, if they aren't visible. Otherwise
1265 		// we may get our parent into trouble, since we could change from
1266 		// "height for width" to "not height for width".
1267 		if (item->HasHeightForWidth())
1268 			fHeightForWidthItems.AddItem(item);
1269 	}
1270 	itemCount = fVisibleItems.CountItems();
1271 
1272 	// create the layouters
1273 	Layouter* itemLayouter = new SimpleLayouter(itemCount, 0);
1274 
1275 	if (fOrientation == B_HORIZONTAL) {
1276 		fHorizontalLayouter = itemLayouter;
1277 		fVerticalLayouter = new OneElementLayouter();
1278 	} else {
1279 		fHorizontalLayouter = new OneElementLayouter();
1280 		fVerticalLayouter = itemLayouter;
1281 	}
1282 
1283 	// tell the layouters about our constraints
1284 	if (itemCount > 0) {
1285 		for (int32 i = 0; i < itemCount; i++) {
1286 			BLayoutItem* item = (BLayoutItem*)fVisibleItems.ItemAt(i);
1287 			BSize min = item->MinSize();
1288 			BSize max = item->MaxSize();
1289 			BSize preferred = item->PreferredSize();
1290 
1291 			fHorizontalLayouter->AddConstraints(i, 1, min.width, max.width,
1292 				preferred.width);
1293 			fVerticalLayouter->AddConstraints(i, 1, min.height, max.height,
1294 				preferred.height);
1295 
1296 			float weight = ItemWeight(item);
1297 			fHorizontalLayouter->SetWeight(i, weight);
1298 			fVerticalLayouter->SetWeight(i, weight);
1299 		}
1300 	}
1301 
1302 	fMin.width = fHorizontalLayouter->MinSize();
1303 	fMin.height = fVerticalLayouter->MinSize();
1304 	fMax.width = fHorizontalLayouter->MaxSize();
1305 	fMax.height = fVerticalLayouter->MaxSize();
1306 	fPreferred.width = fHorizontalLayouter->PreferredSize();
1307 	fPreferred.height = fVerticalLayouter->PreferredSize();
1308 
1309 	fHorizontalLayoutInfo = fHorizontalLayouter->CreateLayoutInfo();
1310 	if (fHeightForWidthItems.IsEmpty())
1311 		fVerticalLayoutInfo = fVerticalLayouter->CreateLayoutInfo();
1312 
1313 	ResetLayoutInvalidation();
1314 }
1315 
1316 
1317 void
1318 BSplitLayout::_InternalGetHeightForWidth(float width, bool realLayout,
1319 	float* minHeight, float* maxHeight, float* preferredHeight)
1320 {
1321 	if ((realLayout && fHeightForWidthVerticalLayouterWidth != width)
1322 		|| (!realLayout && fCachedHeightForWidthWidth != width)) {
1323 		// The general strategy is to clone the vertical layouter, which only
1324 		// knows the general min/max constraints, do a horizontal layout for the
1325 		// given width, and add the children's height for width constraints to
1326 		// the cloned vertical layouter. If this method is invoked internally,
1327 		// we keep the cloned vertical layouter, for it will be used for doing
1328 		// the layout. Otherwise we just drop it after we've got the height for
1329 		// width info.
1330 
1331 		// clone the vertical layouter and get the horizontal layout info to be used
1332 		LayoutInfo* horizontalLayoutInfo = NULL;
1333 		Layouter* verticalLayouter = fVerticalLayouter->CloneLayouter();
1334 		if (realLayout) {
1335 			horizontalLayoutInfo = fHorizontalLayoutInfo;
1336 			delete fHeightForWidthVerticalLayouter;
1337 			fHeightForWidthVerticalLayouter = verticalLayouter;
1338 			delete fVerticalLayoutInfo;
1339 			fVerticalLayoutInfo = verticalLayouter->CreateLayoutInfo();
1340 			fHeightForWidthVerticalLayouterWidth = width;
1341 		} else {
1342 			if (fHeightForWidthHorizontalLayoutInfo == NULL) {
1343 				delete fHeightForWidthHorizontalLayoutInfo;
1344 				fHeightForWidthHorizontalLayoutInfo
1345 					= fHorizontalLayouter->CreateLayoutInfo();
1346 			}
1347 			horizontalLayoutInfo = fHeightForWidthHorizontalLayoutInfo;
1348 		}
1349 
1350 		// do the horizontal layout (already done when doing this for the real
1351 		// layout)
1352 		if (!realLayout)
1353 			fHorizontalLayouter->Layout(horizontalLayoutInfo, width);
1354 
1355 		// add the children's height for width constraints
1356 		int32 count = fHeightForWidthItems.CountItems();
1357 		for (int32 i = 0; i < count; i++) {
1358 			BLayoutItem* item = (BLayoutItem*)fHeightForWidthItems.ItemAt(i);
1359 			int32 index = fVisibleItems.IndexOf(item);
1360 			if (index >= 0) {
1361 				float itemMinHeight, itemMaxHeight, itemPreferredHeight;
1362 				item->GetHeightForWidth(
1363 					horizontalLayoutInfo->ElementSize(index),
1364 					&itemMinHeight, &itemMaxHeight, &itemPreferredHeight);
1365 				verticalLayouter->AddConstraints(index, 1, itemMinHeight,
1366 					itemMaxHeight, itemPreferredHeight);
1367 			}
1368 		}
1369 
1370 		// get the height for width info
1371 		fCachedHeightForWidthWidth = width;
1372 		fCachedMinHeightForWidth = verticalLayouter->MinSize();
1373 		fCachedMaxHeightForWidth = verticalLayouter->MaxSize();
1374 		fCachedPreferredHeightForWidth = verticalLayouter->PreferredSize();
1375 	}
1376 
1377 	if (minHeight)
1378 		*minHeight = fCachedMinHeightForWidth;
1379 	if (maxHeight)
1380 		*maxHeight = fCachedMaxHeightForWidth;
1381 	if (preferredHeight)
1382 		*preferredHeight = fCachedPreferredHeightForWidth;
1383 }
1384 
1385 
1386 float
1387 BSplitLayout::_SplitterSpace() const
1388 {
1389 	int32 splitters = fSplitterItems.CountItems();
1390 	float space = 0;
1391 	if (splitters > 0) {
1392 		space = (fVisibleItems.CountItems() + splitters - 1) * fSpacing
1393 			+ splitters * fSplitterSize;
1394 	}
1395 
1396 	return space;
1397 }
1398 
1399 
1400 BSize
1401 BSplitLayout::_AddInsets(BSize size)
1402 {
1403 	size.width = BLayoutUtils::AddDistances(size.width,
1404 		fLeftInset + fRightInset - 1);
1405 	size.height = BLayoutUtils::AddDistances(size.height,
1406 		fTopInset + fBottomInset - 1);
1407 
1408 	float spacing = _SplitterSpace();
1409 	if (fOrientation == B_HORIZONTAL)
1410 		size.width = BLayoutUtils::AddDistances(size.width, spacing - 1);
1411 	else
1412 		size.height = BLayoutUtils::AddDistances(size.height, spacing - 1);
1413 
1414 	return size;
1415 }
1416 
1417 
1418 void
1419 BSplitLayout::_AddInsets(float* minHeight, float* maxHeight,
1420 	float* preferredHeight)
1421 {
1422 	float insets = fTopInset + fBottomInset - 1;
1423 	if (fOrientation == B_VERTICAL)
1424 		insets += _SplitterSpace();
1425 	if (minHeight)
1426 		*minHeight = BLayoutUtils::AddDistances(*minHeight, insets);
1427 	if (maxHeight)
1428 		*maxHeight = BLayoutUtils::AddDistances(*maxHeight, insets);
1429 	if (preferredHeight)
1430 		*preferredHeight = BLayoutUtils::AddDistances(*preferredHeight, insets);
1431 }
1432 
1433 
1434 BSize
1435 BSplitLayout::_SubtractInsets(BSize size)
1436 {
1437 	size.width = BLayoutUtils::SubtractDistances(size.width,
1438 		fLeftInset + fRightInset - 1);
1439 	size.height = BLayoutUtils::SubtractDistances(size.height,
1440 		fTopInset + fBottomInset - 1);
1441 
1442 	float spacing = _SplitterSpace();
1443 	if (fOrientation == B_HORIZONTAL)
1444 		size.width = BLayoutUtils::SubtractDistances(size.width, spacing - 1);
1445 	else
1446 		size.height = BLayoutUtils::SubtractDistances(size.height, spacing - 1);
1447 
1448 	return size;
1449 }
1450 
1451