* Copyright 2006-2009, Ingo Weinhold <ingo_weinhold@gmx.de>.
* Copyright 2015, Rene Gollent, rene@gollent.com.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "SplitLayout.h"
#include <new>
#include <stdio.h>
#include <ControlLook.h>
#include <LayoutItem.h>
#include <LayoutUtils.h>
#include <Message.h>
#include <View.h>
#include "OneElementLayouter.h"
#include "SimpleLayouter.h"
using std::nothrow;
namespace {
const char* const kItemCollapsibleField = "BSplitLayout:item:collapsible";
const char* const kItemWeightField = "BSplitLayout:item:weight";
const char* const kSpacingField = "BSplitLayout:spacing";
const char* const kSplitterSizeField = "BSplitLayout:splitterSize";
const char* const kIsVerticalField = "BSplitLayout:vertical";
const char* const kInsetsField = "BSplitLayout:insets";
}
class BSplitLayout::ItemLayoutInfo {
public:
float weight;
BRect layoutFrame;
BSize min;
BSize max;
bool isVisible;
bool isCollapsible;
ItemLayoutInfo()
:
weight(1.0f),
layoutFrame(0, 0, -1, -1),
min(),
max(),
isVisible(true),
isCollapsible(true)
{
}
};
class BSplitLayout::ValueRange {
public:
int32 sumValue;
int32 previousMin;
int32 previousMax;
int32 previousSize;
int32 nextMin;
int32 nextMax;
int32 nextSize;
};
class BSplitLayout::SplitterItem : public BLayoutItem {
public:
SplitterItem(BSplitLayout* layout)
:
fLayout(layout),
fFrame()
{
}
virtual BSize MinSize()
{
if (fLayout->Orientation() == B_HORIZONTAL)
return BSize(fLayout->SplitterSize() - 1, -1);
else
return BSize(-1, fLayout->SplitterSize() - 1);
}
virtual BSize MaxSize()
{
if (fLayout->Orientation() == B_HORIZONTAL)
return BSize(fLayout->SplitterSize() - 1, B_SIZE_UNLIMITED);
else
return BSize(B_SIZE_UNLIMITED, fLayout->SplitterSize() - 1);
}
virtual BSize PreferredSize()
{
return MinSize();
}
virtual BAlignment Alignment()
{
return BAlignment(B_ALIGN_HORIZONTAL_CENTER, B_ALIGN_VERTICAL_CENTER);
}
virtual void SetExplicitMinSize(BSize size)
{
}
virtual void SetExplicitMaxSize(BSize size)
{
}
virtual void SetExplicitPreferredSize(BSize size)
{
}
virtual void SetExplicitAlignment(BAlignment alignment)
{
}
virtual bool IsVisible()
{
return true;
}
virtual void SetVisible(bool visible)
{
}
virtual BRect Frame()
{
return fFrame;
}
virtual void SetFrame(BRect frame)
{
fFrame = frame;
}
private:
BSplitLayout* fLayout;
BRect fFrame;
};
BSplitLayout::BSplitLayout(orientation orientation, float spacing)
:
fOrientation(orientation),
fLeftInset(0),
fRightInset(0),
fTopInset(0),
fBottomInset(0),
fSplitterSize(6),
fSpacing(BControlLook::ComposeSpacing(spacing)),
fSplitterItems(),
fVisibleItems(),
fMin(),
fMax(),
fPreferred(),
fHorizontalLayouter(NULL),
fVerticalLayouter(NULL),
fHorizontalLayoutInfo(NULL),
fVerticalLayoutInfo(NULL),
fHeightForWidthItems(),
fHeightForWidthVerticalLayouter(NULL),
fHeightForWidthHorizontalLayoutInfo(NULL),
fLayoutValid(false),
fCachedHeightForWidthWidth(-2),
fHeightForWidthVerticalLayouterWidth(-2),
fCachedMinHeightForWidth(-1),
fCachedMaxHeightForWidth(-1),
fCachedPreferredHeightForWidth(-1),
fDraggingStartPoint(),
fDraggingStartValue(0),
fDraggingCurrentValue(0),
fDraggingSplitterIndex(-1)
{
}
BSplitLayout::BSplitLayout(BMessage* from)
:
BAbstractLayout(BUnarchiver::PrepareArchive(from)),
fOrientation(B_HORIZONTAL),
fLeftInset(0),
fRightInset(0),
fTopInset(0),
fBottomInset(0),
fSplitterSize(6),
fSpacing(be_control_look->DefaultItemSpacing()),
fSplitterItems(),
fVisibleItems(),
fMin(),
fMax(),
fPreferred(),
fHorizontalLayouter(NULL),
fVerticalLayouter(NULL),
fHorizontalLayoutInfo(NULL),
fVerticalLayoutInfo(NULL),
fHeightForWidthItems(),
fHeightForWidthVerticalLayouter(NULL),
fHeightForWidthHorizontalLayoutInfo(NULL),
fLayoutValid(false),
fCachedHeightForWidthWidth(-2),
fHeightForWidthVerticalLayouterWidth(-2),
fCachedMinHeightForWidth(-1),
fCachedMaxHeightForWidth(-1),
fCachedPreferredHeightForWidth(-1),
fDraggingStartPoint(),
fDraggingStartValue(0),
fDraggingCurrentValue(0),
fDraggingSplitterIndex(-1)
{
BUnarchiver unarchiver(from);
bool isVertical;
status_t err = from->FindBool(kIsVerticalField, &isVertical);
if (err != B_OK) {
unarchiver.Finish(err);
return;
}
fOrientation = (isVertical) ? B_VERTICAL : B_HORIZONTAL ;
BRect insets;
err = from->FindRect(kInsetsField, &insets);
if (err != B_OK) {
unarchiver.Finish(err);
return;
}
SetInsets(insets.left, insets.top, insets.right, insets.bottom);
err = from->FindFloat(kSplitterSizeField, &fSplitterSize);
if (err == B_OK)
err = from->FindFloat(kSpacingField, &fSpacing);
unarchiver.Finish(err);
}
BSplitLayout::~BSplitLayout()
{
}
void
BSplitLayout::SetInsets(float left, float top, float right, float bottom)
{
fLeftInset = left;
fTopInset = top;
fRightInset = right;
fBottomInset = bottom;
InvalidateLayout();
}
void
BSplitLayout::GetInsets(float* left, float* top, float* right,
float* bottom) const
{
if (left)
*left = fLeftInset;
if (top)
*top = fTopInset;
if (right)
*right = fRightInset;
if (bottom)
*bottom = fBottomInset;
}
float
BSplitLayout::Spacing() const
{
return fSpacing;
}
void
BSplitLayout::SetSpacing(float spacing)
{
spacing = BControlLook::ComposeSpacing(spacing);
if (spacing != fSpacing) {
fSpacing = spacing;
InvalidateLayout();
}
}
orientation
BSplitLayout::Orientation() const
{
return fOrientation;
}
void
BSplitLayout::SetOrientation(orientation orientation)
{
if (orientation != fOrientation) {
fOrientation = orientation;
InvalidateLayout();
}
}
float
BSplitLayout::SplitterSize() const
{
return fSplitterSize;
}
void
BSplitLayout::SetSplitterSize(float size)
{
if (size != fSplitterSize) {
fSplitterSize = size;
InvalidateLayout();
}
}
BLayoutItem*
BSplitLayout::AddView(BView* child)
{
return BAbstractLayout::AddView(child);
}
BLayoutItem*
BSplitLayout::AddView(int32 index, BView* child)
{
return BAbstractLayout::AddView(index, child);
}
BLayoutItem*
BSplitLayout::AddView(BView* child, float weight)
{
return AddView(-1, child, weight);
}
BLayoutItem*
BSplitLayout::AddView(int32 index, BView* child, float weight)
{
BLayoutItem* item = AddView(index, child);
if (item)
SetItemWeight(item, weight);
return item;
}
bool
BSplitLayout::AddItem(BLayoutItem* item)
{
return BAbstractLayout::AddItem(item);
}
bool
BSplitLayout::AddItem(int32 index, BLayoutItem* item)
{
return BAbstractLayout::AddItem(index, item);
}
bool
BSplitLayout::AddItem(BLayoutItem* item, float weight)
{
return AddItem(-1, item, weight);
}
bool
BSplitLayout::AddItem(int32 index, BLayoutItem* item, float weight)
{
bool success = AddItem(index, item);
if (success)
SetItemWeight(item, weight);
return success;
}
float
BSplitLayout::ItemWeight(int32 index) const
{
if (index < 0 || index >= CountItems())
return 0;
return ItemWeight(ItemAt(index));
}
float
BSplitLayout::ItemWeight(BLayoutItem* item) const
{
if (ItemLayoutInfo* info = _ItemLayoutInfo(item))
return info->weight;
return 0;
}
void
BSplitLayout::SetItemWeight(int32 index, float weight, bool invalidateLayout)
{
if (index < 0 || index >= CountItems())
return;
BLayoutItem* item = ItemAt(index);
SetItemWeight(item, weight);
if (fHorizontalLayouter) {
int32 visibleIndex = fVisibleItems.IndexOf(item);
if (visibleIndex >= 0) {
if (fOrientation == B_HORIZONTAL)
fHorizontalLayouter->SetWeight(visibleIndex, weight);
else
fVerticalLayouter->SetWeight(visibleIndex, weight);
}
}
if (invalidateLayout)
InvalidateLayout();
}
void
BSplitLayout::SetItemWeight(BLayoutItem* item, float weight)
{
if (ItemLayoutInfo* info = _ItemLayoutInfo(item))
info->weight = weight;
}
bool
BSplitLayout::IsCollapsible(int32 index) const
{
return _ItemLayoutInfo(ItemAt(index))->isCollapsible;
}
void
BSplitLayout::SetCollapsible(bool collapsible)
{
SetCollapsible(0, CountItems() - 1, collapsible);
}
void
BSplitLayout::SetCollapsible(int32 index, bool collapsible)
{
SetCollapsible(index, index, collapsible);
}
void
BSplitLayout::SetCollapsible(int32 first, int32 last, bool collapsible)
{
for (int32 i = first; i <= last; i++)
_ItemLayoutInfo(ItemAt(i))->isCollapsible = collapsible;
}
bool
BSplitLayout::IsItemCollapsed(int32 index) const
{
return !_ItemLayoutInfo(ItemAt(index))->isVisible;
}
void
BSplitLayout::SetItemCollapsed(int32 index, bool collapsed)
{
ItemAt(index)->SetVisible(!collapsed);
InvalidateLayout(true);
}
BSize
BSplitLayout::BaseMinSize()
{
_ValidateMinMax();
return _AddInsets(fMin);
}
BSize
BSplitLayout::BaseMaxSize()
{
_ValidateMinMax();
return _AddInsets(fMax);
}
BSize
BSplitLayout::BasePreferredSize()
{
_ValidateMinMax();
return _AddInsets(fPreferred);
}
BAlignment
BSplitLayout::BaseAlignment()
{
return BAbstractLayout::BaseAlignment();
}
bool
BSplitLayout::HasHeightForWidth()
{
_ValidateMinMax();
return !fHeightForWidthItems.IsEmpty();
}
void
BSplitLayout::GetHeightForWidth(float width, float* min, float* max,
float* preferred)
{
if (!HasHeightForWidth())
return;
float innerWidth = _SubtractInsets(BSize(width, 0)).width;
_InternalGetHeightForWidth(innerWidth, false, min, max, preferred);
_AddInsets(min, max, preferred);
}
void
BSplitLayout::LayoutInvalidated(bool children)
{
delete fHorizontalLayouter;
delete fVerticalLayouter;
delete fHorizontalLayoutInfo;
delete fVerticalLayoutInfo;
fHorizontalLayouter = NULL;
fVerticalLayouter = NULL;
fHorizontalLayoutInfo = NULL;
fVerticalLayoutInfo = NULL;
_InvalidateCachedHeightForWidth();
fLayoutValid = false;
}
void
BSplitLayout::DoLayout()
{
_ValidateMinMax();
BSize size = _SubtractInsets(LayoutArea().Size());
fHorizontalLayouter->Layout(fHorizontalLayoutInfo, size.width);
Layouter* verticalLayouter;
if (HasHeightForWidth()) {
float minHeight, maxHeight, preferredHeight;
_InternalGetHeightForWidth(size.width, true, &minHeight, &maxHeight,
&preferredHeight);
size.height = max_c(size.height, minHeight);
verticalLayouter = fHeightForWidthVerticalLayouter;
} else
verticalLayouter = fVerticalLayouter;
verticalLayouter->Layout(fVerticalLayoutInfo, size.height);
float xOffset = fLeftInset;
float yOffset = fTopInset;
float splitterWidth = 0;
float splitterHeight = 0;
float xSpacing = 0;
float ySpacing = 0;
if (fOrientation == B_HORIZONTAL) {
splitterWidth = fSplitterSize;
splitterHeight = size.height + 1;
xSpacing = fSpacing;
} else {
splitterWidth = size.width + 1;
splitterHeight = fSplitterSize;
ySpacing = fSpacing;
}
int itemCount = CountItems();
for (int i = 0; i < itemCount; i++) {
if (i > 0) {
SplitterItem* splitterItem = _SplitterItemAt(i - 1);
_LayoutItem(splitterItem, BRect(xOffset, yOffset,
xOffset + splitterWidth - 1, yOffset + splitterHeight - 1),
true);
if (fOrientation == B_HORIZONTAL)
xOffset += splitterWidth + xSpacing;
else
yOffset += splitterHeight + ySpacing;
}
BLayoutItem* item = ItemAt(i);
int32 visibleIndex = fVisibleItems.IndexOf(item);
if (visibleIndex < 0) {
_LayoutItem(item, BRect(), false);
continue;
}
float width = fHorizontalLayoutInfo->ElementSize(visibleIndex);
float height = fVerticalLayoutInfo->ElementSize(visibleIndex);
_LayoutItem(item, BRect(xOffset, yOffset, xOffset + width,
yOffset + height), true);
if (fOrientation == B_HORIZONTAL)
xOffset += width + xSpacing + 1;
else
yOffset += height + ySpacing + 1;
}
fLayoutValid = true;
}
BRect
BSplitLayout::SplitterItemFrame(int32 index) const
{
if (SplitterItem* item = _SplitterItemAt(index))
return item->Frame();
return BRect();
}
bool
BSplitLayout::IsAboveSplitter(const BPoint& point) const
{
return _SplitterItemAt(point) != NULL;
}
bool
BSplitLayout::StartDraggingSplitter(BPoint point)
{
StopDraggingSplitter();
if (!fLayoutValid)
return false;
BSize size = _SubtractInsets(LayoutArea().Size());
if ((fOrientation == B_HORIZONTAL && size.width >= fMax.width)
|| (fOrientation == B_VERTICAL && size.height >= fMax.height)) {
return false;
}
int32 index = -1;
if (_SplitterItemAt(point, &index) != NULL) {
fDraggingStartPoint = Owner()->ConvertToScreen(point);
fDraggingStartValue = _SplitterValue(index);
fDraggingCurrentValue = fDraggingStartValue;
fDraggingSplitterIndex = index;
return true;
}
return false;
}
bool
BSplitLayout::DragSplitter(BPoint point)
{
if (fDraggingSplitterIndex < 0)
return false;
point = Owner()->ConvertToScreen(point);
int32 valueDiff;
if (fOrientation == B_HORIZONTAL)
valueDiff = int32(point.x - fDraggingStartPoint.x);
else
valueDiff = int32(point.y - fDraggingStartPoint.y);
return _SetSplitterValue(fDraggingSplitterIndex,
fDraggingStartValue + valueDiff);
}
bool
BSplitLayout::StopDraggingSplitter()
{
if (fDraggingSplitterIndex < 0)
return false;
_UpdateSplitterWeights();
fDraggingSplitterIndex = -1;
return true;
}
int32
BSplitLayout::DraggedSplitter() const
{
return fDraggingSplitterIndex;
}
status_t
BSplitLayout::Archive(BMessage* into, bool deep) const
{
BArchiver archiver(into);
status_t err = BAbstractLayout::Archive(into, deep);
if (err == B_OK)
err = into->AddBool(kIsVerticalField, fOrientation == B_VERTICAL);
if (err == B_OK) {
BRect insets(fLeftInset, fTopInset, fRightInset, fBottomInset);
err = into->AddRect(kInsetsField, insets);
}
if (err == B_OK)
err = into->AddFloat(kSplitterSizeField, fSplitterSize);
if (err == B_OK)
err = into->AddFloat(kSpacingField, fSpacing);
return archiver.Finish(err);
}
BArchivable*
BSplitLayout::Instantiate(BMessage* from)
{
if (validate_instantiation(from, "BSplitLayout"))
return new(std::nothrow) BSplitLayout(from);
return NULL;
}
status_t
BSplitLayout::ItemArchived(BMessage* into, BLayoutItem* item, int32 index) const
{
ItemLayoutInfo* info = _ItemLayoutInfo(item);
status_t err = into->AddFloat(kItemWeightField, info->weight);
if (err == B_OK)
err = into->AddBool(kItemCollapsibleField, info->isCollapsible);
return err;
}
status_t
BSplitLayout::ItemUnarchived(const BMessage* from,
BLayoutItem* item, int32 index)
{
ItemLayoutInfo* info = _ItemLayoutInfo(item);
status_t err = from->FindFloat(kItemWeightField, index, &info->weight);
if (err == B_OK) {
bool* collapsible = &info->isCollapsible;
err = from->FindBool(kItemCollapsibleField, index, collapsible);
}
return err;
}
bool
BSplitLayout::ItemAdded(BLayoutItem* item, int32 atIndex)
{
ItemLayoutInfo* itemInfo = new(nothrow) ItemLayoutInfo();
if (!itemInfo)
return false;
if (CountItems() > 1) {
SplitterItem* splitter = new(nothrow) SplitterItem(this);
ItemLayoutInfo* splitterInfo = new(nothrow) ItemLayoutInfo();
if (!splitter || !splitterInfo || !fSplitterItems.AddItem(splitter)) {
delete itemInfo;
delete splitter;
delete splitterInfo;
return false;
}
splitter->SetLayoutData(splitterInfo);
SetItemWeight(splitter, 0);
}
item->SetLayoutData(itemInfo);
SetItemWeight(item, 1);
return true;
}
void
BSplitLayout::ItemRemoved(BLayoutItem* item, int32 atIndex)
{
if (fSplitterItems.CountItems() > 0) {
SplitterItem* splitterItem = (SplitterItem*)fSplitterItems.RemoveItem(
fSplitterItems.CountItems() - 1);
delete _ItemLayoutInfo(splitterItem);
delete splitterItem;
}
delete _ItemLayoutInfo(item);
item->SetLayoutData(NULL);
}
void
BSplitLayout::_InvalidateCachedHeightForWidth()
{
delete fHeightForWidthVerticalLayouter;
delete fHeightForWidthHorizontalLayoutInfo;
fHeightForWidthVerticalLayouter = NULL;
fHeightForWidthHorizontalLayoutInfo = NULL;
fCachedHeightForWidthWidth = -2;
fHeightForWidthVerticalLayouterWidth = -2;
}
BSplitLayout::SplitterItem*
BSplitLayout::_SplitterItemAt(const BPoint& point, int32* index) const
{
int32 splitterCount = fSplitterItems.CountItems();
for (int32 i = 0; i < splitterCount; i++) {
SplitterItem* splitItem = _SplitterItemAt(i);
BRect frame = splitItem->Frame();
if (frame.Contains(point)) {
if (index != NULL)
*index = i;
return splitItem;
}
}
return NULL;
}
BSplitLayout::SplitterItem*
BSplitLayout::_SplitterItemAt(int32 index) const
{
return (SplitterItem*)fSplitterItems.ItemAt(index);
}
void
BSplitLayout::_GetSplitterValueRange(int32 index, ValueRange& range)
{
ItemLayoutInfo* previousInfo = _ItemLayoutInfo(ItemAt(index));
ItemLayoutInfo* nextInfo = _ItemLayoutInfo(ItemAt(index + 1));
if (fOrientation == B_HORIZONTAL) {
range.previousMin = (int32)previousInfo->min.width + 1;
range.previousMax = (int32)previousInfo->max.width + 1;
range.previousSize = previousInfo->layoutFrame.IntegerWidth() + 1;
range.nextMin = (int32)nextInfo->min.width + 1;
range.nextMax = (int32)nextInfo->max.width + 1;
range.nextSize = nextInfo->layoutFrame.IntegerWidth() + 1;
} else {
range.previousMin = (int32)previousInfo->min.height + 1;
range.previousMax = (int32)previousInfo->max.height + 1;
range.previousSize = previousInfo->layoutFrame.IntegerHeight() + 1;
range.nextMin = (int32)nextInfo->min.height + 1;
range.nextMax = (int32)nextInfo->max.height + 1;
range.nextSize = (int32)nextInfo->layoutFrame.IntegerHeight() + 1;
}
range.sumValue = range.previousSize + range.nextSize;
if (previousInfo->isVisible)
range.sumValue += (int32)fSpacing;
if (nextInfo->isVisible)
range.sumValue += (int32)fSpacing;
}
int32
BSplitLayout::_SplitterValue(int32 index) const
{
ItemLayoutInfo* info = _ItemLayoutInfo(ItemAt(index));
if (info && info->isVisible) {
if (fOrientation == B_HORIZONTAL)
return info->layoutFrame.IntegerWidth() + 1 + (int32)fSpacing;
else
return info->layoutFrame.IntegerHeight() + 1 + (int32)fSpacing;
} else
return 0;
}
void
BSplitLayout::_LayoutItem(BLayoutItem* item, BRect frame, bool visible)
{
ItemLayoutInfo* info = _ItemLayoutInfo(item);
info->isVisible = visible;
if (visible)
info->layoutFrame = frame;
else
info->layoutFrame = BRect(0, 0, -1, -1);
info->min = item->MinSize();
info->max = item->MaxSize();
if (item->HasHeightForWidth()) {
BSize size = _SubtractInsets(LayoutArea().Size());
float minHeight, maxHeight;
item->GetHeightForWidth(size.width, &minHeight, &maxHeight, NULL);
info->min.height = max_c(info->min.height, minHeight);
info->max.height = min_c(info->max.height, maxHeight);
}
if (visible)
item->AlignInFrame(frame);
}
void
BSplitLayout::_LayoutItem(BLayoutItem* item, ItemLayoutInfo* info)
{
bool isVisible = item->IsVisible();
bool visibilityChanged = (info->isVisible != isVisible);
if (visibilityChanged)
item->SetVisible(info->isVisible);
if (!info->isVisible)
return;
item->AlignInFrame(info->layoutFrame);
if (visibilityChanged &&
(fOrientation != B_HORIZONTAL || !HasHeightForWidth())) {
item->Relayout(true);
}
}
bool
BSplitLayout::_SetSplitterValue(int32 index, int32 value)
{
BLayoutItem* previousItem = ItemAt(index);
BLayoutItem* nextItem = ItemAt(index + 1);
ItemLayoutInfo* previousInfo = _ItemLayoutInfo(previousItem);
ItemLayoutInfo* nextInfo = _ItemLayoutInfo(nextItem);
ItemLayoutInfo* splitterInfo = _ItemLayoutInfo(_SplitterItemAt(index));
bool previousVisible = previousInfo->isVisible;
bool nextVisible = nextInfo->isVisible;
if (!previousVisible && !nextVisible)
return false;
ValueRange range;
_GetSplitterValueRange(index, range);
value = max_c(min_c(value, range.sumValue), -(int32)fSpacing);
int32 previousSize = value - (int32)fSpacing;
int32 nextSize = range.sumValue - value - (int32)fSpacing;
bool previousCollapsed = (previousSize <= range.previousMin / 2)
&& previousInfo->isCollapsible;
bool nextCollapsed = (nextSize <= range.nextMin / 2)
&& nextInfo->isCollapsible;
if (previousCollapsed && nextCollapsed) {
if (previousSize < nextSize) {
nextCollapsed = false;
nextSize = range.sumValue - (int32)fSpacing;
} else {
previousCollapsed = false;
previousSize = range.sumValue - (int32)fSpacing;
}
}
if (previousCollapsed || nextCollapsed) {
int32 availableSpace = range.sumValue - (int32)fSpacing;
if (previousCollapsed) {
if (availableSpace < range.nextMin
|| availableSpace > range.nextMax) {
previousCollapsed = false;
}
} else {
if (availableSpace < range.previousMin
|| availableSpace > range.previousMax) {
nextCollapsed = false;
}
}
}
if (!(previousCollapsed || nextCollapsed)) {
previousSize = value - (int32)fSpacing;
nextSize = range.sumValue - value - (int32)fSpacing;
if (range.previousMin + range.nextMin + 2 * fSpacing > range.sumValue) {
int32 availableSpace = range.sumValue - (int32)fSpacing;
if (previousSize < nextSize && availableSpace >= range.nextMin
&& availableSpace <= range.nextMax
&& previousInfo->isCollapsible) {
previousCollapsed = true;
} else if (availableSpace >= range.previousMin
&& availableSpace <= range.previousMax
&& nextInfo->isCollapsible) {
nextCollapsed = true;
} else if (availableSpace >= range.nextMin
&& availableSpace <= range.nextMax
&& previousInfo->isCollapsible) {
previousCollapsed = true;
} else {
if (previousSize < nextSize && previousInfo->isCollapsible) {
previousCollapsed = true;
} else if (nextInfo->isCollapsible) {
nextCollapsed = true;
} else {
previousSize = range.previousMin;
nextSize = range.nextMin;
}
}
} else {
if (previousSize < range.previousMin) {
previousSize = range.previousMin;
nextSize = range.sumValue - previousSize - 2 * (int32)fSpacing;
} else if (nextSize < range.nextMin) {
nextSize = range.nextMin;
previousSize = range.sumValue - nextSize - 2 * (int32)fSpacing;
}
if (range.previousMax + range.nextMax + 2 * (int32)fSpacing
>= range.sumValue) {
if (previousSize > range.previousMax) {
previousSize = range.previousMax;
nextSize = range.sumValue - previousSize
- 2 * (int32)fSpacing;
} else if (nextSize > range.nextMax) {
nextSize = range.nextMax;
previousSize = range.sumValue - nextSize
- 2 * (int32)fSpacing;
}
}
}
}
if (previousCollapsed || nextCollapsed) {
int32 availableSpace = range.sumValue - (int32)fSpacing;
if (previousCollapsed) {
previousSize = 0;
nextSize = availableSpace;
} else {
previousSize = availableSpace;
nextSize = 0;
}
}
int32 newValue = previousSize + (previousCollapsed ? 0 : (int32)fSpacing);
if (newValue == fDraggingCurrentValue) {
return false;
}
int32 baseOffset = -fDraggingCurrentValue;
int32 splitterOffset = baseOffset + newValue;
int32 nextOffset = splitterOffset + (int32)fSplitterSize + (int32)fSpacing;
BRect splitterFrame(splitterInfo->layoutFrame);
if (fOrientation == B_HORIZONTAL) {
float left = splitterFrame.left + baseOffset;
previousInfo->layoutFrame.Set(
left,
splitterFrame.top,
left + previousSize - 1,
splitterFrame.bottom);
left = splitterFrame.left + nextOffset;
nextInfo->layoutFrame.Set(
left,
splitterFrame.top,
left + nextSize - 1,
splitterFrame.bottom);
splitterInfo->layoutFrame.left += splitterOffset;
splitterInfo->layoutFrame.right += splitterOffset;
} else {
float top = splitterFrame.top + baseOffset;
previousInfo->layoutFrame.Set(
splitterFrame.left,
top,
splitterFrame.right,
top + previousSize - 1);
top = splitterFrame.top + nextOffset;
nextInfo->layoutFrame.Set(
splitterFrame.left,
top,
splitterFrame.right,
top + nextSize - 1);
splitterInfo->layoutFrame.top += splitterOffset;
splitterInfo->layoutFrame.bottom += splitterOffset;
}
previousInfo->isVisible = !previousCollapsed;
nextInfo->isVisible = !nextCollapsed;
bool heightForWidth = (fOrientation == B_HORIZONTAL && HasHeightForWidth());
if (previousVisible != previousInfo->isVisible
|| nextVisible != nextInfo->isVisible || heightForWidth) {
_UpdateSplitterWeights();
}
if (heightForWidth) {
previousItem->InvalidateLayout();
nextItem->InvalidateLayout();
}
_LayoutItem(previousItem, previousInfo);
_LayoutItem(_SplitterItemAt(index), splitterInfo);
_LayoutItem(nextItem, nextInfo);
fDraggingCurrentValue = newValue;
return true;
}
BSplitLayout::ItemLayoutInfo*
BSplitLayout::_ItemLayoutInfo(BLayoutItem* item) const
{
return (ItemLayoutInfo*)item->LayoutData();
}
void
BSplitLayout::_UpdateSplitterWeights()
{
int32 count = CountItems();
for (int32 i = 0; i < count; i++) {
float weight;
if (fOrientation == B_HORIZONTAL)
weight = _ItemLayoutInfo(ItemAt(i))->layoutFrame.Width() + 1;
else
weight = _ItemLayoutInfo(ItemAt(i))->layoutFrame.Height() + 1;
SetItemWeight(i, weight, false);
}
if (fOrientation == B_VERTICAL)
_InvalidateCachedHeightForWidth();
}
void
BSplitLayout::_ValidateMinMax()
{
if (fHorizontalLayouter != NULL)
return;
fLayoutValid = false;
fVisibleItems.MakeEmpty();
fHeightForWidthItems.MakeEmpty();
_InvalidateCachedHeightForWidth();
int32 itemCount = CountItems();
for (int32 i = 0; i < itemCount; i++) {
BLayoutItem* item = ItemAt(i);
if (item->IsVisible())
fVisibleItems.AddItem(item);
if (item->HasHeightForWidth())
fHeightForWidthItems.AddItem(item);
}
itemCount = fVisibleItems.CountItems();
Layouter* itemLayouter = new SimpleLayouter(itemCount, 0);
if (fOrientation == B_HORIZONTAL) {
fHorizontalLayouter = itemLayouter;
fVerticalLayouter = new OneElementLayouter();
} else {
fHorizontalLayouter = new OneElementLayouter();
fVerticalLayouter = itemLayouter;
}
if (itemCount > 0) {
for (int32 i = 0; i < itemCount; i++) {
BLayoutItem* item = (BLayoutItem*)fVisibleItems.ItemAt(i);
BSize min = item->MinSize();
BSize max = item->MaxSize();
BSize preferred = item->PreferredSize();
fHorizontalLayouter->AddConstraints(i, 1, min.width, max.width,
preferred.width);
fVerticalLayouter->AddConstraints(i, 1, min.height, max.height,
preferred.height);
float weight = ItemWeight(item);
fHorizontalLayouter->SetWeight(i, weight);
fVerticalLayouter->SetWeight(i, weight);
}
}
fMin.width = fHorizontalLayouter->MinSize();
fMin.height = fVerticalLayouter->MinSize();
fMax.width = fHorizontalLayouter->MaxSize();
fMax.height = fVerticalLayouter->MaxSize();
fPreferred.width = fHorizontalLayouter->PreferredSize();
fPreferred.height = fVerticalLayouter->PreferredSize();
fHorizontalLayoutInfo = fHorizontalLayouter->CreateLayoutInfo();
if (fHeightForWidthItems.IsEmpty())
fVerticalLayoutInfo = fVerticalLayouter->CreateLayoutInfo();
ResetLayoutInvalidation();
}
void
BSplitLayout::_InternalGetHeightForWidth(float width, bool realLayout,
float* minHeight, float* maxHeight, float* preferredHeight)
{
if ((realLayout && fHeightForWidthVerticalLayouterWidth != width)
|| (!realLayout && fCachedHeightForWidthWidth != width)) {
LayoutInfo* horizontalLayoutInfo = NULL;
Layouter* verticalLayouter = fVerticalLayouter->CloneLayouter();
if (realLayout) {
horizontalLayoutInfo = fHorizontalLayoutInfo;
delete fHeightForWidthVerticalLayouter;
fHeightForWidthVerticalLayouter = verticalLayouter;
delete fVerticalLayoutInfo;
fVerticalLayoutInfo = verticalLayouter->CreateLayoutInfo();
fHeightForWidthVerticalLayouterWidth = width;
} else {
if (fHeightForWidthHorizontalLayoutInfo == NULL) {
delete fHeightForWidthHorizontalLayoutInfo;
fHeightForWidthHorizontalLayoutInfo
= fHorizontalLayouter->CreateLayoutInfo();
}
horizontalLayoutInfo = fHeightForWidthHorizontalLayoutInfo;
}
if (!realLayout)
fHorizontalLayouter->Layout(horizontalLayoutInfo, width);
int32 count = fHeightForWidthItems.CountItems();
for (int32 i = 0; i < count; i++) {
BLayoutItem* item = (BLayoutItem*)fHeightForWidthItems.ItemAt(i);
int32 index = fVisibleItems.IndexOf(item);
if (index >= 0) {
float itemMinHeight, itemMaxHeight, itemPreferredHeight;
item->GetHeightForWidth(
horizontalLayoutInfo->ElementSize(index),
&itemMinHeight, &itemMaxHeight, &itemPreferredHeight);
verticalLayouter->AddConstraints(index, 1, itemMinHeight,
itemMaxHeight, itemPreferredHeight);
}
}
fCachedHeightForWidthWidth = width;
fCachedMinHeightForWidth = verticalLayouter->MinSize();
fCachedMaxHeightForWidth = verticalLayouter->MaxSize();
fCachedPreferredHeightForWidth = verticalLayouter->PreferredSize();
}
if (minHeight)
*minHeight = fCachedMinHeightForWidth;
if (maxHeight)
*maxHeight = fCachedMaxHeightForWidth;
if (preferredHeight)
*preferredHeight = fCachedPreferredHeightForWidth;
}
float
BSplitLayout::_SplitterSpace() const
{
int32 splitters = fSplitterItems.CountItems();
float space = 0;
if (splitters > 0) {
space = (fVisibleItems.CountItems() + splitters - 1) * fSpacing
+ splitters * fSplitterSize;
}
return space;
}
BSize
BSplitLayout::_AddInsets(BSize size)
{
size.width = BLayoutUtils::AddDistances(size.width,
fLeftInset + fRightInset - 1);
size.height = BLayoutUtils::AddDistances(size.height,
fTopInset + fBottomInset - 1);
float spacing = _SplitterSpace();
if (fOrientation == B_HORIZONTAL)
size.width = BLayoutUtils::AddDistances(size.width, spacing - 1);
else
size.height = BLayoutUtils::AddDistances(size.height, spacing - 1);
return size;
}
void
BSplitLayout::_AddInsets(float* minHeight, float* maxHeight,
float* preferredHeight)
{
float insets = fTopInset + fBottomInset - 1;
if (fOrientation == B_VERTICAL)
insets += _SplitterSpace();
if (minHeight)
*minHeight = BLayoutUtils::AddDistances(*minHeight, insets);
if (maxHeight)
*maxHeight = BLayoutUtils::AddDistances(*maxHeight, insets);
if (preferredHeight)
*preferredHeight = BLayoutUtils::AddDistances(*preferredHeight, insets);
}
BSize
BSplitLayout::_SubtractInsets(BSize size)
{
size.width = BLayoutUtils::SubtractDistances(size.width,
fLeftInset + fRightInset - 1);
size.height = BLayoutUtils::SubtractDistances(size.height,
fTopInset + fBottomInset - 1);
float spacing = _SplitterSpace();
if (fOrientation == B_HORIZONTAL)
size.width = BLayoutUtils::SubtractDistances(size.width, spacing - 1);
else
size.height = BLayoutUtils::SubtractDistances(size.height, spacing - 1);
return size;
}