Files
haiku-beta6/src/libs/alm/ALMLayout.cpp
T
Alex Wilson 09e87fa352 Pass more information to BadLayoutPolicy.
Also, re-add BadLayoutPolicy invocations, which were lost while creating
SharedSolver.
2012-05-03 08:45:06 +12:00

1072 lines
20 KiB
C++

/*
* Copyright 2007-2008, Christof Lutteroth, [email protected]
* Copyright 2007-2008, James Kim, [email protected]
* Copyright 2010, Clemens Zeidler <[email protected]>
* Distributed under the terms of the MIT License.
*/
#include "ALMLayout.h"
#include <vector>
#include <AutoDeleter.h>
#include <ControlLook.h>
#include "RowColumnManager.h"
#include "SharedSolver.h"
#include "ViewLayoutItem.h"
using BPrivate::AutoDeleter;
using namespace LinearProgramming;
const BSize kUnsetSize(B_SIZE_UNSET, B_SIZE_UNSET);
namespace BALM {
template <class T>
struct BALMLayout::TabAddTransaction {
~TabAddTransaction()
{
if (fTab)
fLayout->_RemoveSelfFromTab(fTab);
if (fIndex > 0)
_TabList()->RemoveItemAt(fIndex);
}
TabAddTransaction(BALMLayout* layout)
:
fTab(NULL),
fLayout(layout),
fIndex(-1)
{
}
bool AttempAdd(T* tab)
{
if (fLayout->_HasTabInLayout(tab))
return true;
if (!fLayout->_AddedTab(tab))
return false;
fTab = tab;
BObjectList<T>* tabList = _TabList();
int32 index = tabList->CountItems();
if (!tabList->AddItem(tab, index))
return false;
fIndex = index;
return true;
}
void Commit()
{
fTab = NULL;
fIndex = -1;
}
private:
BObjectList<T>* _TabList();
T* fTab;
BALMLayout* fLayout;
int32 fIndex;
};
template <>
BObjectList<XTab>*
BALMLayout::TabAddTransaction<XTab>::_TabList()
{
return &fLayout->fXTabList;
}
template <>
BObjectList<YTab>*
BALMLayout::TabAddTransaction<YTab>::_TabList()
{
return &fLayout->fYTabList;
}
}; // end namespace BALM
BALM::BALMLayout::BadLayoutPolicy::~BadLayoutPolicy()
{
}
BALM::BALMLayout::DefaultPolicy::~DefaultPolicy()
{
}
bool
BALM::BALMLayout::DefaultPolicy::OnBadLayout(BALMLayout* layout,
ResultType result, BLayoutContext* context)
{
if (!context)
return true;
if (result == kInfeasible) {
debugger("BALMLayout failed to solve your layout!");
return false;
} else
return true;
}
/*!
* Constructor.
* Creates new layout engine.
*
* If friendLayout is not NULL the solver of the friend layout is used.
*/
BALMLayout::BALMLayout(float hSpacing, float vSpacing, BALMLayout* friendLayout)
:
fLeftInset(0),
fRightInset(0),
fTopInset(0),
fBottomInset(0),
fHSpacing(BControlLook::ComposeSpacing(hSpacing)),
fVSpacing(BControlLook::ComposeSpacing(vSpacing)),
fBadLayoutPolicy(new DefaultPolicy())
{
fSolver = friendLayout ? friendLayout->fSolver : new SharedSolver();
fSolver->AcquireReference();
fSolver->RegisterLayout(this);
fRowColumnManager = new RowColumnManager(Solver());
fLeft = AddXTab();
fRight = AddXTab();
fTop = AddYTab();
fBottom = AddYTab();
// the Left tab is always at x-position 0, and the Top tab is always at
// y-position 0
fLeft->SetRange(0, 0);
fTop->SetRange(0, 0);
// cached layout values
// need to be invalidated whenever the layout specification is changed
fMinSize = kUnsetSize;
fMaxSize = kUnsetSize;
fPreferredSize = kUnsetSize;
}
BALMLayout::~BALMLayout()
{
delete fRowColumnManager;
delete fBadLayoutPolicy;
fSolver->LayoutLeaving(this);
fSolver->ReleaseReference();
}
/**
* Adds a new x-tab to the specification.
*
* @return the new x-tab
*/
BReference<XTab>
BALMLayout::AddXTab()
{
BReference<XTab> tab(new(std::nothrow) XTab(this), true);
if (!tab)
return NULL;
if (!Solver()->AddVariable(tab))
return NULL;
fXTabList.AddItem(tab);
if (!tab->AddedToLayout(this)) {
fXTabList.RemoveItem(tab);
return NULL;
}
return tab;
}
void
BALMLayout::AddXTabs(BReference<XTab>* tabs, uint32 count)
{
for (uint32 i = 0; i < count; i++)
tabs[i] = AddXTab();
}
void
BALMLayout::AddYTabs(BReference<YTab>* tabs, uint32 count)
{
for (uint32 i = 0; i < count; i++)
tabs[i] = AddYTab();
}
/**
* Adds a new y-tab to the specification.
*
* @return the new y-tab
*/
BReference<YTab>
BALMLayout::AddYTab()
{
BReference<YTab> tab(new(std::nothrow) YTab(this), true);
if (tab.Get() == NULL)
return NULL;
if (!Solver()->AddVariable(tab))
return NULL;
fYTabList.AddItem(tab);
if (!tab->AddedToLayout(this)) {
fYTabList.RemoveItem(tab);
return NULL;
}
return tab;
}
int32
BALMLayout::CountXTabs() const
{
return fXTabList.CountItems();
}
int32
BALMLayout::CountYTabs() const
{
return fYTabList.CountItems();
}
XTab*
BALMLayout::XTabAt(int32 index) const
{
return fXTabList.ItemAt(index);
}
YTab*
BALMLayout::YTabAt(int32 index) const
{
return fYTabList.ItemAt(index);
}
static int
compare_x_tab_func(const XTab* tab1, const XTab* tab2)
{
if (tab1->Value() < tab2->Value())
return -1;
else if (tab1->Value() == tab2->Value())
return 0;
return 1;
}
static int
compare_y_tab_func(const YTab* tab1, const YTab* tab2)
{
if (tab1->Value() < tab2->Value())
return -1;
else if (tab1->Value() == tab2->Value())
return 0;
return 1;
}
const XTabList&
BALMLayout::OrderedXTabs()
{
fXTabList.SortItems(compare_x_tab_func);
return fXTabList;
}
const YTabList&
BALMLayout::OrderedYTabs()
{
fYTabList.SortItems(compare_y_tab_func);
return fYTabList;
}
/**
* Adds a new row to the specification that is glued to the given y-tabs.
*
* @param top
* @param bottom
* @return the new row
*/
Row*
BALMLayout::AddRow(YTab* _top, YTab* _bottom)
{
BReference<YTab> top = _top;
BReference<YTab> bottom = _bottom;
if (_top == NULL)
top = AddYTab();
if (_bottom == NULL)
bottom = AddYTab();
return new(std::nothrow) Row(Solver(), top, bottom);
}
/**
* Adds a new column to the specification that is glued to the given x-tabs.
*
* @param left
* @param right
* @return the new column
*/
Column*
BALMLayout::AddColumn(XTab* _left, XTab* _right)
{
BReference<XTab> left = _left;
BReference<XTab> right = _right;
if (_left == NULL)
left = AddXTab();
if (_right == NULL)
right = AddXTab();
return new(std::nothrow) Column(Solver(), left, right);
}
Area*
BALMLayout::AreaFor(int32 id) const
{
int32 areaCount = CountAreas();
for (int32 i = 0; i < areaCount; i++) {
Area* area = AreaAt(i);
if (area->ID() == id)
return area;
}
return NULL;
}
/**
* Finds the area that contains the given control.
*
* @param control the control to look for
* @return the area that contains the control
*/
Area*
BALMLayout::AreaFor(const BView* control) const
{
return AreaFor(ItemAt(IndexOfView(const_cast<BView*>(control))));
}
Area*
BALMLayout::AreaFor(const BLayoutItem* item) const
{
if (!item)
return NULL;
return static_cast<Area*>(item->LayoutData());
}
int32
BALMLayout::CountAreas() const
{
return CountItems();
}
Area*
BALMLayout::AreaAt(int32 index) const
{
return AreaFor(ItemAt(index));
}
XTab*
BALMLayout::LeftOf(const BView* view) const
{
Area* area = AreaFor(view);
if (!area)
return NULL;
return area->Left();
}
XTab*
BALMLayout::LeftOf(const BLayoutItem* item) const
{
Area* area = AreaFor(item);
if (!area)
return NULL;
return area->Left();
}
XTab*
BALMLayout::RightOf(const BView* view) const
{
Area* area = AreaFor(view);
if (!area)
return NULL;
return area->Right();
}
XTab*
BALMLayout::RightOf(const BLayoutItem* item) const
{
Area* area = AreaFor(item);
if (!area)
return NULL;
return area->Right();
}
YTab*
BALMLayout::TopOf(const BView* view) const
{
Area* area = AreaFor(view);
if (!area)
return NULL;
return area->Top();
}
YTab*
BALMLayout::TopOf(const BLayoutItem* item) const
{
Area* area = AreaFor(item);
if (!area)
return NULL;
return area->Top();
}
YTab*
BALMLayout::BottomOf(const BView* view) const
{
Area* area = AreaFor(view);
if (!area)
return NULL;
return area->Bottom();
}
YTab*
BALMLayout::BottomOf(const BLayoutItem* item) const
{
Area* area = AreaFor(item);
if (!area)
return NULL;
return area->Bottom();
}
BLayoutItem*
BALMLayout::AddView(BView* child)
{
return AddView(-1, child);
}
BLayoutItem*
BALMLayout::AddView(int32 index, BView* child)
{
return BAbstractLayout::AddView(index, child);
}
/**
* Adds a new area to the specification, automatically setting preferred size constraints.
*
* @param left left border
* @param top top border
* @param right right border
* @param bottom bottom border
* @param content the control which is the area content
* @return the new area
*/
Area*
BALMLayout::AddView(BView* view, XTab* left, YTab* top, XTab* right,
YTab* bottom)
{
BLayoutItem* item = _LayoutItemToAdd(view);
Area* area = AddItem(item, left, top, right, bottom);
if (!area) {
if (item != view->GetLayout())
delete item;
return NULL;
}
return area;
}
/**
* Adds a new area to the specification, automatically setting preferred size constraints.
*
* @param row the row that defines the top and bottom border
* @param column the column that defines the left and right border
* @param content the control which is the area content
* @return the new area
*/
Area*
BALMLayout::AddView(BView* view, Row* row, Column* column)
{
BLayoutItem* item = _LayoutItemToAdd(view);
Area* area = AddItem(item, row, column);
if (!area) {
if (item != view->GetLayout())
delete item;
return NULL;
}
return area;
}
bool
BALMLayout::AddItem(BLayoutItem* item)
{
return AddItem(-1, item);
}
bool
BALMLayout::AddItem(int32 index, BLayoutItem* item)
{
if (!item)
return false;
// simply add the item at the upper right corner of the previous item
// TODO maybe find a more elegant solution
XTab* left = Left();
YTab* top = Top();
// check range
if (index < 0 || index > CountItems())
index = CountItems();
// for index = 0 we already have set the right tabs
if (index != 0) {
BLayoutItem* prevItem = ItemAt(index - 1);
Area* area = AreaFor(prevItem);
if (area) {
left = area->Right();
top = area->Top();
}
}
Area* area = AddItem(item, left, top);
return area ? true : false;
}
Area*
BALMLayout::AddItem(BLayoutItem* item, XTab* left, YTab* top, XTab* _right,
YTab* _bottom)
{
if (!left->IsSuitableFor(this) || !top->IsSuitableFor(this)
|| (_right && !_right->IsSuitableFor(this))
|| (_bottom && !_bottom->IsSuitableFor(this)))
debugger("Tab added to unfriendly layout!");
BReference<XTab> right = _right;
if (right.Get() == NULL)
right = AddXTab();
BReference<YTab> bottom = _bottom;
if (bottom.Get() == NULL)
bottom = AddYTab();
TabAddTransaction<XTab> leftTabAdd(this);
if (!leftTabAdd.AttempAdd(left))
return NULL;
TabAddTransaction<YTab> topTabAdd(this);
if (!topTabAdd.AttempAdd(top))
return NULL;
TabAddTransaction<XTab> rightTabAdd(this);
if (!rightTabAdd.AttempAdd(right))
return NULL;
TabAddTransaction<YTab> bottomTabAdd(this);
if (!bottomTabAdd.AttempAdd(bottom))
return NULL;
// Area is added in ItemAdded
if (!BAbstractLayout::AddItem(-1, item))
return NULL;
Area* area = AreaFor(item);
if (!area) {
RemoveItem(item);
return NULL;
}
area->_Init(Solver(), left, top, right, bottom, fRowColumnManager);
fRowColumnManager->AddArea(area);
leftTabAdd.Commit();
topTabAdd.Commit();
rightTabAdd.Commit();
bottomTabAdd.Commit();
return area;
}
Area*
BALMLayout::AddItem(BLayoutItem* item, Row* row, Column* column)
{
if (!BAbstractLayout::AddItem(-1, item))
return NULL;
Area* area = AreaFor(item);
if (!area)
return NULL;
area->_Init(Solver(), row, column, fRowColumnManager);
fRowColumnManager->AddArea(area);
return area;
}
enum {
kLeftBorderIndex = -2,
kTopBorderIndex = -3,
kRightBorderIndex = -4,
kBottomBorderIndex = -5,
};
bool
BALMLayout::SaveLayout(BMessage* archive) const
{
archive->MakeEmpty();
archive->AddInt32("nXTabs", CountXTabs());
archive->AddInt32("nYTabs", CountYTabs());
XTabList xTabs = fXTabList;
xTabs.RemoveItem(fLeft);
xTabs.RemoveItem(fRight);
YTabList yTabs = fYTabList;
yTabs.RemoveItem(fTop);
yTabs.RemoveItem(fBottom);
int32 nAreas = CountAreas();
for (int32 i = 0; i < nAreas; i++) {
Area* area = AreaAt(i);
if (area->Left() == fLeft)
archive->AddInt32("left", kLeftBorderIndex);
else
archive->AddInt32("left", xTabs.IndexOf(area->Left()));
if (area->Top() == fTop)
archive->AddInt32("top", kTopBorderIndex);
else
archive->AddInt32("top", yTabs.IndexOf(area->Top()));
if (area->Right() == fRight)
archive->AddInt32("right", kRightBorderIndex);
else
archive->AddInt32("right", xTabs.IndexOf(area->Right()));
if (area->Bottom() == fBottom)
archive->AddInt32("bottom", kBottomBorderIndex);
else
archive->AddInt32("bottom", yTabs.IndexOf(area->Bottom()));
}
return true;
}
bool
BALMLayout::RestoreLayout(const BMessage* archive)
{
int32 neededXTabs;
int32 neededYTabs;
if (archive->FindInt32("nXTabs", &neededXTabs) != B_OK)
return false;
if (archive->FindInt32("nYTabs", &neededYTabs) != B_OK)
return false;
// First store a reference to all needed tabs otherwise they might get lost
// while editing the layout
std::vector<BReference<XTab> > newXTabs;
std::vector<BReference<YTab> > newYTabs;
int32 existingXTabs = fXTabList.CountItems();
for (int32 i = 0; i < neededXTabs; i++) {
if (i < existingXTabs)
newXTabs.push_back(BReference<XTab>(fXTabList.ItemAt(i)));
else
newXTabs.push_back(AddXTab());
}
int32 existingYTabs = fYTabList.CountItems();
for (int32 i = 0; i < neededYTabs; i++) {
if (i < existingYTabs)
newYTabs.push_back(BReference<YTab>(fYTabList.ItemAt(i)));
else
newYTabs.push_back(AddYTab());
}
XTabList xTabs = fXTabList;
xTabs.RemoveItem(fLeft);
xTabs.RemoveItem(fRight);
YTabList yTabs = fYTabList;
yTabs.RemoveItem(fTop);
yTabs.RemoveItem(fBottom);
int32 nAreas = CountAreas();
for (int32 i = 0; i < nAreas; i++) {
Area* area = AreaAt(i);
if (area == NULL)
return false;
int32 left = -1;
if (archive->FindInt32("left", i, &left) != B_OK)
break;
int32 top = archive->FindInt32("top", i);
int32 right = archive->FindInt32("right", i);
int32 bottom = archive->FindInt32("bottom", i);
XTab* leftTab = NULL;
YTab* topTab = NULL;
XTab* rightTab = NULL;
YTab* bottomTab = NULL;
if (left == kLeftBorderIndex)
leftTab = fLeft;
else
leftTab = xTabs.ItemAt(left);
if (top == kTopBorderIndex)
topTab = fTop;
else
topTab = yTabs.ItemAt(top);
if (right == kRightBorderIndex)
rightTab = fRight;
else
rightTab = xTabs.ItemAt(right);
if (bottom == kBottomBorderIndex)
bottomTab = fBottom;
else
bottomTab = yTabs.ItemAt(bottom);
if (leftTab == NULL || topTab == NULL || rightTab == NULL
|| bottomTab == NULL)
return false;
area->SetLeft(leftTab);
area->SetTop(topTab);
area->SetRight(rightTab);
area->SetBottom(bottomTab);
}
return true;
}
/**
* Gets the left variable.
*/
XTab*
BALMLayout::Left() const
{
return fLeft;
}
/**
* Gets the right variable.
*/
XTab*
BALMLayout::Right() const
{
return fRight;
}
/**
* Gets the top variable.
*/
YTab*
BALMLayout::Top() const
{
return fTop;
}
/**
* Gets the bottom variable.
*/
YTab*
BALMLayout::Bottom() const
{
return fBottom;
}
void
BALMLayout::SetBadLayoutPolicy(BadLayoutPolicy* policy)
{
if (fBadLayoutPolicy != policy)
delete fBadLayoutPolicy;
if (policy == NULL)
policy = new DefaultPolicy();
fBadLayoutPolicy = policy;
}
struct BALMLayout::BadLayoutPolicy*
BALMLayout::GetBadLayoutPolicy() const
{
return fBadLayoutPolicy;
}
/**
* Gets minimum size.
*/
BSize
BALMLayout::BaseMinSize()
{
ResultType result = fSolver->ValidateMinSize();
if (result != kOptimal && result != kUnbounded)
fBadLayoutPolicy->OnBadLayout(this, result, NULL);
return fMinSize;
}
/**
* Gets maximum size.
*/
BSize
BALMLayout::BaseMaxSize()
{
ResultType result = fSolver->ValidateMaxSize();
if (result != kOptimal && result != kUnbounded)
fBadLayoutPolicy->OnBadLayout(this, result, NULL);
return fMaxSize;
}
/**
* Gets preferred size.
*/
BSize
BALMLayout::BasePreferredSize()
{
fSolver->ValidateMinSize();
return fMinSize;
}
/**
* Gets the alignment.
*/
BAlignment
BALMLayout::BaseAlignment()
{
BAlignment alignment;
alignment.SetHorizontal(B_ALIGN_HORIZONTAL_CENTER);
alignment.SetVertical(B_ALIGN_VERTICAL_CENTER);
return alignment;
}
/**
* Invalidates the layout.
* Resets minimum/maximum/preferred size.
*/
void
BALMLayout::LayoutInvalidated(bool children)
{
fMinSize = kUnsetSize;
fMaxSize = kUnsetSize;
fPreferredSize = kUnsetSize;
fSolver->Invalidate(children);
}
bool
BALMLayout::ItemAdded(BLayoutItem* item, int32 atIndex)
{
item->SetLayoutData(new(std::nothrow) Area(item));
return item->LayoutData() != NULL;
}
void
BALMLayout::ItemRemoved(BLayoutItem* item, int32 fromIndex)
{
if (Area* area = AreaFor(item)) {
fRowColumnManager->RemoveArea(area);
item->SetLayoutData(NULL);
delete area;
}
}
/**
* Calculate and set the layout.
* If no layout specification is given, a specification is reverse engineered automatically.
*/
void
BALMLayout::DoLayout()
{
BLayoutContext* context = LayoutContext();
ResultType result = fSolver->ValidateLayout(context);
if (result != kOptimal
&& !fBadLayoutPolicy->OnBadLayout(this, result, context)) {
return;
}
// set the calculated positions and sizes for every area
for (int32 i = 0; i < CountItems(); i++)
AreaFor(ItemAt(i))->_DoLayout(LayoutArea().LeftTop());
}
LinearSpec*
BALMLayout::Solver() const
{
return fSolver->Solver();
}
void
BALMLayout::SetInsets(float left, float top, float right,
float bottom)
{
fLeftInset = BControlLook::ComposeSpacing(left);
fTopInset = BControlLook::ComposeSpacing(top);
fRightInset = BControlLook::ComposeSpacing(right);
fBottomInset = BControlLook::ComposeSpacing(bottom);
InvalidateLayout();
}
void
BALMLayout::SetInsets(float horizontal, float vertical)
{
fLeftInset = BControlLook::ComposeSpacing(horizontal);
fRightInset = fLeftInset;
fTopInset = BControlLook::ComposeSpacing(vertical);
fBottomInset = fTopInset;
InvalidateLayout();
}
void
BALMLayout::SetInsets(float insets)
{
fLeftInset = BControlLook::ComposeSpacing(insets);
fRightInset = fLeftInset;
fTopInset = fLeftInset;
fBottomInset = fLeftInset;
InvalidateLayout();
}
void
BALMLayout::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;
}
void
BALMLayout::SetSpacing(float hSpacing, float vSpacing)
{
fHSpacing = BControlLook::ComposeSpacing(hSpacing);
fVSpacing = BControlLook::ComposeSpacing(vSpacing);
}
void
BALMLayout::GetSpacing(float *_hSpacing, float *_vSpacing) const
{
if (_hSpacing)
*_hSpacing = fHSpacing;
if (_vSpacing)
*_vSpacing = fVSpacing;
}
float
BALMLayout::InsetForTab(XTab* tab) const
{
if (tab == fLeft.Get())
return fLeftInset;
if (tab == fRight.Get())
return fRightInset;
return fHSpacing / 2;
}
float
BALMLayout::InsetForTab(YTab* tab) const
{
if (tab == fTop.Get())
return fTopInset;
if (tab == fBottom.Get())
return fBottomInset;
return fVSpacing / 2;
}
void
BALMLayout::UpdateConstraints(BLayoutContext* context)
{
for (int i = 0; i < CountItems(); i++)
AreaFor(ItemAt(i))->InvalidateSizeConstraints();
fRowColumnManager->UpdateConstraints();
}
void BALMLayout::_RemoveSelfFromTab(XTab* tab) { tab->LayoutLeaving(this); }
void BALMLayout::_RemoveSelfFromTab(YTab* tab) { tab->LayoutLeaving(this); }
bool BALMLayout::_HasTabInLayout(XTab* tab) { return tab->IsInLayout(this); }
bool BALMLayout::_HasTabInLayout(YTab* tab) { return tab->IsInLayout(this); }
bool BALMLayout::_AddedTab(XTab* tab) { return tab->AddedToLayout(this); }
bool BALMLayout::_AddedTab(YTab* tab) { return tab->AddedToLayout(this); }
BLayoutItem*
BALMLayout::_LayoutItemToAdd(BView* view)
{
if (view->GetLayout())
return view->GetLayout();
return new(std::nothrow) BViewLayoutItem(view);
}
status_t
BALMLayout::Perform(perform_code d, void* arg)
{
return BAbstractLayout::Perform(d, arg);
}
void BALMLayout::_ReservedALMLayout1() {}
void BALMLayout::_ReservedALMLayout2() {}
void BALMLayout::_ReservedALMLayout3() {}
void BALMLayout::_ReservedALMLayout4() {}
void BALMLayout::_ReservedALMLayout5() {}
void BALMLayout::_ReservedALMLayout6() {}
void BALMLayout::_ReservedALMLayout7() {}
void BALMLayout::_ReservedALMLayout8() {}
void BALMLayout::_ReservedALMLayout9() {}
void BALMLayout::_ReservedALMLayout10() {}