* Copyright 2010-2014 Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* John Scipione, jscipione@gmail.com
* Clemens Zeidler, haiku@clemens-zeidler.de
*/
#include "SATGroup.h"
#include <vector>
#include <Debug.h>
#include <Message.h>
#include "Desktop.h"
#include "SATWindow.h"
#include "StackAndTile.h"
#include "Window.h"
using namespace std;
using namespace LinearProgramming;
const float kExtentPenalty = 1;
const float kHighPenalty = 100;
const float kInequalityPenalty = 10000;
WindowArea::WindowArea(Crossing* leftTop, Crossing* rightTop,
Crossing* leftBottom, Crossing* rightBottom)
:
fGroup(NULL),
fLeftTopCrossing(leftTop),
fRightTopCrossing(rightTop),
fLeftBottomCrossing(leftBottom),
fRightBottomCrossing(rightBottom),
fMinWidthConstraint(NULL),
fMinHeightConstraint(NULL),
fMaxWidthConstraint(NULL),
fMaxHeightConstraint(NULL),
fWidthConstraint(NULL),
fHeightConstraint(NULL)
{
}
WindowArea::~WindowArea()
{
if (fGroup)
fGroup->WindowAreaRemoved(this);
_CleanupCorners();
fGroup->fWindowAreaList.RemoveItem(this);
_UninitConstraints();
}
bool
WindowArea::Init(SATGroup* group)
{
_UninitConstraints();
if (group == NULL || group->fWindowAreaList.AddItem(this) == false)
return false;
fGroup = group;
LinearSpec* linearSpec = fGroup->GetLinearSpec();
fMinWidthConstraint = linearSpec->AddConstraint(1.0, RightVar(), -1.0,
LeftVar(), kGE, 0);
fMinHeightConstraint = linearSpec->AddConstraint(1.0, BottomVar(), -1.0,
TopVar(), kGE, 0);
fMaxWidthConstraint = linearSpec->AddConstraint(1.0, RightVar(), -1.0,
LeftVar(), kLE, 0, kInequalityPenalty, kInequalityPenalty);
fMaxHeightConstraint = linearSpec->AddConstraint(1.0, BottomVar(), -1.0,
TopVar(), kLE, 0, kInequalityPenalty, kInequalityPenalty);
fWidthConstraint = linearSpec->AddConstraint(1.0, RightVar(), -1.0,
LeftVar(), kEQ, 0, kExtentPenalty,
kExtentPenalty);
fHeightConstraint = linearSpec->AddConstraint(-1.0, TopVar(), 1.0,
BottomVar(), kEQ, 0, kExtentPenalty,
kExtentPenalty);
if (!fMinWidthConstraint || !fMinHeightConstraint || !fWidthConstraint
|| !fHeightConstraint || !fMaxWidthConstraint
|| !fMaxHeightConstraint)
return false;
return true;
}
void
WindowArea::DoGroupLayout()
{
SATWindow* parentWindow = fWindowLayerOrder.ItemAt(0);
if (parentWindow == NULL)
return;
BRect frame = parentWindow->CompleteWindowFrame();
frame.OffsetBy(kMakePositiveOffset, kMakePositiveOffset);
fWidthConstraint->SetRightSide(frame.Width());
fHeightConstraint->SetRightSide(frame.Height());
LinearSpec* linearSpec = fGroup->GetLinearSpec();
Constraint* leftConstraint = linearSpec->AddConstraint(1.0, LeftVar(),
kEQ, frame.left);
Constraint* topConstraint = linearSpec->AddConstraint(1.0, TopVar(), kEQ,
frame.top);
fWidthConstraint->SetPenaltyNeg(kHighPenalty);
fWidthConstraint->SetPenaltyPos(kHighPenalty);
fHeightConstraint->SetPenaltyNeg(kHighPenalty);
fHeightConstraint->SetPenaltyPos(kHighPenalty);
ResultType result;
for (int32 tries = 0; tries < 15; tries++) {
result = fGroup->GetLinearSpec()->Solve();
if (result == kInfeasible) {
debug_printf("can't solve constraints!\n");
break;
}
if (result == kOptimal) {
const WindowAreaList& areas = fGroup->GetAreaList();
for (int32 i = 0; i < areas.CountItems(); i++) {
WindowArea* area = areas.ItemAt(i);
area->_MoveToSAT(parentWindow);
}
break;
}
}
fWidthConstraint->SetPenaltyNeg(kExtentPenalty);
fWidthConstraint->SetPenaltyPos(kExtentPenalty);
fHeightConstraint->SetPenaltyNeg(kExtentPenalty);
fHeightConstraint->SetPenaltyPos(kExtentPenalty);
linearSpec->RemoveConstraint(leftConstraint);
linearSpec->RemoveConstraint(topConstraint);
}
void
WindowArea::UpdateSizeLimits()
{
_UpdateConstraintValues();
}
void
WindowArea::UpdateSizeConstaints(const BRect& frame)
{
fWidthConstraint->SetRightSide(frame.Width());
fHeightConstraint->SetRightSide(frame.Height());
}
bool
WindowArea::MoveWindowToPosition(SATWindow* window, int32 index)
{
int32 oldIndex = fWindowList.IndexOf(window);
ASSERT(oldIndex != index);
return fWindowList.MoveItem(oldIndex, index);
}
SATWindow*
WindowArea::TopWindow()
{
return fWindowLayerOrder.ItemAt(fWindowLayerOrder.CountItems() - 1);
}
void
WindowArea::_UpdateConstraintValues()
{
SATWindow* topWindow = TopWindow();
if (topWindow == NULL)
return;
int32 minWidth, maxWidth;
int32 minHeight, maxHeight;
SATWindow* window = fWindowList.ItemAt(0);
window->GetSizeLimits(&minWidth, &maxWidth, &minHeight, &maxHeight);
for (int32 i = 1; i < fWindowList.CountItems(); i++) {
window = fWindowList.ItemAt(i);
int32 minW, maxW;
int32 minH, maxH;
window->GetSizeLimits(&minW, &maxW, &minH, &maxH);
if (minWidth < minW)
minWidth = minW;
if (minHeight < minH)
minHeight = minH;
if (maxWidth < maxW)
maxWidth = maxW;
if (maxHeight < maxH)
maxHeight = maxH;
}
const int32 kMaxSolverValue = 5000;
if (minWidth > kMaxSolverValue)
minWidth = kMaxSolverValue;
if (minHeight > kMaxSolverValue)
minHeight = kMaxSolverValue;
if (maxWidth > kMaxSolverValue)
maxWidth = kMaxSolverValue;
if (maxHeight > kMaxSolverValue)
maxHeight = kMaxSolverValue;
topWindow->AddDecorator(&minWidth, &maxWidth, &minHeight, &maxHeight);
fMinWidthConstraint->SetRightSide(minWidth);
fMinHeightConstraint->SetRightSide(minHeight);
fMaxWidthConstraint->SetRightSide(maxWidth);
fMaxHeightConstraint->SetRightSide(maxHeight);
BRect frame = topWindow->CompleteWindowFrame();
fWidthConstraint->SetRightSide(frame.Width());
fHeightConstraint->SetRightSide(frame.Height());
}
bool
WindowArea::_AddWindow(SATWindow* window, SATWindow* after)
{
if (after) {
int32 indexAfter = fWindowList.IndexOf(after);
if (!fWindowList.AddItem(window, indexAfter + 1))
return false;
} else if (fWindowList.AddItem(window) == false)
return false;
AcquireReference();
if (fWindowList.CountItems() <= 1)
_InitCorners();
fWindowLayerOrder.AddItem(window);
_UpdateConstraintValues();
return true;
}
bool
WindowArea::_RemoveWindow(SATWindow* window)
{
if (!fWindowList.RemoveItem(window))
return false;
fWindowLayerOrder.RemoveItem(window);
_UpdateConstraintValues();
window->RemovedFromArea(this);
ReleaseReference();
return true;
}
Tab*
WindowArea::LeftTab()
{
return fLeftTopCrossing->VerticalTab();
}
Tab*
WindowArea::RightTab()
{
return fRightBottomCrossing->VerticalTab();
}
Tab*
WindowArea::TopTab()
{
return fLeftTopCrossing->HorizontalTab();
}
Tab*
WindowArea::BottomTab()
{
return fRightBottomCrossing->HorizontalTab();
}
BRect
WindowArea::Frame()
{
return BRect(fLeftTopCrossing->VerticalTab()->Position(),
fLeftTopCrossing->HorizontalTab()->Position(),
fRightBottomCrossing->VerticalTab()->Position(),
fRightBottomCrossing->HorizontalTab()->Position());
}
bool
WindowArea::PropagateToGroup(SATGroup* group)
{
BReference<Crossing> newLeftTop = _CrossingByPosition(fLeftTopCrossing,
group);
BReference<Crossing> newRightTop = _CrossingByPosition(fRightTopCrossing,
group);
BReference<Crossing> newLeftBottom = _CrossingByPosition(
fLeftBottomCrossing, group);
BReference<Crossing> newRightBottom = _CrossingByPosition(
fRightBottomCrossing, group);
if (!newLeftTop || !newRightTop || !newLeftBottom || !newRightBottom)
return false;
BReference<Crossing> oldLeftTop = fLeftTopCrossing;
BReference<Crossing> oldRightTop = fRightTopCrossing;
BReference<Crossing> oldLeftBottom = fLeftBottomCrossing;
BReference<Crossing> oldRightBottom = fRightBottomCrossing;
fLeftTopCrossing = newLeftTop;
fRightTopCrossing = newRightTop;
fLeftBottomCrossing = newLeftBottom;
fRightBottomCrossing = newRightBottom;
_InitCorners();
BReference<SATGroup> oldGroup = fGroup;
if (Init(group) == false)
return false;
oldGroup->fWindowAreaList.RemoveItem(this);
for (int32 i = 0; i < fWindowList.CountItems(); i++) {
SATWindow* window = fWindowList.ItemAt(i);
if (oldGroup->fSATWindowList.RemoveItem(window) == false)
return false;
if (group->fSATWindowList.AddItem(window) == false) {
_UninitConstraints();
return false;
}
}
_UpdateConstraintValues();
return true;
}
bool
WindowArea::MoveToTopLayer(SATWindow* window)
{
if (!fWindowLayerOrder.RemoveItem(window))
return false;
return fWindowLayerOrder.AddItem(window);
}
void
WindowArea::_UninitConstraints()
{
if (fGroup != NULL) {
LinearSpec* linearSpec = fGroup->GetLinearSpec();
if (linearSpec != NULL) {
linearSpec->RemoveConstraint(fMinWidthConstraint, true);
linearSpec->RemoveConstraint(fMinHeightConstraint, true);
linearSpec->RemoveConstraint(fMaxWidthConstraint, true);
linearSpec->RemoveConstraint(fMaxHeightConstraint, true);
linearSpec->RemoveConstraint(fWidthConstraint, true);
linearSpec->RemoveConstraint(fHeightConstraint, true);
}
}
fMinWidthConstraint = NULL;
fMinHeightConstraint = NULL;
fMaxWidthConstraint = NULL;
fMaxHeightConstraint = NULL;
fWidthConstraint = NULL;
fHeightConstraint = NULL;
}
BReference<Crossing>
WindowArea::_CrossingByPosition(Crossing* crossing, SATGroup* group)
{
BReference<Crossing> crossRef = NULL;
Tab* oldHTab = crossing->HorizontalTab();
BReference<Tab> hTab = group->FindHorizontalTab(oldHTab->Position());
if (!hTab)
hTab = group->_AddHorizontalTab(oldHTab->Position());
if (!hTab)
return crossRef;
Tab* oldVTab = crossing->VerticalTab();
crossRef = hTab->FindCrossing(oldVTab->Position());
if (crossRef)
return crossRef;
BReference<Tab> vTab = group->FindVerticalTab(oldVTab->Position());
if (!vTab)
vTab = group->_AddVerticalTab(oldVTab->Position());
if (!vTab)
return crossRef;
return hTab->AddCrossing(vTab);
}
void
WindowArea::_InitCorners()
{
_SetToWindowCorner(fLeftTopCrossing->RightBottomCorner());
_SetToNeighbourCorner(fLeftTopCrossing->LeftBottomCorner());
_SetToNeighbourCorner(fLeftTopCrossing->RightTopCorner());
_SetToWindowCorner(fRightTopCrossing->LeftBottomCorner());
_SetToNeighbourCorner(fRightTopCrossing->LeftTopCorner());
_SetToNeighbourCorner(fRightTopCrossing->RightBottomCorner());
_SetToWindowCorner(fLeftBottomCrossing->RightTopCorner());
_SetToNeighbourCorner(fLeftBottomCrossing->LeftTopCorner());
_SetToNeighbourCorner(fLeftBottomCrossing->RightBottomCorner());
_SetToWindowCorner(fRightBottomCrossing->LeftTopCorner());
_SetToNeighbourCorner(fRightBottomCrossing->LeftBottomCorner());
_SetToNeighbourCorner(fRightBottomCrossing->RightTopCorner());
}
void
WindowArea::_CleanupCorners()
{
_UnsetWindowCorner(fLeftTopCrossing->RightBottomCorner());
_UnsetNeighbourCorner(fLeftTopCrossing->LeftBottomCorner(),
fLeftBottomCrossing->LeftTopCorner());
_UnsetNeighbourCorner(fLeftTopCrossing->RightTopCorner(),
fLeftBottomCrossing->LeftTopCorner());
_UnsetWindowCorner(fRightTopCrossing->LeftBottomCorner());
_UnsetNeighbourCorner(fRightTopCrossing->LeftTopCorner(),
fLeftBottomCrossing->RightTopCorner());
_UnsetNeighbourCorner(fRightTopCrossing->RightBottomCorner(),
fLeftBottomCrossing->RightTopCorner());
_UnsetWindowCorner(fLeftBottomCrossing->RightTopCorner());
_UnsetNeighbourCorner(fLeftBottomCrossing->LeftTopCorner(),
fLeftBottomCrossing->LeftBottomCorner());
_UnsetNeighbourCorner(fLeftBottomCrossing->RightBottomCorner(),
fLeftBottomCrossing->LeftBottomCorner());
_UnsetWindowCorner(fRightBottomCrossing->LeftTopCorner());
_UnsetNeighbourCorner(fRightBottomCrossing->LeftBottomCorner(),
fRightBottomCrossing->RightBottomCorner());
_UnsetNeighbourCorner(fRightBottomCrossing->RightTopCorner(),
fRightBottomCrossing->RightBottomCorner());
}
void
WindowArea::_SetToWindowCorner(Corner* corner)
{
corner->status = Corner::kUsed;
corner->windowArea = this;
}
void
WindowArea::_SetToNeighbourCorner(Corner* neighbour)
{
if (neighbour->status == Corner::kNotDockable)
neighbour->status = Corner::kFree;
}
void
WindowArea::_UnsetWindowCorner(Corner* corner)
{
corner->status = Corner::kFree;
corner->windowArea = NULL;
}
void
WindowArea::_UnsetNeighbourCorner(Corner* neighbour, Corner* opponent)
{
if (neighbour->status == Corner::kFree && opponent->status != Corner::kUsed)
neighbour->status = Corner::kNotDockable;
}
void
WindowArea::_MoveToSAT(SATWindow* triggerWindow)
{
SATWindow* topWindow = TopWindow();
if (topWindow == NULL)
return;
BRect frameSAT(LeftVar()->Value() - kMakePositiveOffset,
TopVar()->Value() - kMakePositiveOffset,
RightVar()->Value() - kMakePositiveOffset,
BottomVar()->Value() - kMakePositiveOffset);
topWindow->AdjustSizeLimits(frameSAT);
BRect frame = topWindow->CompleteWindowFrame();
float deltaToX = round(frameSAT.left - frame.left);
float deltaToY = round(frameSAT.top - frame.top);
frame.OffsetBy(deltaToX, deltaToY);
float deltaByX = round(frameSAT.right - frame.right);
float deltaByY = round(frameSAT.bottom - frame.bottom);
int32 workspace = triggerWindow->GetWindow()->CurrentWorkspace();
if (workspace < 0)
workspace = triggerWindow->GetWindow()->PriorWorkspace();
Desktop* desktop = triggerWindow->GetWindow()->Desktop();
desktop->MoveWindowBy(topWindow->GetWindow(), deltaToX, deltaToY, workspace);
desktop->ResizeWindowBy(topWindow->GetWindow(), deltaByX, deltaByY);
UpdateSizeConstaints(frameSAT);
}
Corner::Corner()
:
status(kNotDockable),
windowArea(NULL)
{
}
void
Corner::Trace() const
{
switch (status) {
case kFree:
debug_printf("free corner\n");
break;
case kUsed:
{
debug_printf("attached windows:\n");
const SATWindowList& list = windowArea->WindowList();
for (int i = 0; i < list.CountItems(); i++) {
debug_printf("- %s\n", list.ItemAt(i)->GetWindow()->Title());
}
break;
}
case kNotDockable:
debug_printf("not dockable\n");
break;
};
}
Crossing::Crossing(Tab* vertical, Tab* horizontal)
:
fVerticalTab(vertical),
fHorizontalTab(horizontal)
{
}
Crossing::~Crossing()
{
fVerticalTab->RemoveCrossing(this);
fHorizontalTab->RemoveCrossing(this);
}
Corner*
Crossing::GetCorner(Corner::position_t corner) const
{
return &const_cast<Corner*>(fCorners)[corner];
}
Corner*
Crossing::GetOppositeCorner(Corner::position_t corner) const
{
return &const_cast<Corner*>(fCorners)[3 - corner];
}
Tab*
Crossing::VerticalTab() const
{
return fVerticalTab;
}
Tab*
Crossing::HorizontalTab() const
{
return fHorizontalTab;
}
void
Crossing::Trace() const
{
debug_printf("left-top corner: ");
fCorners[Corner::kLeftTop].Trace();
debug_printf("right-top corner: ");
fCorners[Corner::kRightTop].Trace();
debug_printf("left-bottom corner: ");
fCorners[Corner::kLeftBottom].Trace();
debug_printf("right-bottom corner: ");
fCorners[Corner::kRightBottom].Trace();
}
Tab::Tab(SATGroup* group, Variable* variable, orientation_t orientation)
:
fGroup(group),
fVariable(variable),
fOrientation(orientation)
{
}
Tab::~Tab()
{
if (fOrientation == kVertical)
fGroup->_RemoveVerticalTab(this);
else
fGroup->_RemoveHorizontalTab(this);
}
float
Tab::Position() const
{
return (float)fVariable->Value() - kMakePositiveOffset;
}
void
Tab::SetPosition(float position)
{
fVariable->SetValue(position + kMakePositiveOffset);
}
Tab::orientation_t
Tab::Orientation() const
{
return fOrientation;
}
Constraint*
Tab::Connect(Variable* variable)
{
return fVariable->IsEqual(variable);
}
BReference<Crossing>
Tab::AddCrossing(Tab* tab)
{
if (tab->Orientation() == fOrientation)
return NULL;
Tab* vTab = (fOrientation == kVertical) ? this : tab;
Tab* hTab = (fOrientation == kHorizontal) ? this : tab;
Crossing* crossing = new (std::nothrow)Crossing(vTab, hTab);
if (!crossing)
return NULL;
if (!fCrossingList.AddItem(crossing)) {
return NULL;
}
if (!tab->fCrossingList.AddItem(crossing)) {
fCrossingList.RemoveItem(crossing);
return NULL;
}
BReference<Crossing> crossingRef(crossing, true);
return crossingRef;
}
bool
Tab::RemoveCrossing(Crossing* crossing)
{
Tab* vTab = crossing->VerticalTab();
Tab* hTab = crossing->HorizontalTab();
if (vTab != this && hTab != this)
return false;
fCrossingList.RemoveItem(crossing);
return true;
}
int32
Tab::FindCrossingIndex(Tab* tab)
{
if (fOrientation == kVertical) {
for (int32 i = 0; i < fCrossingList.CountItems(); i++) {
if (fCrossingList.ItemAt(i)->HorizontalTab() == tab)
return i;
}
} else {
for (int32 i = 0; i < fCrossingList.CountItems(); i++) {
if (fCrossingList.ItemAt(i)->VerticalTab() == tab)
return i;
}
}
return -1;
}
int32
Tab::FindCrossingIndex(float pos)
{
if (fOrientation == kVertical) {
for (int32 i = 0; i < fCrossingList.CountItems(); i++) {
if (fabs(fCrossingList.ItemAt(i)->HorizontalTab()->Position() - pos)
< 0.0001)
return i;
}
} else {
for (int32 i = 0; i < fCrossingList.CountItems(); i++) {
if (fabs(fCrossingList.ItemAt(i)->VerticalTab()->Position() - pos)
< 0.0001)
return i;
}
}
return -1;
}
Crossing*
Tab::FindCrossing(Tab* tab)
{
return fCrossingList.ItemAt(FindCrossingIndex(tab));
}
Crossing*
Tab::FindCrossing(float tabPosition)
{
return fCrossingList.ItemAt(FindCrossingIndex(tabPosition));
}
const CrossingList*
Tab::GetCrossingList() const
{
return &fCrossingList;
}
int
Tab::CompareFunction(const Tab* tab1, const Tab* tab2)
{
if (tab1->Position() < tab2->Position())
return -1;
return 1;
}
SATGroup::SATGroup()
:
fLinearSpec(new(std::nothrow) LinearSpec()),
fHorizontalTabsSorted(false),
fVerticalTabsSorted(false),
fActiveWindow(NULL)
{
}
SATGroup::~SATGroup()
{
if (fSATWindowList.CountItems() > 0)
debugger("Deleting a SATGroup which is not empty");
fLinearSpec->ReleaseReference();
}
bool
SATGroup::AddWindow(SATWindow* window, Tab* left, Tab* top, Tab* right,
Tab* bottom)
{
STRACE_SAT("SATGroup::AddWindow\n");
BReference<Tab> leftRef, rightRef, topRef, bottomRef;
BReference<Crossing> leftTopRef, rightTopRef, leftBottomRef, rightBottomRef;
if (left != NULL && top != NULL)
leftTopRef = left->FindCrossing(top);
if (right != NULL && top != NULL)
rightTopRef = right->FindCrossing(top);
if (left != NULL && bottom != NULL)
leftBottomRef = left->FindCrossing(bottom);
if (right != NULL && bottom != NULL)
rightBottomRef = right->FindCrossing(bottom);
if (left == NULL) {
leftRef = _AddVerticalTab();
left = leftRef.Get();
}
if (top == NULL) {
topRef = _AddHorizontalTab();
top = topRef.Get();
}
if (right == NULL) {
rightRef = _AddVerticalTab();
right = rightRef.Get();
}
if (bottom == NULL) {
bottomRef = _AddHorizontalTab();
bottom = bottomRef.Get();
}
if (left == NULL || top == NULL || right == NULL || bottom == NULL)
return false;
if (leftTopRef == NULL) {
leftTopRef = left->AddCrossing(top);
if (leftTopRef == NULL)
return false;
}
if (!rightTopRef) {
rightTopRef = right->AddCrossing(top);
if (!rightTopRef)
return false;
}
if (!leftBottomRef) {
leftBottomRef = left->AddCrossing(bottom);
if (!leftBottomRef)
return false;
}
if (!rightBottomRef) {
rightBottomRef = right->AddCrossing(bottom);
if (!rightBottomRef)
return false;
}
WindowArea* area = new(std::nothrow) WindowArea(leftTopRef, rightTopRef,
leftBottomRef, rightBottomRef);
if (area == NULL)
return false;
if (area->Init(this) == false) {
delete area;
return false;
}
BReference<WindowArea> areaRef(area, true);
return AddWindow(window, area);
}
bool
SATGroup::AddWindow(SATWindow* window, WindowArea* area, SATWindow* after)
{
if (!area->_AddWindow(window, after))
return false;
if (!fSATWindowList.AddItem(window)) {
area->_RemoveWindow(window);
return false;
}
if (!window->AddedToGroup(this, area)) {
area->_RemoveWindow(window);
fSATWindowList.RemoveItem(window);
return false;
}
return true;
}
bool
SATGroup::RemoveWindow(SATWindow* window, bool stayBelowMouse)
{
if (!fSATWindowList.RemoveItem(window))
return false;
BReference<WindowArea> area = window->GetWindowArea();
if (area.IsSet())
area->_RemoveWindow(window);
window->RemovedFromGroup(this, stayBelowMouse);
if (CountItems() >= 2)
WindowAt(0)->DoGroupLayout();
return true;
}
int32
SATGroup::CountItems()
{
return fSATWindowList.CountItems();
}
SATWindow*
SATGroup::WindowAt(int32 index)
{
return fSATWindowList.ItemAt(index);
}
SATWindow*
SATGroup::ActiveWindow() const
{
return fActiveWindow;
}
void
SATGroup::SetActiveWindow(SATWindow* window)
{
fActiveWindow = window;
}
const TabList*
SATGroup::HorizontalTabs()
{
if (!fHorizontalTabsSorted) {
fHorizontalTabs.SortItems(Tab::CompareFunction);
fHorizontalTabsSorted = true;
}
return &fHorizontalTabs;
}
const TabList*
SATGroup::VerticalTabs()
{
if (!fVerticalTabsSorted) {
fVerticalTabs.SortItems(Tab::CompareFunction);
fVerticalTabsSorted = true;
}
return &fVerticalTabs;
}
Tab*
SATGroup::FindHorizontalTab(float position)
{
return _FindTab(fHorizontalTabs, position);
}
Tab*
SATGroup::FindVerticalTab(float position)
{
return _FindTab(fVerticalTabs, position);
}
void
SATGroup::WindowAreaRemoved(WindowArea* area)
{
_SplitGroupIfNecessary(area);
}
status_t
SATGroup::RestoreGroup(const BMessage& archive, StackAndTile* sat)
{
SATGroup* group = new (std::nothrow)SATGroup;
if (group == NULL)
return B_NO_MEMORY;
BReference<SATGroup> groupRef;
groupRef.SetTo(group, true);
int32 nHTabs, nVTabs;
status_t status;
status = archive.FindInt32("htab_count", &nHTabs);
if (status != B_OK)
return status;
status = archive.FindInt32("vtab_count", &nVTabs);
if (status != B_OK)
return status;
vector<BReference<Tab> > tempHTabs;
for (int i = 0; i < nHTabs; i++) {
BReference<Tab> tab = group->_AddHorizontalTab();
if (!tab)
return B_NO_MEMORY;
tempHTabs.push_back(tab);
}
vector<BReference<Tab> > tempVTabs;
for (int i = 0; i < nVTabs; i++) {
BReference<Tab> tab = group->_AddVerticalTab();
if (!tab)
return B_NO_MEMORY;
tempVTabs.push_back(tab);
}
BMessage areaArchive;
for (int32 i = 0; archive.FindMessage("area", i, &areaArchive) == B_OK;
i++) {
uint32 leftTab, rightTab, topTab, bottomTab;
if (areaArchive.FindInt32("left_tab", (int32*)&leftTab) != B_OK
|| areaArchive.FindInt32("right_tab", (int32*)&rightTab) != B_OK
|| areaArchive.FindInt32("top_tab", (int32*)&topTab) != B_OK
|| areaArchive.FindInt32("bottom_tab", (int32*)&bottomTab) != B_OK)
return B_ERROR;
if (leftTab >= tempVTabs.size() || rightTab >= tempVTabs.size())
return B_BAD_VALUE;
if (topTab >= tempHTabs.size() || bottomTab >= tempHTabs.size())
return B_BAD_VALUE;
Tab* left = tempVTabs[leftTab];
Tab* right = tempVTabs[rightTab];
Tab* top = tempHTabs[topTab];
Tab* bottom = tempHTabs[bottomTab];
uint64 windowId;
SATWindow* prevWindow = NULL;
for (int32 i = 0; areaArchive.FindInt64("window", i,
(int64*)&windowId) == B_OK; i++) {
SATWindow* window = sat->FindSATWindow(windowId);
if (!window)
continue;
if (prevWindow == NULL) {
if (!group->AddWindow(window, left, top, right, bottom))
continue;
prevWindow = window;
} else {
if (!prevWindow->StackWindow(window))
continue;
prevWindow = window;
}
}
}
return B_OK;
}
status_t
SATGroup::ArchiveGroup(BMessage& archive)
{
archive.AddInt32("htab_count", fHorizontalTabs.CountItems());
archive.AddInt32("vtab_count", fVerticalTabs.CountItems());
for (int i = 0; i < fWindowAreaList.CountItems(); i++) {
WindowArea* area = fWindowAreaList.ItemAt(i);
int32 leftTab = fVerticalTabs.IndexOf(area->LeftTab());
int32 rightTab = fVerticalTabs.IndexOf(area->RightTab());
int32 topTab = fHorizontalTabs.IndexOf(area->TopTab());
int32 bottomTab = fHorizontalTabs.IndexOf(area->BottomTab());
BMessage areaMessage;
areaMessage.AddInt32("left_tab", leftTab);
areaMessage.AddInt32("right_tab", rightTab);
areaMessage.AddInt32("top_tab", topTab);
areaMessage.AddInt32("bottom_tab", bottomTab);
const SATWindowList& windowList = area->WindowList();
for (int a = 0; a < windowList.CountItems(); a++)
areaMessage.AddInt64("window", windowList.ItemAt(a)->Id());
archive.AddMessage("area", &areaMessage);
}
return B_OK;
}
BReference<Tab>
SATGroup::_AddHorizontalTab(float position)
{
if (fLinearSpec == NULL)
return NULL;
Variable* variable = fLinearSpec->AddVariable();
if (variable == NULL)
return NULL;
Tab* tab = new (std::nothrow)Tab(this, variable, Tab::kHorizontal);
if (tab == NULL)
return NULL;
BReference<Tab> tabRef(tab, true);
if (!fHorizontalTabs.AddItem(tab))
return NULL;
fHorizontalTabsSorted = false;
tabRef->SetPosition(position);
return tabRef;
}
BReference<Tab>
SATGroup::_AddVerticalTab(float position)
{
if (fLinearSpec == NULL)
return NULL;
Variable* variable = fLinearSpec->AddVariable();
if (variable == NULL)
return NULL;
Tab* tab = new (std::nothrow)Tab(this, variable, Tab::kVertical);
if (tab == NULL)
return NULL;
BReference<Tab> tabRef(tab, true);
if (!fVerticalTabs.AddItem(tab))
return NULL;
fVerticalTabsSorted = false;
tabRef->SetPosition(position);
return tabRef;
}
bool
SATGroup::_RemoveHorizontalTab(Tab* tab)
{
if (!fHorizontalTabs.RemoveItem(tab))
return false;
fHorizontalTabsSorted = false;
return true;
}
bool
SATGroup::_RemoveVerticalTab(Tab* tab)
{
if (!fVerticalTabs.RemoveItem(tab))
return false;
fVerticalTabsSorted = false;
return true;
}
Tab*
SATGroup::_FindTab(const TabList& list, float position)
{
for (int i = 0; i < list.CountItems(); i++)
if (fabs(list.ItemAt(i)->Position() - position) < 0.00001)
return list.ItemAt(i);
return NULL;
}
void
SATGroup::_SplitGroupIfNecessary(WindowArea* removedArea)
{
if (removedArea == NULL || removedArea->WindowList().CountItems() > 1)
return;
WindowAreaList neighbourWindows;
_FillNeighbourList(neighbourWindows, removedArea);
bool ownGroupProcessed = false;
WindowAreaList newGroup;
while (_FindConnectedGroup(neighbourWindows, removedArea, newGroup)) {
STRACE_SAT("Connected group found; %i window(s)\n",
(int)newGroup.CountItems());
if (newGroup.CountItems() == 1
&& newGroup.ItemAt(0)->WindowList().CountItems() == 1) {
SATWindow* window = newGroup.ItemAt(0)->WindowList().ItemAt(0);
RemoveWindow(window);
_EnsureGroupIsOnScreen(window->GetGroup());
} else if (ownGroupProcessed)
_SpawnNewGroup(newGroup);
else {
_EnsureGroupIsOnScreen(this);
ownGroupProcessed = true;
}
newGroup.MakeEmpty();
}
}
void
SATGroup::_FillNeighbourList(WindowAreaList& neighbourWindows,
WindowArea* area)
{
_LeftNeighbours(neighbourWindows, area);
_RightNeighbours(neighbourWindows, area);
_TopNeighbours(neighbourWindows, area);
_BottomNeighbours(neighbourWindows, area);
}
void
SATGroup::_LeftNeighbours(WindowAreaList& neighbourWindows, WindowArea* parent)
{
float startPos = parent->LeftTopCrossing()->HorizontalTab()->Position();
float endPos = parent->LeftBottomCrossing()->HorizontalTab()->Position();
Tab* tab = parent->LeftTopCrossing()->VerticalTab();
const CrossingList* crossingList = tab->GetCrossingList();
for (int i = 0; i < crossingList->CountItems(); i++) {
Corner* corner = crossingList->ItemAt(i)->LeftTopCorner();
if (corner->status != Corner::kUsed)
continue;
WindowArea* area = corner->windowArea;
float pos1 = area->LeftTopCrossing()->HorizontalTab()->Position();
float pos2 = area->LeftBottomCrossing()->HorizontalTab()->Position();
if (pos1 < endPos && pos2 > startPos)
neighbourWindows.AddItem(area);
if (pos2 > endPos)
break;
}
}
void
SATGroup::_TopNeighbours(WindowAreaList& neighbourWindows, WindowArea* parent)
{
float startPos = parent->LeftTopCrossing()->VerticalTab()->Position();
float endPos = parent->RightTopCrossing()->VerticalTab()->Position();
Tab* tab = parent->LeftTopCrossing()->HorizontalTab();
const CrossingList* crossingList = tab->GetCrossingList();
for (int i = 0; i < crossingList->CountItems(); i++) {
Corner* corner = crossingList->ItemAt(i)->LeftTopCorner();
if (corner->status != Corner::kUsed)
continue;
WindowArea* area = corner->windowArea;
float pos1 = area->LeftTopCrossing()->VerticalTab()->Position();
float pos2 = area->RightTopCrossing()->VerticalTab()->Position();
if (pos1 < endPos && pos2 > startPos)
neighbourWindows.AddItem(area);
if (pos2 > endPos)
break;
}
}
void
SATGroup::_RightNeighbours(WindowAreaList& neighbourWindows, WindowArea* parent)
{
float startPos = parent->RightTopCrossing()->HorizontalTab()->Position();
float endPos = parent->RightBottomCrossing()->HorizontalTab()->Position();
Tab* tab = parent->RightTopCrossing()->VerticalTab();
const CrossingList* crossingList = tab->GetCrossingList();
for (int i = 0; i < crossingList->CountItems(); i++) {
Corner* corner = crossingList->ItemAt(i)->RightTopCorner();
if (corner->status != Corner::kUsed)
continue;
WindowArea* area = corner->windowArea;
float pos1 = area->RightTopCrossing()->HorizontalTab()->Position();
float pos2 = area->RightBottomCrossing()->HorizontalTab()->Position();
if (pos1 < endPos && pos2 > startPos)
neighbourWindows.AddItem(area);
if (pos2 > endPos)
break;
}
}
void
SATGroup::_BottomNeighbours(WindowAreaList& neighbourWindows,
WindowArea* parent)
{
float startPos = parent->LeftBottomCrossing()->VerticalTab()->Position();
float endPos = parent->RightBottomCrossing()->VerticalTab()->Position();
Tab* tab = parent->LeftBottomCrossing()->HorizontalTab();
const CrossingList* crossingList = tab->GetCrossingList();
for (int i = 0; i < crossingList->CountItems(); i++) {
Corner* corner = crossingList->ItemAt(i)->LeftBottomCorner();
if (corner->status != Corner::kUsed)
continue;
WindowArea* area = corner->windowArea;
float pos1 = area->LeftBottomCrossing()->VerticalTab()->Position();
float pos2 = area->RightBottomCrossing()->VerticalTab()->Position();
if (pos1 < endPos && pos2 > startPos)
neighbourWindows.AddItem(area);
if (pos2 > endPos)
break;
}
}
bool
SATGroup::_FindConnectedGroup(WindowAreaList& seedList, WindowArea* removedArea,
WindowAreaList& newGroup)
{
if (seedList.CountItems() == 0)
return false;
WindowArea* area = seedList.RemoveItemAt(0);
newGroup.AddItem(area);
_FollowSeed(area, removedArea, seedList, newGroup);
return true;
}
void
SATGroup::_FollowSeed(WindowArea* area, WindowArea* veto,
WindowAreaList& seedList, WindowAreaList& newGroup)
{
WindowAreaList neighbours;
_FillNeighbourList(neighbours, area);
for (int i = 0; i < neighbours.CountItems(); i++) {
WindowArea* currentArea = neighbours.ItemAt(i);
if (currentArea != veto && !newGroup.HasItem(currentArea)) {
newGroup.AddItem(currentArea);
seedList.RemoveItem(currentArea);
} else {
neighbours.RemoveItemAt(i);
i--;
}
}
for (int i = 0; i < neighbours.CountItems(); i++)
_FollowSeed(neighbours.ItemAt(i), veto, seedList, newGroup);
}
void
SATGroup::_SpawnNewGroup(const WindowAreaList& newGroup)
{
STRACE_SAT("SATGroup::_SpawnNewGroup\n");
SATGroup* group = new (std::nothrow)SATGroup;
if (group == NULL)
return;
BReference<SATGroup> groupRef;
groupRef.SetTo(group, true);
for (int i = 0; i < newGroup.CountItems(); i++)
newGroup.ItemAt(i)->PropagateToGroup(group);
_EnsureGroupIsOnScreen(group);
}
const float kMinOverlap = 50;
const float kMoveToScreen = 75;
void
SATGroup::_EnsureGroupIsOnScreen(SATGroup* group)
{
STRACE_SAT("SATGroup::_EnsureGroupIsOnScreen\n");
if (group == NULL || group->CountItems() < 1)
return;
SATWindow* window = group->WindowAt(0);
Desktop* desktop = window->GetWindow()->Desktop();
if (desktop == NULL)
return;
const float kBigDistance = 1E+10;
float minLeftDistance = kBigDistance;
BRect leftRect;
float minTopDistance = kBigDistance;
BRect topRect;
float minRightDistance = kBigDistance;
BRect rightRect;
float minBottomDistance = kBigDistance;
BRect bottomRect;
BRect screen = window->GetWindow()->Screen()->Frame();
BRect reducedScreen = screen;
reducedScreen.InsetBy(kMinOverlap, kMinOverlap);
for (int i = 0; i < group->CountItems(); i++) {
SATWindow* window = group->WindowAt(i);
BRect frame = window->CompleteWindowFrame();
if (reducedScreen.Intersects(frame))
return;
if (frame.right < screen.left + kMinOverlap) {
float dist = fabs(screen.left - frame.right);
if (dist < minLeftDistance) {
minLeftDistance = dist;
leftRect = frame;
} else if (dist == minLeftDistance)
leftRect = leftRect | frame;
}
if (frame.top > screen.bottom - kMinOverlap) {
float dist = fabs(frame.top - screen.bottom);
if (dist < minBottomDistance) {
minBottomDistance = dist;
bottomRect = frame;
} else if (dist == minBottomDistance)
bottomRect = bottomRect | frame;
}
if (frame.left > screen.right - kMinOverlap) {
float dist = fabs(frame.left - screen.right);
if (dist < minRightDistance) {
minRightDistance = dist;
rightRect = frame;
} else if (dist == minRightDistance)
rightRect = rightRect | frame;
}
if (frame.bottom < screen.top + kMinOverlap) {
float dist = fabs(frame.bottom - screen.top);
if (dist < minTopDistance) {
minTopDistance = dist;
topRect = frame;
} else if (dist == minTopDistance)
topRect = topRect | frame;
}
}
BPoint offset;
if (minLeftDistance < kBigDistance) {
offset.x = screen.left - leftRect.right + kMoveToScreen;
_CallculateYOffset(offset, leftRect, screen);
} else if (minTopDistance < kBigDistance) {
offset.y = screen.top - topRect.bottom + kMoveToScreen;
_CallculateXOffset(offset, topRect, screen);
} else if (minRightDistance < kBigDistance) {
offset.x = screen.right - rightRect.left - kMoveToScreen;
_CallculateYOffset(offset, rightRect, screen);
} else if (minBottomDistance < kBigDistance) {
offset.y = screen.bottom - bottomRect.top - kMoveToScreen;
_CallculateXOffset(offset, bottomRect, screen);
}
if (offset.x == 0. && offset.y == 0.)
return;
STRACE_SAT("move group back to screen: offset x: %f offset y: %f\n",
offset.x, offset.y);
desktop->MoveWindowBy(window->GetWindow(), offset.x, offset.y);
window->DoGroupLayout();
}
void
SATGroup::_CallculateXOffset(BPoint& offset, BRect& frame, BRect& screen)
{
if (frame.right < screen.left + kMinOverlap)
offset.x = screen.left - frame.right + kMoveToScreen;
else if (frame.left > screen.right - kMinOverlap)
offset.x = screen.right - frame.left - kMoveToScreen;
}
void
SATGroup::_CallculateYOffset(BPoint& offset, BRect& frame, BRect& screen)
{
if (frame.top > screen.bottom - kMinOverlap)
offset.y = screen.bottom - frame.top - kMoveToScreen;
else if (frame.bottom < screen.top + kMinOverlap)
offset.y = screen.top - frame.bottom + kMoveToScreen;
}