⛏️ index : haiku.git

/*
 * Copyright 2007-2008, Christof Lutteroth, lutteroth@cs.auckland.ac.nz
 * Copyright 2007-2008, James Kim, jkim202@ec.auckland.ac.nz
 * Copyright 2010, Clemens Zeidler <haiku@clemens-zeidler.de>
 * Distributed under the terms of the MIT License.
 */


#include "Area.h"

#include <Alignment.h>
#include <ControlLook.h>
#include <View.h>

#include "ALMLayout.h"
#include "RowColumnManager.h"
#include "Row.h"
#include "Column.h"


using namespace LinearProgramming;


BLayoutItem*
Area::Item()
{
	return fLayoutItem;
}


/**
 * Gets the left tab of the area.
 *
 * @return the left tab of the area
 */
XTab*
Area::Left() const
{
	return fLeft;
}


/**
 * Gets the right tab of the area.
 *
 * @return the right tab of the area
 */
XTab*
Area::Right() const
{
	return fRight;
}


/**
 * Gets the top tab of the area.
 */
YTab*
Area::Top() const
{
	return fTop;
}


/**
 * Gets the bottom tab of the area.
 */
YTab*
Area::Bottom() const
{
	return fBottom;
}


/**
 * Sets the left tab of the area.
 *
 * @param left	the left tab of the area
 */
void
Area::SetLeft(BReference<XTab> left)
{
	fLeft = left;

	fMinContentWidth->SetLeftSide(-1.0, fLeft, 1.0, fRight);
	if (fMaxContentWidth != NULL)
		fMaxContentWidth->SetLeftSide(-1.0, fLeft, 1.0, fRight);
	fRowColumnManager->TabsChanged(this);

	fLayoutItem->Layout()->InvalidateLayout();
}


/**
 * Sets the right tab of the area.
 *
 * @param right	the right tab of the area
 */
void
Area::SetRight(BReference<XTab> right)
{
	fRight = right;

	fMinContentWidth->SetLeftSide(-1.0, fLeft, 1.0, fRight);
	if (fMaxContentWidth != NULL)
		fMaxContentWidth->SetLeftSide(-1.0, fLeft, 1.0, fRight);
	fRowColumnManager->TabsChanged(this);

	fLayoutItem->Layout()->InvalidateLayout();
}


/**
 * Sets the top tab of the area.
 */
void
Area::SetTop(BReference<YTab> top)
{
	fTop = top;

	fMinContentHeight->SetLeftSide(-1.0, fTop, 1.0, fBottom);
	if (fMaxContentHeight != NULL)
		fMaxContentHeight->SetLeftSide(-1.0, fTop, 1.0, fBottom);
	fRowColumnManager->TabsChanged(this);

	fLayoutItem->Layout()->InvalidateLayout();
}


/**
 * Sets the bottom tab of the area.
 */
void
Area::SetBottom(BReference<YTab> bottom)
{
	fBottom = bottom;

	fMinContentHeight->SetLeftSide(-1.0, fTop, 1.0, fBottom);
	if (fMaxContentHeight != NULL)
		fMaxContentHeight->SetLeftSide(-1.0, fTop, 1.0, fBottom);
	fRowColumnManager->TabsChanged(this);

	fLayoutItem->Layout()->InvalidateLayout();
}


/**
 * Gets the row that defines the top and bottom tabs.
 */
Row*
Area::GetRow() const
{
	return fRow;
}


/**
 * Gets the column that defines the left and right tabs.
 */
Column*
Area::GetColumn() const
{
	return fColumn;
}


/**
 * The reluctance with which the area's content shrinks below its preferred size.
 * The bigger the less likely is such shrinking.
 */
BSize
Area::ShrinkPenalties() const
{
	return fShrinkPenalties;
}


/**
 * The reluctance with which the area's content grows over its preferred size.
 * The bigger the less likely is such growth.
 */
BSize
Area::GrowPenalties() const
{
	return fGrowPenalties;
}


void
Area::SetShrinkPenalties(BSize shrink) {
	fShrinkPenalties = shrink;

	fLayoutItem->Layout()->InvalidateLayout();
}


void
Area::SetGrowPenalties(BSize grow)
{
	fGrowPenalties = grow;

	fLayoutItem->Layout()->InvalidateLayout();
}


/**
 * Gets aspect ratio of the area's content.
 */
double
Area::ContentAspectRatio() const
{
	return fContentAspectRatio;
}


/**
 * Sets aspect ratio of the area's content.
 * May be different from the aspect ratio of the area.
 */
void
Area::SetContentAspectRatio(double ratio)
{
	fContentAspectRatio = ratio;
	if (fContentAspectRatio <= 0) {
		delete fContentAspectRatioC;
		fContentAspectRatioC = NULL;
	} else if (fContentAspectRatioC == NULL) {
		fContentAspectRatioC = fLS->AddConstraint(-1.0, fLeft, 1.0, fRight,
			ratio, fTop, -ratio, fBottom, kEQ, 0.0);
	} else {
		fContentAspectRatioC->SetLeftSide(-1.0, fLeft, 1.0, fRight, ratio,
			fTop, -ratio, fBottom);
	}
	/* called during BALMLayout::ItemUnarchived */
	if (BLayout* layout = fLayoutItem->Layout())
		layout->InvalidateLayout();
}


void
Area::GetInsets(float* left, float* top, float* right, float* bottom) const
{
	if (left)
		*left = fLeftTopInset.Width();
	if (top)
		*top = fLeftTopInset.Height();
	if (right)
		*right = fRightBottomInset.Width();
	if (bottom)
		*bottom = fRightBottomInset.Height();
}


/**
 * Gets left inset between area and its content.
 */
float
Area::LeftInset() const
{
	if (fLeftTopInset.IsWidthSet())
		return fLeftTopInset.Width();

	BALMLayout* layout = static_cast<BALMLayout*>(fLayoutItem->Layout());
	return layout->InsetForTab(fLeft.Get());
}


/**
 * Gets top inset between area and its content.
 */
float
Area::TopInset() const
{
	if (fLeftTopInset.IsHeightSet())
		return fLeftTopInset.Height();

	BALMLayout* layout = static_cast<BALMLayout*>(fLayoutItem->Layout());
	return layout->InsetForTab(fTop.Get());
}


/**
 * Gets right inset between area and its content.
 */
float
Area::RightInset() const
{
	if (fRightBottomInset.IsWidthSet())
		return fRightBottomInset.Width();

	BALMLayout* layout = static_cast<BALMLayout*>(fLayoutItem->Layout());
	return layout->InsetForTab(fRight.Get());
}


/**
 * Gets bottom inset between area and its content.
 */
float
Area::BottomInset() const
{
	if (fRightBottomInset.IsHeightSet())
		return fRightBottomInset.Height();

	BALMLayout* layout = static_cast<BALMLayout*>(fLayoutItem->Layout());
	return layout->InsetForTab(fBottom.Get());
}


void
Area::SetInsets(float insets)
{
	if (insets != B_SIZE_UNSET)
		insets = BControlLook::ComposeSpacing(insets);

	fLeftTopInset.Set(insets, insets);
	fRightBottomInset.Set(insets, insets);
	fLayoutItem->Layout()->InvalidateLayout();
}


void
Area::SetInsets(float horizontal, float vertical)
{
	if (horizontal != B_SIZE_UNSET)
		horizontal = BControlLook::ComposeSpacing(horizontal);
	if (vertical != B_SIZE_UNSET)
		vertical = BControlLook::ComposeSpacing(vertical);

	fLeftTopInset.Set(horizontal, horizontal);
	fRightBottomInset.Set(vertical, vertical);
	fLayoutItem->Layout()->InvalidateLayout();
}


void
Area::SetInsets(float left, float top, float right, float bottom)
{
	if (left != B_SIZE_UNSET)
		left = BControlLook::ComposeSpacing(left);
	if (right != B_SIZE_UNSET)
		right = BControlLook::ComposeSpacing(right);
	if (top != B_SIZE_UNSET)
		top = BControlLook::ComposeSpacing(top);
	if (bottom != B_SIZE_UNSET)
		bottom = BControlLook::ComposeSpacing(bottom);

	fLeftTopInset.Set(left, top);
	fRightBottomInset.Set(right, bottom);
	fLayoutItem->Layout()->InvalidateLayout();
}


/**
 * Sets left inset between area and its content.
 */
void
Area::SetLeftInset(float left)
{
	fLeftTopInset.width = left;
	fLayoutItem->Layout()->InvalidateLayout();
}


/**
 * Sets top inset between area and its content.
 */
void
Area::SetTopInset(float top)
{
	fLeftTopInset.height = top;
	fLayoutItem->Layout()->InvalidateLayout();
}


/**
 * Sets right inset between area and its content.
 */
void
Area::SetRightInset(float right)
{
	fRightBottomInset.width = right;
	fLayoutItem->Layout()->InvalidateLayout();
}


/**
 * Sets bottom inset between area and its content.
 */
void
Area::SetBottomInset(float bottom)
{
	fRightBottomInset.height = bottom;
	fLayoutItem->Layout()->InvalidateLayout();
}


BString
Area::ToString() const
{
	BString string = "Area(";
	string += fLeft->ToString();
	string << ", ";
	string += fTop->ToString();
	string << ", ";
	string += fRight->ToString();
	string << ", ";
	string += fBottom->ToString();
	string << ")";
	return string;
}


/*!
 * Sets the width of the area to be the same as the width of the given area
 * times factor.
 *
 * @param area	the area that should have the same width
 * @return the same-width constraint
 */
Constraint*
Area::SetWidthAs(Area* area, float factor)
{
	return fLS->AddConstraint(-1.0, fLeft, 1.0, fRight, factor, area->Left(),
		-factor, area->Right(), kEQ, 0.0);
}


/*!
 * Sets the height of the area to be the same as the height of the given area
 * times factor.
 *
 * @param area	the area that should have the same height
 * @return the same-height constraint
 */
Constraint*
Area::SetHeightAs(Area* area, float factor)
{
	return fLS->AddConstraint(-1.0, fTop, 1.0, fBottom, factor, area->Top(),
		-factor, area->Bottom(), kEQ, 0.0);
}


void
Area::InvalidateSizeConstraints()
{
	// check if if we are initialized
	if (!fLeft)
		return;

	BSize minSize = fLayoutItem->MinSize();
	BSize maxSize = fLayoutItem->MaxSize();

	_UpdateMinSizeConstraint(minSize);
	_UpdateMaxSizeConstraint(maxSize);
}


BRect
Area::Frame() const
{
	return BRect(round(fLeft->Value()), round(fTop->Value()),
		round(fRight->Value()), round(fBottom->Value()));
}


/**
 * Destructor.
 * Removes the area from its specification.
 */
Area::~Area()
{
	delete fMinContentWidth;
	delete fMaxContentWidth;
	delete fMinContentHeight;
	delete fMaxContentHeight;
	delete fContentAspectRatioC;
}


static int32 sAreaID = 0;

static int32
new_area_id()
{
	return sAreaID++;
}


/**
 * Constructor.
 * Uses XTabs and YTabs.
 */
Area::Area(BLayoutItem* item)
	:
	fLayoutItem(item),
	fLS(NULL),
	fLeft(NULL),
	fRight(NULL),
	fTop(NULL),
	fBottom(NULL),
	fRow(NULL),
	fColumn(NULL),
	fShrinkPenalties(5, 5),
	fGrowPenalties(5, 5),
	fContentAspectRatio(-1),
	fRowColumnManager(NULL),
	fMinContentWidth(NULL),
	fMaxContentWidth(NULL),
	fMinContentHeight(NULL),
	fMaxContentHeight(NULL),
	fContentAspectRatioC(NULL)
{
	fID = new_area_id();
}


int32
Area::ID() const
{
	return fID;
}


void
Area::SetID(int32 id)
{
	fID = id;
}


/**
 * Initialize variables.
 */
void
Area::_Init(LinearSpec* ls, XTab* left, YTab* top, XTab* right, YTab* bottom,
	RowColumnManager* manager)
{
	fLS = ls;
	fLeft = left;
	fRight = right;
	fTop = top;
	fBottom = bottom;

	fRowColumnManager = manager;

	// adds the two essential constraints of the area that make sure that the
	// left x-tab is really to the left of the right x-tab, and the top y-tab
	// really above the bottom y-tab
	fMinContentWidth = ls->AddConstraint(-1.0, fLeft, 1.0, fRight, kGE, 0);
	fMinContentHeight = ls->AddConstraint(-1.0, fTop, 1.0, fBottom, kGE, 0);

	InvalidateSizeConstraints();
}


void
Area::_Init(LinearSpec* ls, Row* row, Column* column, RowColumnManager* manager)
{
	_Init(ls, column->Left(), row->Top(), column->Right(),
		row->Bottom(), manager);

	fRow = row;
	fColumn = column;
}


/**
 * Perform layout on the area.
 */
void
Area::_DoLayout(const BPoint& offset)
{
	// check if if we are initialized
	if (!fLeft)
		return;

	if (!fLayoutItem->IsVisible())
		fLayoutItem->AlignInFrame(BRect(0, 0, -1, -1));

	BRect areaFrame(Frame());
	areaFrame.left += LeftInset();
	areaFrame.right -= RightInset();
	areaFrame.top += TopInset();
	areaFrame.bottom -= BottomInset();

	fLayoutItem->AlignInFrame(areaFrame.OffsetBySelf(offset));
}


void
Area::_UpdateMinSizeConstraint(BSize min)
{
	if (!fLayoutItem->IsVisible()) {
		fMinContentHeight->SetRightSide(-1);
		fMinContentWidth->SetRightSide(-1);
		return;
	}

	float width = 0.;
	float height = 0.;
	if (min.width > 0)
		width = min.Width() + LeftInset() + RightInset();
	if (min.height > 0)
		height = min.Height() + TopInset() + BottomInset();

	fMinContentWidth->SetRightSide(width);
	fMinContentHeight->SetRightSide(height);
}


void
Area::_UpdateMaxSizeConstraint(BSize max)
{
	if (!fLayoutItem->IsVisible()) {
		if (fMaxContentHeight != NULL)
			fMaxContentHeight->SetRightSide(B_SIZE_UNLIMITED);
		if (fMaxContentWidth != NULL)
			fMaxContentWidth->SetRightSide(B_SIZE_UNLIMITED);
		return;
	}

	max.width += LeftInset() + RightInset();
	max.height += TopInset() + BottomInset();

	const double kPriority = 100;
	// we only need max constraints if the alignment is full height/width
	// otherwise we can just align the item in the free space
	BAlignment alignment = fLayoutItem->Alignment();
	double priority = kPriority;
	if (alignment.Vertical() == B_ALIGN_USE_FULL_HEIGHT)
		priority = -1;

	if (max.Height() < 20000) {
		if (fMaxContentHeight == NULL) {
			fMaxContentHeight = fLS->AddConstraint(-1.0, fTop, 1.0, fBottom,
				kLE, max.Height(), priority, priority);
		} else {
			fMaxContentHeight->SetRightSide(max.Height());
			fMaxContentHeight->SetPenaltyNeg(priority);
			fMaxContentHeight->SetPenaltyPos(priority);
		}
	} else {
		delete fMaxContentHeight;
		fMaxContentHeight = NULL;
	}

	priority = kPriority;
	if (alignment.Horizontal() == B_ALIGN_USE_FULL_WIDTH)
		priority = -1;

	if (max.Width() < 20000) {
		if (fMaxContentWidth == NULL) {
			fMaxContentWidth = fLS->AddConstraint(-1.0, fLeft, 1.0, fRight, kLE,
				max.Width(), priority, priority);
		} else {
			fMaxContentWidth->SetRightSide(max.Width());
			fMaxContentWidth->SetPenaltyNeg(priority);
			fMaxContentWidth->SetPenaltyPos(priority);
		}
	} else {
		delete fMaxContentWidth;
		fMaxContentWidth = NULL;
	}
}