/*
 * 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;


// archivng constants
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;	// including spacing
	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)
	{
		// not allowed
	}

	virtual void SetExplicitMaxSize(BSize size)
	{
		// not allowed
	}

	virtual void SetExplicitPreferredSize(BSize size)
	{
		// not allowed
	}

	virtual void SetExplicitAlignment(BAlignment alignment)
	{
		// not allowed
	}

	virtual bool IsVisible()
	{
		return true;
	}

	virtual void SetVisible(bool visible)
	{
		// not allowed
	}


	virtual BRect Frame()
	{
		return fFrame;
	}

	virtual void SetFrame(BRect frame)
	{
		fFrame = frame;
	}

private:
	BSplitLayout*	fLayout;
	BRect			fFrame;
};


// #pragma mark -


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();

	// layout the elements
	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;	// pixel counts, no distances
	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++) {
		// layout the splitter
		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;
		}

		// layout the item
		BLayoutItem* item = ItemAt(i);
		int32 visibleIndex = fVisibleItems.IndexOf(item);
		if (visibleIndex < 0) {
			_LayoutItem(item, BRect(), false);
			continue;
		}

		// get the dimensions of the item
		float width = fHorizontalLayoutInfo->ElementSize(visibleIndex);
		float height = fVerticalLayoutInfo->ElementSize(visibleIndex);

		// place the component
		_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();

	// Layout must be valid. Bail out, if it isn't.
	if (!fLayoutValid)
		return false;

	// Things shouldn't be draggable, if we have a >= max layout.
	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;

	// update the item weights
	_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)
{
	// update the layout frame
	ItemLayoutInfo* info = _ItemLayoutInfo(item);
	info->isVisible = visible;
	if (visible)
		info->layoutFrame = frame;
	else
		info->layoutFrame = BRect(0, 0, -1, -1);

	// update min/max
	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);
	}

	// layout the item
	if (visible)
		item->AlignInFrame(frame);
}


void
BSplitLayout::_LayoutItem(BLayoutItem* item, ItemLayoutInfo* info)
{
	// update the visibility of the item
	bool isVisible = item->IsVisible();
	bool visibilityChanged = (info->isVisible != isVisible);
	if (visibilityChanged)
		item->SetVisible(info->isVisible);

	// nothing more to do, if the item is not visible
	if (!info->isVisible)
		return;

	item->AlignInFrame(info->layoutFrame);

	// if the item became visible, we need to update its internal layout
	if (visibilityChanged &&
		(fOrientation != B_HORIZONTAL || !HasHeightForWidth())) {
		item->Relayout(true);
	}
}


bool
BSplitLayout::_SetSplitterValue(int32 index, int32 value)
{
	// if both items are collapsed, nothing can be dragged
	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;

	// Note: While this collapsed-check is mathmatically correct (i.e. we
	// collapse an item, if it would become smaller than half its minimum
	// size), we might want to change it, since for the user it looks like
	// collapsing happens earlier. The reason being that the only visual mark
	// the user has is the mouse cursor which indeed hasn't crossed the middle
	// of the item yet.
	bool previousCollapsed = (previousSize <= range.previousMin / 2)
		&& previousInfo->isCollapsible;
	bool nextCollapsed = (nextSize <= range.nextMin / 2)
		&& nextInfo->isCollapsible;
	if (previousCollapsed && nextCollapsed) {
		// we cannot collapse both items; we have to decide for one
		if (previousSize < nextSize) {
			// collapse previous
			nextCollapsed = false;
			nextSize = range.sumValue - (int32)fSpacing;
		} else {
			// collapse next
			previousCollapsed = false;
			previousSize = range.sumValue - (int32)fSpacing;
		}
	}

	if (previousCollapsed || nextCollapsed) {
		// one collapsed item -- check whether that violates the constraints
		// of the other one
		int32 availableSpace = range.sumValue - (int32)fSpacing;
		if (previousCollapsed) {
			if (availableSpace < range.nextMin
				|| availableSpace > range.nextMax) {
				// we cannot collapse the previous item
				previousCollapsed = false;
			}
		} else {
			if (availableSpace < range.previousMin
				|| availableSpace > range.previousMax) {
				// we cannot collapse the next item
				nextCollapsed = false;
			}
		}
	}

	if (!(previousCollapsed || nextCollapsed)) {
		// no collapsed item -- check whether there is a close solution
		previousSize = value - (int32)fSpacing;
		nextSize = range.sumValue - value - (int32)fSpacing;

		if (range.previousMin + range.nextMin + 2 * fSpacing > range.sumValue) {
			// we don't have enough space to uncollapse both items
			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 {
					// Neither item is collapsible although there's not enough
					// space: Give them both their minimum size.
					previousSize = range.previousMin;
					nextSize = range.nextMin;
				}
			}

		} else {
			// there is enough space for both items
			// make sure the min constraints are satisfied
			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 we can, also satisfy the max constraints
			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;
				}
			}
		}
	}

	// compute the size for one collapsed item; for none collapsed item we
	// already have correct values
	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) {
		// nothing changed
		return false;
	}

	// something changed: we need to recompute the layout
	int32 baseOffset = -fDraggingCurrentValue;
		// offset to the current splitter position
	int32 splitterOffset = baseOffset + newValue;
	int32 nextOffset = splitterOffset + (int32)fSplitterSize + (int32)fSpacing;

	BRect splitterFrame(splitterInfo->layoutFrame);
	if (fOrientation == B_HORIZONTAL) {
		// horizontal layout
		// previous item
		float left = splitterFrame.left + baseOffset;
		previousInfo->layoutFrame.Set(
			left,
			splitterFrame.top,
			left + previousSize - 1,
			splitterFrame.bottom);

		// next item
		left = splitterFrame.left + nextOffset;
		nextInfo->layoutFrame.Set(
			left,
			splitterFrame.top,
			left + nextSize - 1,
			splitterFrame.bottom);

		// splitter
		splitterInfo->layoutFrame.left += splitterOffset;
		splitterInfo->layoutFrame.right += splitterOffset;
	} else {
		// vertical layout
		// previous item
		float top = splitterFrame.top + baseOffset;
		previousInfo->layoutFrame.Set(
			splitterFrame.left,
			top,
			splitterFrame.right,
			top + previousSize - 1);

		// next item
		top = splitterFrame.top + nextOffset;
		nextInfo->layoutFrame.Set(
			splitterFrame.left,
			top,
			splitterFrame.right,
			top + nextSize - 1);

		// splitter
		splitterInfo->layoutFrame.top += splitterOffset;
		splitterInfo->layoutFrame.bottom += splitterOffset;
	}

	previousInfo->isVisible = !previousCollapsed;
	nextInfo->isVisible = !nextCollapsed;

	bool heightForWidth = (fOrientation == B_HORIZONTAL && HasHeightForWidth());

	// If the item visibility is to be changed, we need to update the splitter
	// values now, since the visibility change will cause an invalidation.
	if (previousVisible != previousInfo->isVisible
		|| nextVisible != nextInfo->isVisible || heightForWidth) {
		_UpdateSplitterWeights();
	}

	// If we have height for width items, we need to invalidate the previous
	// and the next item. Actually we would only need to invalidate height for
	// width items, but since non height for width items might be aligned with
	// height for width items, we need to trigger a layout that creates a
	// context that spans all aligned items.
	// We invalidate already here, so that changing the items' size won't cause
	// an immediate relayout.
	if (heightForWidth) {
		previousItem->InvalidateLayout();
		nextItem->InvalidateLayout();
	}

	// do the layout
	_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);
	}

	// Just updating the splitter weights is fine in principle. The next
	// LayoutItems() will use the correct values. But, if our orientation is
	// vertical, the cached height for width info needs to be flushed, or the
	// obsolete cached values will be used.
	if (fOrientation == B_VERTICAL)
		_InvalidateCachedHeightForWidth();
}


void
BSplitLayout::_ValidateMinMax()
{
	if (fHorizontalLayouter != NULL)
		return;

	fLayoutValid = false;

	fVisibleItems.MakeEmpty();
	fHeightForWidthItems.MakeEmpty();

	_InvalidateCachedHeightForWidth();

	// filter the visible items
	int32 itemCount = CountItems();
	for (int32 i = 0; i < itemCount; i++) {
		BLayoutItem* item = ItemAt(i);
		if (item->IsVisible())
			fVisibleItems.AddItem(item);

		// Add "height for width" items even, if they aren't visible. Otherwise
		// we may get our parent into trouble, since we could change from
		// "height for width" to "not height for width".
		if (item->HasHeightForWidth())
			fHeightForWidthItems.AddItem(item);
	}
	itemCount = fVisibleItems.CountItems();

	// create the layouters
	Layouter* itemLayouter = new SimpleLayouter(itemCount, 0);

	if (fOrientation == B_HORIZONTAL) {
		fHorizontalLayouter = itemLayouter;
		fVerticalLayouter = new OneElementLayouter();
	} else {
		fHorizontalLayouter = new OneElementLayouter();
		fVerticalLayouter = itemLayouter;
	}

	// tell the layouters about our constraints
	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)) {
		// The general strategy is to clone the vertical layouter, which only
		// knows the general min/max constraints, do a horizontal layout for the
		// given width, and add the children's height for width constraints to
		// the cloned vertical layouter. If this method is invoked internally,
		// we keep the cloned vertical layouter, for it will be used for doing
		// the layout. Otherwise we just drop it after we've got the height for
		// width info.

		// clone the vertical layouter and get the horizontal layout info to be used
		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;
		}

		// do the horizontal layout (already done when doing this for the real
		// layout)
		if (!realLayout)
			fHorizontalLayouter->Layout(horizontalLayoutInfo, width);

		// add the children's height for width constraints
		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);
			}
		}

		// get the height for width info
		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;
}

