vfs: check userland buffers before reading them.
[haiku.git] / src / kits / interface / SplitLayout.cpp
blobb15c9984ab7ba3b34ce7273f5f770c7dcb78071a
1 /*
2 * Copyright 2006-2009, Ingo Weinhold <ingo_weinhold@gmx.de>.
3 * Copyright 2015, Rene Gollent, rene@gollent.com.
4 * All rights reserved. Distributed under the terms of the MIT License.
5 */
8 #include "SplitLayout.h"
10 #include <new>
11 #include <stdio.h>
13 #include <ControlLook.h>
14 #include <LayoutItem.h>
15 #include <LayoutUtils.h>
16 #include <Message.h>
17 #include <View.h>
19 #include "OneElementLayouter.h"
20 #include "SimpleLayouter.h"
23 using std::nothrow;
26 // archivng constants
27 namespace {
28 const char* const kItemCollapsibleField = "BSplitLayout:item:collapsible";
29 const char* const kItemWeightField = "BSplitLayout:item:weight";
30 const char* const kSpacingField = "BSplitLayout:spacing";
31 const char* const kSplitterSizeField = "BSplitLayout:splitterSize";
32 const char* const kIsVerticalField = "BSplitLayout:vertical";
33 const char* const kInsetsField = "BSplitLayout:insets";
37 class BSplitLayout::ItemLayoutInfo {
38 public:
39 float weight;
40 BRect layoutFrame;
41 BSize min;
42 BSize max;
43 bool isVisible;
44 bool isCollapsible;
46 ItemLayoutInfo()
48 weight(1.0f),
49 layoutFrame(0, 0, -1, -1),
50 min(),
51 max(),
52 isVisible(true),
53 isCollapsible(true)
59 class BSplitLayout::ValueRange {
60 public:
61 int32 sumValue; // including spacing
62 int32 previousMin;
63 int32 previousMax;
64 int32 previousSize;
65 int32 nextMin;
66 int32 nextMax;
67 int32 nextSize;
71 class BSplitLayout::SplitterItem : public BLayoutItem {
72 public:
73 SplitterItem(BSplitLayout* layout)
75 fLayout(layout),
76 fFrame()
81 virtual BSize MinSize()
83 if (fLayout->Orientation() == B_HORIZONTAL)
84 return BSize(fLayout->SplitterSize() - 1, -1);
85 else
86 return BSize(-1, fLayout->SplitterSize() - 1);
89 virtual BSize MaxSize()
91 if (fLayout->Orientation() == B_HORIZONTAL)
92 return BSize(fLayout->SplitterSize() - 1, B_SIZE_UNLIMITED);
93 else
94 return BSize(B_SIZE_UNLIMITED, fLayout->SplitterSize() - 1);
97 virtual BSize PreferredSize()
99 return MinSize();
102 virtual BAlignment Alignment()
104 return BAlignment(B_ALIGN_HORIZONTAL_CENTER, B_ALIGN_VERTICAL_CENTER);
107 virtual void SetExplicitMinSize(BSize size)
109 // not allowed
112 virtual void SetExplicitMaxSize(BSize size)
114 // not allowed
117 virtual void SetExplicitPreferredSize(BSize size)
119 // not allowed
122 virtual void SetExplicitAlignment(BAlignment alignment)
124 // not allowed
127 virtual bool IsVisible()
129 return true;
132 virtual void SetVisible(bool visible)
134 // not allowed
138 virtual BRect Frame()
140 return fFrame;
143 virtual void SetFrame(BRect frame)
145 fFrame = frame;
148 private:
149 BSplitLayout* fLayout;
150 BRect fFrame;
154 // #pragma mark -
157 BSplitLayout::BSplitLayout(orientation orientation, float spacing)
159 fOrientation(orientation),
160 fLeftInset(0),
161 fRightInset(0),
162 fTopInset(0),
163 fBottomInset(0),
164 fSplitterSize(6),
165 fSpacing(BControlLook::ComposeSpacing(spacing)),
167 fSplitterItems(),
168 fVisibleItems(),
169 fMin(),
170 fMax(),
171 fPreferred(),
173 fHorizontalLayouter(NULL),
174 fVerticalLayouter(NULL),
175 fHorizontalLayoutInfo(NULL),
176 fVerticalLayoutInfo(NULL),
178 fHeightForWidthItems(),
179 fHeightForWidthVerticalLayouter(NULL),
180 fHeightForWidthHorizontalLayoutInfo(NULL),
182 fLayoutValid(false),
184 fCachedHeightForWidthWidth(-2),
185 fHeightForWidthVerticalLayouterWidth(-2),
186 fCachedMinHeightForWidth(-1),
187 fCachedMaxHeightForWidth(-1),
188 fCachedPreferredHeightForWidth(-1),
190 fDraggingStartPoint(),
191 fDraggingStartValue(0),
192 fDraggingCurrentValue(0),
193 fDraggingSplitterIndex(-1)
198 BSplitLayout::BSplitLayout(BMessage* from)
200 BAbstractLayout(BUnarchiver::PrepareArchive(from)),
201 fOrientation(B_HORIZONTAL),
202 fLeftInset(0),
203 fRightInset(0),
204 fTopInset(0),
205 fBottomInset(0),
206 fSplitterSize(6),
207 fSpacing(be_control_look->DefaultItemSpacing()),
209 fSplitterItems(),
210 fVisibleItems(),
211 fMin(),
212 fMax(),
213 fPreferred(),
215 fHorizontalLayouter(NULL),
216 fVerticalLayouter(NULL),
217 fHorizontalLayoutInfo(NULL),
218 fVerticalLayoutInfo(NULL),
220 fHeightForWidthItems(),
221 fHeightForWidthVerticalLayouter(NULL),
222 fHeightForWidthHorizontalLayoutInfo(NULL),
224 fLayoutValid(false),
226 fCachedHeightForWidthWidth(-2),
227 fHeightForWidthVerticalLayouterWidth(-2),
228 fCachedMinHeightForWidth(-1),
229 fCachedMaxHeightForWidth(-1),
230 fCachedPreferredHeightForWidth(-1),
232 fDraggingStartPoint(),
233 fDraggingStartValue(0),
234 fDraggingCurrentValue(0),
235 fDraggingSplitterIndex(-1)
237 BUnarchiver unarchiver(from);
239 bool isVertical;
240 status_t err = from->FindBool(kIsVerticalField, &isVertical);
241 if (err != B_OK) {
242 unarchiver.Finish(err);
243 return;
245 fOrientation = (isVertical) ? B_VERTICAL : B_HORIZONTAL ;
247 BRect insets;
248 err = from->FindRect(kInsetsField, &insets);
249 if (err != B_OK) {
250 unarchiver.Finish(err);
251 return;
253 SetInsets(insets.left, insets.top, insets.right, insets.bottom);
255 err = from->FindFloat(kSplitterSizeField, &fSplitterSize);
256 if (err == B_OK)
257 err = from->FindFloat(kSpacingField, &fSpacing);
259 unarchiver.Finish(err);
263 BSplitLayout::~BSplitLayout()
268 void
269 BSplitLayout::SetInsets(float left, float top, float right, float bottom)
271 fLeftInset = left;
272 fTopInset = top;
273 fRightInset = right;
274 fBottomInset = bottom;
276 InvalidateLayout();
280 void
281 BSplitLayout::GetInsets(float* left, float* top, float* right,
282 float* bottom) const
284 if (left)
285 *left = fLeftInset;
286 if (top)
287 *top = fTopInset;
288 if (right)
289 *right = fRightInset;
290 if (bottom)
291 *bottom = fBottomInset;
295 float
296 BSplitLayout::Spacing() const
298 return fSpacing;
302 void
303 BSplitLayout::SetSpacing(float spacing)
305 spacing = BControlLook::ComposeSpacing(spacing);
306 if (spacing != fSpacing) {
307 fSpacing = spacing;
309 InvalidateLayout();
314 orientation
315 BSplitLayout::Orientation() const
317 return fOrientation;
321 void
322 BSplitLayout::SetOrientation(orientation orientation)
324 if (orientation != fOrientation) {
325 fOrientation = orientation;
327 InvalidateLayout();
332 float
333 BSplitLayout::SplitterSize() const
335 return fSplitterSize;
339 void
340 BSplitLayout::SetSplitterSize(float size)
342 if (size != fSplitterSize) {
343 fSplitterSize = size;
345 InvalidateLayout();
350 BLayoutItem*
351 BSplitLayout::AddView(BView* child)
353 return BAbstractLayout::AddView(child);
357 BLayoutItem*
358 BSplitLayout::AddView(int32 index, BView* child)
360 return BAbstractLayout::AddView(index, child);
364 BLayoutItem*
365 BSplitLayout::AddView(BView* child, float weight)
367 return AddView(-1, child, weight);
371 BLayoutItem*
372 BSplitLayout::AddView(int32 index, BView* child, float weight)
374 BLayoutItem* item = AddView(index, child);
375 if (item)
376 SetItemWeight(item, weight);
378 return item;
382 bool
383 BSplitLayout::AddItem(BLayoutItem* item)
385 return BAbstractLayout::AddItem(item);
389 bool
390 BSplitLayout::AddItem(int32 index, BLayoutItem* item)
392 return BAbstractLayout::AddItem(index, item);
396 bool
397 BSplitLayout::AddItem(BLayoutItem* item, float weight)
399 return AddItem(-1, item, weight);
403 bool
404 BSplitLayout::AddItem(int32 index, BLayoutItem* item, float weight)
406 bool success = AddItem(index, item);
407 if (success)
408 SetItemWeight(item, weight);
410 return success;
414 float
415 BSplitLayout::ItemWeight(int32 index) const
417 if (index < 0 || index >= CountItems())
418 return 0;
420 return ItemWeight(ItemAt(index));
424 float
425 BSplitLayout::ItemWeight(BLayoutItem* item) const
427 if (ItemLayoutInfo* info = _ItemLayoutInfo(item))
428 return info->weight;
429 return 0;
433 void
434 BSplitLayout::SetItemWeight(int32 index, float weight, bool invalidateLayout)
436 if (index < 0 || index >= CountItems())
437 return;
439 BLayoutItem* item = ItemAt(index);
440 SetItemWeight(item, weight);
442 if (fHorizontalLayouter) {
443 int32 visibleIndex = fVisibleItems.IndexOf(item);
444 if (visibleIndex >= 0) {
445 if (fOrientation == B_HORIZONTAL)
446 fHorizontalLayouter->SetWeight(visibleIndex, weight);
447 else
448 fVerticalLayouter->SetWeight(visibleIndex, weight);
452 if (invalidateLayout)
453 InvalidateLayout();
457 void
458 BSplitLayout::SetItemWeight(BLayoutItem* item, float weight)
460 if (ItemLayoutInfo* info = _ItemLayoutInfo(item))
461 info->weight = weight;
465 bool
466 BSplitLayout::IsCollapsible(int32 index) const
468 return _ItemLayoutInfo(ItemAt(index))->isCollapsible;
472 void
473 BSplitLayout::SetCollapsible(bool collapsible)
475 SetCollapsible(0, CountItems() - 1, collapsible);
479 void
480 BSplitLayout::SetCollapsible(int32 index, bool collapsible)
482 SetCollapsible(index, index, collapsible);
486 void
487 BSplitLayout::SetCollapsible(int32 first, int32 last, bool collapsible)
489 for (int32 i = first; i <= last; i++)
490 _ItemLayoutInfo(ItemAt(i))->isCollapsible = collapsible;
494 bool
495 BSplitLayout::IsItemCollapsed(int32 index) const
497 return !_ItemLayoutInfo(ItemAt(index))->isVisible;
501 void
502 BSplitLayout::SetItemCollapsed(int32 index, bool collapsed)
504 ItemAt(index)->SetVisible(!collapsed);
506 InvalidateLayout(true);
510 BSize
511 BSplitLayout::BaseMinSize()
513 _ValidateMinMax();
515 return _AddInsets(fMin);
519 BSize
520 BSplitLayout::BaseMaxSize()
522 _ValidateMinMax();
524 return _AddInsets(fMax);
528 BSize
529 BSplitLayout::BasePreferredSize()
531 _ValidateMinMax();
533 return _AddInsets(fPreferred);
537 BAlignment
538 BSplitLayout::BaseAlignment()
540 return BAbstractLayout::BaseAlignment();
544 bool
545 BSplitLayout::HasHeightForWidth()
547 _ValidateMinMax();
549 return !fHeightForWidthItems.IsEmpty();
553 void
554 BSplitLayout::GetHeightForWidth(float width, float* min, float* max,
555 float* preferred)
557 if (!HasHeightForWidth())
558 return;
560 float innerWidth = _SubtractInsets(BSize(width, 0)).width;
561 _InternalGetHeightForWidth(innerWidth, false, min, max, preferred);
562 _AddInsets(min, max, preferred);
566 void
567 BSplitLayout::LayoutInvalidated(bool children)
569 delete fHorizontalLayouter;
570 delete fVerticalLayouter;
571 delete fHorizontalLayoutInfo;
572 delete fVerticalLayoutInfo;
574 fHorizontalLayouter = NULL;
575 fVerticalLayouter = NULL;
576 fHorizontalLayoutInfo = NULL;
577 fVerticalLayoutInfo = NULL;
579 _InvalidateCachedHeightForWidth();
581 fLayoutValid = false;
585 void
586 BSplitLayout::DoLayout()
588 _ValidateMinMax();
590 // layout the elements
591 BSize size = _SubtractInsets(LayoutArea().Size());
592 fHorizontalLayouter->Layout(fHorizontalLayoutInfo, size.width);
594 Layouter* verticalLayouter;
595 if (HasHeightForWidth()) {
596 float minHeight, maxHeight, preferredHeight;
597 _InternalGetHeightForWidth(size.width, true, &minHeight, &maxHeight,
598 &preferredHeight);
599 size.height = max_c(size.height, minHeight);
600 verticalLayouter = fHeightForWidthVerticalLayouter;
601 } else
602 verticalLayouter = fVerticalLayouter;
604 verticalLayouter->Layout(fVerticalLayoutInfo, size.height);
606 float xOffset = fLeftInset;
607 float yOffset = fTopInset;
608 float splitterWidth = 0; // pixel counts, no distances
609 float splitterHeight = 0; //
610 float xSpacing = 0;
611 float ySpacing = 0;
612 if (fOrientation == B_HORIZONTAL) {
613 splitterWidth = fSplitterSize;
614 splitterHeight = size.height + 1;
615 xSpacing = fSpacing;
616 } else {
617 splitterWidth = size.width + 1;
618 splitterHeight = fSplitterSize;
619 ySpacing = fSpacing;
622 int itemCount = CountItems();
623 for (int i = 0; i < itemCount; i++) {
624 // layout the splitter
625 if (i > 0) {
626 SplitterItem* splitterItem = _SplitterItemAt(i - 1);
628 _LayoutItem(splitterItem, BRect(xOffset, yOffset,
629 xOffset + splitterWidth - 1, yOffset + splitterHeight - 1),
630 true);
632 if (fOrientation == B_HORIZONTAL)
633 xOffset += splitterWidth + xSpacing;
634 else
635 yOffset += splitterHeight + ySpacing;
638 // layout the item
639 BLayoutItem* item = ItemAt(i);
640 int32 visibleIndex = fVisibleItems.IndexOf(item);
641 if (visibleIndex < 0) {
642 _LayoutItem(item, BRect(), false);
643 continue;
646 // get the dimensions of the item
647 float width = fHorizontalLayoutInfo->ElementSize(visibleIndex);
648 float height = fVerticalLayoutInfo->ElementSize(visibleIndex);
650 // place the component
651 _LayoutItem(item, BRect(xOffset, yOffset, xOffset + width,
652 yOffset + height), true);
654 if (fOrientation == B_HORIZONTAL)
655 xOffset += width + xSpacing + 1;
656 else
657 yOffset += height + ySpacing + 1;
660 fLayoutValid = true;
664 BRect
665 BSplitLayout::SplitterItemFrame(int32 index) const
667 if (SplitterItem* item = _SplitterItemAt(index))
668 return item->Frame();
669 return BRect();
673 bool
674 BSplitLayout::IsAboveSplitter(const BPoint& point) const
676 return _SplitterItemAt(point) != NULL;
680 bool
681 BSplitLayout::StartDraggingSplitter(BPoint point)
683 StopDraggingSplitter();
685 // Layout must be valid. Bail out, if it isn't.
686 if (!fLayoutValid)
687 return false;
689 // Things shouldn't be draggable, if we have a >= max layout.
690 BSize size = _SubtractInsets(LayoutArea().Size());
691 if ((fOrientation == B_HORIZONTAL && size.width >= fMax.width)
692 || (fOrientation == B_VERTICAL && size.height >= fMax.height)) {
693 return false;
696 int32 index = -1;
697 if (_SplitterItemAt(point, &index) != NULL) {
698 fDraggingStartPoint = Owner()->ConvertToScreen(point);
699 fDraggingStartValue = _SplitterValue(index);
700 fDraggingCurrentValue = fDraggingStartValue;
701 fDraggingSplitterIndex = index;
703 return true;
706 return false;
710 bool
711 BSplitLayout::DragSplitter(BPoint point)
713 if (fDraggingSplitterIndex < 0)
714 return false;
716 point = Owner()->ConvertToScreen(point);
718 int32 valueDiff;
719 if (fOrientation == B_HORIZONTAL)
720 valueDiff = int32(point.x - fDraggingStartPoint.x);
721 else
722 valueDiff = int32(point.y - fDraggingStartPoint.y);
724 return _SetSplitterValue(fDraggingSplitterIndex,
725 fDraggingStartValue + valueDiff);
729 bool
730 BSplitLayout::StopDraggingSplitter()
732 if (fDraggingSplitterIndex < 0)
733 return false;
735 // update the item weights
736 _UpdateSplitterWeights();
738 fDraggingSplitterIndex = -1;
740 return true;
744 int32
745 BSplitLayout::DraggedSplitter() const
747 return fDraggingSplitterIndex;
751 status_t
752 BSplitLayout::Archive(BMessage* into, bool deep) const
754 BArchiver archiver(into);
755 status_t err = BAbstractLayout::Archive(into, deep);
757 if (err == B_OK)
758 err = into->AddBool(kIsVerticalField, fOrientation == B_VERTICAL);
760 if (err == B_OK) {
761 BRect insets(fLeftInset, fTopInset, fRightInset, fBottomInset);
762 err = into->AddRect(kInsetsField, insets);
765 if (err == B_OK)
766 err = into->AddFloat(kSplitterSizeField, fSplitterSize);
768 if (err == B_OK)
769 err = into->AddFloat(kSpacingField, fSpacing);
771 return archiver.Finish(err);
775 BArchivable*
776 BSplitLayout::Instantiate(BMessage* from)
778 if (validate_instantiation(from, "BSplitLayout"))
779 return new(std::nothrow) BSplitLayout(from);
780 return NULL;
784 status_t
785 BSplitLayout::ItemArchived(BMessage* into, BLayoutItem* item, int32 index) const
787 ItemLayoutInfo* info = _ItemLayoutInfo(item);
789 status_t err = into->AddFloat(kItemWeightField, info->weight);
790 if (err == B_OK)
791 err = into->AddBool(kItemCollapsibleField, info->isCollapsible);
793 return err;
797 status_t
798 BSplitLayout::ItemUnarchived(const BMessage* from,
799 BLayoutItem* item, int32 index)
801 ItemLayoutInfo* info = _ItemLayoutInfo(item);
802 status_t err = from->FindFloat(kItemWeightField, index, &info->weight);
804 if (err == B_OK) {
805 bool* collapsible = &info->isCollapsible;
806 err = from->FindBool(kItemCollapsibleField, index, collapsible);
808 return err;
812 bool
813 BSplitLayout::ItemAdded(BLayoutItem* item, int32 atIndex)
815 ItemLayoutInfo* itemInfo = new(nothrow) ItemLayoutInfo();
816 if (!itemInfo)
817 return false;
819 if (CountItems() > 1) {
820 SplitterItem* splitter = new(nothrow) SplitterItem(this);
821 ItemLayoutInfo* splitterInfo = new(nothrow) ItemLayoutInfo();
822 if (!splitter || !splitterInfo || !fSplitterItems.AddItem(splitter)) {
823 delete itemInfo;
824 delete splitter;
825 delete splitterInfo;
826 return false;
828 splitter->SetLayoutData(splitterInfo);
829 SetItemWeight(splitter, 0);
832 item->SetLayoutData(itemInfo);
833 SetItemWeight(item, 1);
834 return true;
838 void
839 BSplitLayout::ItemRemoved(BLayoutItem* item, int32 atIndex)
841 if (fSplitterItems.CountItems() > 0) {
842 SplitterItem* splitterItem = (SplitterItem*)fSplitterItems.RemoveItem(
843 fSplitterItems.CountItems() - 1);
844 delete _ItemLayoutInfo(splitterItem);
845 delete splitterItem;
848 delete _ItemLayoutInfo(item);
849 item->SetLayoutData(NULL);
853 void
854 BSplitLayout::_InvalidateCachedHeightForWidth()
856 delete fHeightForWidthVerticalLayouter;
857 delete fHeightForWidthHorizontalLayoutInfo;
859 fHeightForWidthVerticalLayouter = NULL;
860 fHeightForWidthHorizontalLayoutInfo = NULL;
862 fCachedHeightForWidthWidth = -2;
863 fHeightForWidthVerticalLayouterWidth = -2;
867 BSplitLayout::SplitterItem*
868 BSplitLayout::_SplitterItemAt(const BPoint& point, int32* index) const
870 int32 splitterCount = fSplitterItems.CountItems();
871 for (int32 i = 0; i < splitterCount; i++) {
872 SplitterItem* splitItem = _SplitterItemAt(i);
873 BRect frame = splitItem->Frame();
874 if (frame.Contains(point)) {
875 if (index != NULL)
876 *index = i;
877 return splitItem;
880 return NULL;
884 BSplitLayout::SplitterItem*
885 BSplitLayout::_SplitterItemAt(int32 index) const
887 return (SplitterItem*)fSplitterItems.ItemAt(index);
891 void
892 BSplitLayout::_GetSplitterValueRange(int32 index, ValueRange& range)
894 ItemLayoutInfo* previousInfo = _ItemLayoutInfo(ItemAt(index));
895 ItemLayoutInfo* nextInfo = _ItemLayoutInfo(ItemAt(index + 1));
896 if (fOrientation == B_HORIZONTAL) {
897 range.previousMin = (int32)previousInfo->min.width + 1;
898 range.previousMax = (int32)previousInfo->max.width + 1;
899 range.previousSize = previousInfo->layoutFrame.IntegerWidth() + 1;
900 range.nextMin = (int32)nextInfo->min.width + 1;
901 range.nextMax = (int32)nextInfo->max.width + 1;
902 range.nextSize = nextInfo->layoutFrame.IntegerWidth() + 1;
903 } else {
904 range.previousMin = (int32)previousInfo->min.height + 1;
905 range.previousMax = (int32)previousInfo->max.height + 1;
906 range.previousSize = previousInfo->layoutFrame.IntegerHeight() + 1;
907 range.nextMin = (int32)nextInfo->min.height + 1;
908 range.nextMax = (int32)nextInfo->max.height + 1;
909 range.nextSize = (int32)nextInfo->layoutFrame.IntegerHeight() + 1;
912 range.sumValue = range.previousSize + range.nextSize;
913 if (previousInfo->isVisible)
914 range.sumValue += (int32)fSpacing;
915 if (nextInfo->isVisible)
916 range.sumValue += (int32)fSpacing;
920 int32
921 BSplitLayout::_SplitterValue(int32 index) const
923 ItemLayoutInfo* info = _ItemLayoutInfo(ItemAt(index));
924 if (info && info->isVisible) {
925 if (fOrientation == B_HORIZONTAL)
926 return info->layoutFrame.IntegerWidth() + 1 + (int32)fSpacing;
927 else
928 return info->layoutFrame.IntegerHeight() + 1 + (int32)fSpacing;
929 } else
930 return 0;
934 void
935 BSplitLayout::_LayoutItem(BLayoutItem* item, BRect frame, bool visible)
937 // update the layout frame
938 ItemLayoutInfo* info = _ItemLayoutInfo(item);
939 info->isVisible = visible;
940 if (visible)
941 info->layoutFrame = frame;
942 else
943 info->layoutFrame = BRect(0, 0, -1, -1);
945 // update min/max
946 info->min = item->MinSize();
947 info->max = item->MaxSize();
949 if (item->HasHeightForWidth()) {
950 BSize size = _SubtractInsets(LayoutArea().Size());
951 float minHeight, maxHeight;
952 item->GetHeightForWidth(size.width, &minHeight, &maxHeight, NULL);
953 info->min.height = max_c(info->min.height, minHeight);
954 info->max.height = min_c(info->max.height, maxHeight);
957 // layout the item
958 if (visible)
959 item->AlignInFrame(frame);
963 void
964 BSplitLayout::_LayoutItem(BLayoutItem* item, ItemLayoutInfo* info)
966 // update the visibility of the item
967 bool isVisible = item->IsVisible();
968 bool visibilityChanged = (info->isVisible != isVisible);
969 if (visibilityChanged)
970 item->SetVisible(info->isVisible);
972 // nothing more to do, if the item is not visible
973 if (!info->isVisible)
974 return;
976 item->AlignInFrame(info->layoutFrame);
978 // if the item became visible, we need to update its internal layout
979 if (visibilityChanged &&
980 (fOrientation != B_HORIZONTAL || !HasHeightForWidth())) {
981 item->Relayout(true);
986 bool
987 BSplitLayout::_SetSplitterValue(int32 index, int32 value)
989 // if both items are collapsed, nothing can be dragged
990 BLayoutItem* previousItem = ItemAt(index);
991 BLayoutItem* nextItem = ItemAt(index + 1);
992 ItemLayoutInfo* previousInfo = _ItemLayoutInfo(previousItem);
993 ItemLayoutInfo* nextInfo = _ItemLayoutInfo(nextItem);
994 ItemLayoutInfo* splitterInfo = _ItemLayoutInfo(_SplitterItemAt(index));
995 bool previousVisible = previousInfo->isVisible;
996 bool nextVisible = nextInfo->isVisible;
997 if (!previousVisible && !nextVisible)
998 return false;
1000 ValueRange range;
1001 _GetSplitterValueRange(index, range);
1003 value = max_c(min_c(value, range.sumValue), -(int32)fSpacing);
1005 int32 previousSize = value - (int32)fSpacing;
1006 int32 nextSize = range.sumValue - value - (int32)fSpacing;
1008 // Note: While this collapsed-check is mathmatically correct (i.e. we
1009 // collapse an item, if it would become smaller than half its minimum
1010 // size), we might want to change it, since for the user it looks like
1011 // collapsing happens earlier. The reason being that the only visual mark
1012 // the user has is the mouse cursor which indeed hasn't crossed the middle
1013 // of the item yet.
1014 bool previousCollapsed = (previousSize <= range.previousMin / 2)
1015 && previousInfo->isCollapsible;
1016 bool nextCollapsed = (nextSize <= range.nextMin / 2)
1017 && nextInfo->isCollapsible;
1018 if (previousCollapsed && nextCollapsed) {
1019 // we cannot collapse both items; we have to decide for one
1020 if (previousSize < nextSize) {
1021 // collapse previous
1022 nextCollapsed = false;
1023 nextSize = range.sumValue - (int32)fSpacing;
1024 } else {
1025 // collapse next
1026 previousCollapsed = false;
1027 previousSize = range.sumValue - (int32)fSpacing;
1031 if (previousCollapsed || nextCollapsed) {
1032 // one collapsed item -- check whether that violates the constraints
1033 // of the other one
1034 int32 availableSpace = range.sumValue - (int32)fSpacing;
1035 if (previousCollapsed) {
1036 if (availableSpace < range.nextMin
1037 || availableSpace > range.nextMax) {
1038 // we cannot collapse the previous item
1039 previousCollapsed = false;
1041 } else {
1042 if (availableSpace < range.previousMin
1043 || availableSpace > range.previousMax) {
1044 // we cannot collapse the next item
1045 nextCollapsed = false;
1050 if (!(previousCollapsed || nextCollapsed)) {
1051 // no collapsed item -- check whether there is a close solution
1052 previousSize = value - (int32)fSpacing;
1053 nextSize = range.sumValue - value - (int32)fSpacing;
1055 if (range.previousMin + range.nextMin + 2 * fSpacing > range.sumValue) {
1056 // we don't have enough space to uncollapse both items
1057 int32 availableSpace = range.sumValue - (int32)fSpacing;
1058 if (previousSize < nextSize && availableSpace >= range.nextMin
1059 && availableSpace <= range.nextMax
1060 && previousInfo->isCollapsible) {
1061 previousCollapsed = true;
1062 } else if (availableSpace >= range.previousMin
1063 && availableSpace <= range.previousMax
1064 && nextInfo->isCollapsible) {
1065 nextCollapsed = true;
1066 } else if (availableSpace >= range.nextMin
1067 && availableSpace <= range.nextMax
1068 && previousInfo->isCollapsible) {
1069 previousCollapsed = true;
1070 } else {
1071 if (previousSize < nextSize && previousInfo->isCollapsible) {
1072 previousCollapsed = true;
1073 } else if (nextInfo->isCollapsible) {
1074 nextCollapsed = true;
1075 } else {
1076 // Neither item is collapsible although there's not enough
1077 // space: Give them both their minimum size.
1078 previousSize = range.previousMin;
1079 nextSize = range.nextMin;
1083 } else {
1084 // there is enough space for both items
1085 // make sure the min constraints are satisfied
1086 if (previousSize < range.previousMin) {
1087 previousSize = range.previousMin;
1088 nextSize = range.sumValue - previousSize - 2 * (int32)fSpacing;
1089 } else if (nextSize < range.nextMin) {
1090 nextSize = range.nextMin;
1091 previousSize = range.sumValue - nextSize - 2 * (int32)fSpacing;
1094 // if we can, also satisfy the max constraints
1095 if (range.previousMax + range.nextMax + 2 * (int32)fSpacing
1096 >= range.sumValue) {
1097 if (previousSize > range.previousMax) {
1098 previousSize = range.previousMax;
1099 nextSize = range.sumValue - previousSize
1100 - 2 * (int32)fSpacing;
1101 } else if (nextSize > range.nextMax) {
1102 nextSize = range.nextMax;
1103 previousSize = range.sumValue - nextSize
1104 - 2 * (int32)fSpacing;
1110 // compute the size for one collapsed item; for none collapsed item we
1111 // already have correct values
1112 if (previousCollapsed || nextCollapsed) {
1113 int32 availableSpace = range.sumValue - (int32)fSpacing;
1114 if (previousCollapsed) {
1115 previousSize = 0;
1116 nextSize = availableSpace;
1117 } else {
1118 previousSize = availableSpace;
1119 nextSize = 0;
1123 int32 newValue = previousSize + (previousCollapsed ? 0 : (int32)fSpacing);
1124 if (newValue == fDraggingCurrentValue) {
1125 // nothing changed
1126 return false;
1129 // something changed: we need to recompute the layout
1130 int32 baseOffset = -fDraggingCurrentValue;
1131 // offset to the current splitter position
1132 int32 splitterOffset = baseOffset + newValue;
1133 int32 nextOffset = splitterOffset + (int32)fSplitterSize + (int32)fSpacing;
1135 BRect splitterFrame(splitterInfo->layoutFrame);
1136 if (fOrientation == B_HORIZONTAL) {
1137 // horizontal layout
1138 // previous item
1139 float left = splitterFrame.left + baseOffset;
1140 previousInfo->layoutFrame.Set(
1141 left,
1142 splitterFrame.top,
1143 left + previousSize - 1,
1144 splitterFrame.bottom);
1146 // next item
1147 left = splitterFrame.left + nextOffset;
1148 nextInfo->layoutFrame.Set(
1149 left,
1150 splitterFrame.top,
1151 left + nextSize - 1,
1152 splitterFrame.bottom);
1154 // splitter
1155 splitterInfo->layoutFrame.left += splitterOffset;
1156 splitterInfo->layoutFrame.right += splitterOffset;
1157 } else {
1158 // vertical layout
1159 // previous item
1160 float top = splitterFrame.top + baseOffset;
1161 previousInfo->layoutFrame.Set(
1162 splitterFrame.left,
1163 top,
1164 splitterFrame.right,
1165 top + previousSize - 1);
1167 // next item
1168 top = splitterFrame.top + nextOffset;
1169 nextInfo->layoutFrame.Set(
1170 splitterFrame.left,
1171 top,
1172 splitterFrame.right,
1173 top + nextSize - 1);
1175 // splitter
1176 splitterInfo->layoutFrame.top += splitterOffset;
1177 splitterInfo->layoutFrame.bottom += splitterOffset;
1180 previousInfo->isVisible = !previousCollapsed;
1181 nextInfo->isVisible = !nextCollapsed;
1183 bool heightForWidth = (fOrientation == B_HORIZONTAL && HasHeightForWidth());
1185 // If the item visibility is to be changed, we need to update the splitter
1186 // values now, since the visibility change will cause an invalidation.
1187 if (previousVisible != previousInfo->isVisible
1188 || nextVisible != nextInfo->isVisible || heightForWidth) {
1189 _UpdateSplitterWeights();
1192 // If we have height for width items, we need to invalidate the previous
1193 // and the next item. Actually we would only need to invalidate height for
1194 // width items, but since non height for width items might be aligned with
1195 // height for width items, we need to trigger a layout that creates a
1196 // context that spans all aligned items.
1197 // We invalidate already here, so that changing the items' size won't cause
1198 // an immediate relayout.
1199 if (heightForWidth) {
1200 previousItem->InvalidateLayout();
1201 nextItem->InvalidateLayout();
1204 // do the layout
1205 _LayoutItem(previousItem, previousInfo);
1206 _LayoutItem(_SplitterItemAt(index), splitterInfo);
1207 _LayoutItem(nextItem, nextInfo);
1209 fDraggingCurrentValue = newValue;
1211 return true;
1215 BSplitLayout::ItemLayoutInfo*
1216 BSplitLayout::_ItemLayoutInfo(BLayoutItem* item) const
1218 return (ItemLayoutInfo*)item->LayoutData();
1222 void
1223 BSplitLayout::_UpdateSplitterWeights()
1225 int32 count = CountItems();
1226 for (int32 i = 0; i < count; i++) {
1227 float weight;
1228 if (fOrientation == B_HORIZONTAL)
1229 weight = _ItemLayoutInfo(ItemAt(i))->layoutFrame.Width() + 1;
1230 else
1231 weight = _ItemLayoutInfo(ItemAt(i))->layoutFrame.Height() + 1;
1233 SetItemWeight(i, weight, false);
1236 // Just updating the splitter weights is fine in principle. The next
1237 // LayoutItems() will use the correct values. But, if our orientation is
1238 // vertical, the cached height for width info needs to be flushed, or the
1239 // obsolete cached values will be used.
1240 if (fOrientation == B_VERTICAL)
1241 _InvalidateCachedHeightForWidth();
1245 void
1246 BSplitLayout::_ValidateMinMax()
1248 if (fHorizontalLayouter != NULL)
1249 return;
1251 fLayoutValid = false;
1253 fVisibleItems.MakeEmpty();
1254 fHeightForWidthItems.MakeEmpty();
1256 _InvalidateCachedHeightForWidth();
1258 // filter the visible items
1259 int32 itemCount = CountItems();
1260 for (int32 i = 0; i < itemCount; i++) {
1261 BLayoutItem* item = ItemAt(i);
1262 if (item->IsVisible())
1263 fVisibleItems.AddItem(item);
1265 // Add "height for width" items even, if they aren't visible. Otherwise
1266 // we may get our parent into trouble, since we could change from
1267 // "height for width" to "not height for width".
1268 if (item->HasHeightForWidth())
1269 fHeightForWidthItems.AddItem(item);
1271 itemCount = fVisibleItems.CountItems();
1273 // create the layouters
1274 Layouter* itemLayouter = new SimpleLayouter(itemCount, 0);
1276 if (fOrientation == B_HORIZONTAL) {
1277 fHorizontalLayouter = itemLayouter;
1278 fVerticalLayouter = new OneElementLayouter();
1279 } else {
1280 fHorizontalLayouter = new OneElementLayouter();
1281 fVerticalLayouter = itemLayouter;
1284 // tell the layouters about our constraints
1285 if (itemCount > 0) {
1286 for (int32 i = 0; i < itemCount; i++) {
1287 BLayoutItem* item = (BLayoutItem*)fVisibleItems.ItemAt(i);
1288 BSize min = item->MinSize();
1289 BSize max = item->MaxSize();
1290 BSize preferred = item->PreferredSize();
1292 fHorizontalLayouter->AddConstraints(i, 1, min.width, max.width,
1293 preferred.width);
1294 fVerticalLayouter->AddConstraints(i, 1, min.height, max.height,
1295 preferred.height);
1297 float weight = ItemWeight(item);
1298 fHorizontalLayouter->SetWeight(i, weight);
1299 fVerticalLayouter->SetWeight(i, weight);
1303 fMin.width = fHorizontalLayouter->MinSize();
1304 fMin.height = fVerticalLayouter->MinSize();
1305 fMax.width = fHorizontalLayouter->MaxSize();
1306 fMax.height = fVerticalLayouter->MaxSize();
1307 fPreferred.width = fHorizontalLayouter->PreferredSize();
1308 fPreferred.height = fVerticalLayouter->PreferredSize();
1310 fHorizontalLayoutInfo = fHorizontalLayouter->CreateLayoutInfo();
1311 if (fHeightForWidthItems.IsEmpty())
1312 fVerticalLayoutInfo = fVerticalLayouter->CreateLayoutInfo();
1314 ResetLayoutInvalidation();
1318 void
1319 BSplitLayout::_InternalGetHeightForWidth(float width, bool realLayout,
1320 float* minHeight, float* maxHeight, float* preferredHeight)
1322 if ((realLayout && fHeightForWidthVerticalLayouterWidth != width)
1323 || (!realLayout && fCachedHeightForWidthWidth != width)) {
1324 // The general strategy is to clone the vertical layouter, which only
1325 // knows the general min/max constraints, do a horizontal layout for the
1326 // given width, and add the children's height for width constraints to
1327 // the cloned vertical layouter. If this method is invoked internally,
1328 // we keep the cloned vertical layouter, for it will be used for doing
1329 // the layout. Otherwise we just drop it after we've got the height for
1330 // width info.
1332 // clone the vertical layouter and get the horizontal layout info to be used
1333 LayoutInfo* horizontalLayoutInfo = NULL;
1334 Layouter* verticalLayouter = fVerticalLayouter->CloneLayouter();
1335 if (realLayout) {
1336 horizontalLayoutInfo = fHorizontalLayoutInfo;
1337 delete fHeightForWidthVerticalLayouter;
1338 fHeightForWidthVerticalLayouter = verticalLayouter;
1339 delete fVerticalLayoutInfo;
1340 fVerticalLayoutInfo = verticalLayouter->CreateLayoutInfo();
1341 fHeightForWidthVerticalLayouterWidth = width;
1342 } else {
1343 if (fHeightForWidthHorizontalLayoutInfo == NULL) {
1344 delete fHeightForWidthHorizontalLayoutInfo;
1345 fHeightForWidthHorizontalLayoutInfo
1346 = fHorizontalLayouter->CreateLayoutInfo();
1348 horizontalLayoutInfo = fHeightForWidthHorizontalLayoutInfo;
1351 // do the horizontal layout (already done when doing this for the real
1352 // layout)
1353 if (!realLayout)
1354 fHorizontalLayouter->Layout(horizontalLayoutInfo, width);
1356 // add the children's height for width constraints
1357 int32 count = fHeightForWidthItems.CountItems();
1358 for (int32 i = 0; i < count; i++) {
1359 BLayoutItem* item = (BLayoutItem*)fHeightForWidthItems.ItemAt(i);
1360 int32 index = fVisibleItems.IndexOf(item);
1361 if (index >= 0) {
1362 float itemMinHeight, itemMaxHeight, itemPreferredHeight;
1363 item->GetHeightForWidth(
1364 horizontalLayoutInfo->ElementSize(index),
1365 &itemMinHeight, &itemMaxHeight, &itemPreferredHeight);
1366 verticalLayouter->AddConstraints(index, 1, itemMinHeight,
1367 itemMaxHeight, itemPreferredHeight);
1371 // get the height for width info
1372 fCachedHeightForWidthWidth = width;
1373 fCachedMinHeightForWidth = verticalLayouter->MinSize();
1374 fCachedMaxHeightForWidth = verticalLayouter->MaxSize();
1375 fCachedPreferredHeightForWidth = verticalLayouter->PreferredSize();
1378 if (minHeight)
1379 *minHeight = fCachedMinHeightForWidth;
1380 if (maxHeight)
1381 *maxHeight = fCachedMaxHeightForWidth;
1382 if (preferredHeight)
1383 *preferredHeight = fCachedPreferredHeightForWidth;
1387 float
1388 BSplitLayout::_SplitterSpace() const
1390 int32 splitters = fSplitterItems.CountItems();
1391 float space = 0;
1392 if (splitters > 0) {
1393 space = (fVisibleItems.CountItems() + splitters - 1) * fSpacing
1394 + splitters * fSplitterSize;
1397 return space;
1401 BSize
1402 BSplitLayout::_AddInsets(BSize size)
1404 size.width = BLayoutUtils::AddDistances(size.width,
1405 fLeftInset + fRightInset - 1);
1406 size.height = BLayoutUtils::AddDistances(size.height,
1407 fTopInset + fBottomInset - 1);
1409 float spacing = _SplitterSpace();
1410 if (fOrientation == B_HORIZONTAL)
1411 size.width = BLayoutUtils::AddDistances(size.width, spacing - 1);
1412 else
1413 size.height = BLayoutUtils::AddDistances(size.height, spacing - 1);
1415 return size;
1419 void
1420 BSplitLayout::_AddInsets(float* minHeight, float* maxHeight,
1421 float* preferredHeight)
1423 float insets = fTopInset + fBottomInset - 1;
1424 if (fOrientation == B_VERTICAL)
1425 insets += _SplitterSpace();
1426 if (minHeight)
1427 *minHeight = BLayoutUtils::AddDistances(*minHeight, insets);
1428 if (maxHeight)
1429 *maxHeight = BLayoutUtils::AddDistances(*maxHeight, insets);
1430 if (preferredHeight)
1431 *preferredHeight = BLayoutUtils::AddDistances(*preferredHeight, insets);
1435 BSize
1436 BSplitLayout::_SubtractInsets(BSize size)
1438 size.width = BLayoutUtils::SubtractDistances(size.width,
1439 fLeftInset + fRightInset - 1);
1440 size.height = BLayoutUtils::SubtractDistances(size.height,
1441 fTopInset + fBottomInset - 1);
1443 float spacing = _SplitterSpace();
1444 if (fOrientation == B_HORIZONTAL)
1445 size.width = BLayoutUtils::SubtractDistances(size.width, spacing - 1);
1446 else
1447 size.height = BLayoutUtils::SubtractDistances(size.height, spacing - 1);
1449 return size;