root/src/servers/app/stackandtile/SATGroup.cpp
/*
 * 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);

        // Width and height have soft constraints
        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();
        // Make it also work for solver which don't support negative variables
        frame.OffsetBy(kMakePositiveOffset, kMakePositiveOffset);

        // adjust window size soft constraints
        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);

        // give soft constraints a high penalty
        fWidthConstraint->SetPenaltyNeg(kHighPenalty);
        fWidthConstraint->SetPenaltyPos(kHighPenalty);
        fHeightConstraint->SetPenaltyNeg(kHighPenalty);
        fHeightConstraint->SetPenaltyPos(kHighPenalty);

        // After we set the new parameter solve and apply the new layout.
        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;
                }
        }

        // set penalties back to normal
        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)
{
        // adjust window size soft constraints
        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);
                // size limit constraints
                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;
        }
        // the current solver don't like big values
        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;

        // hold a ref to the crossings till we cleaned up everything
        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;
        // manage constraints
        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 there is no window in the group we are done
        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);
        // Update frame to the new position
        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()
{
        // Should be empty
        if (fSATWindowList.CountItems() > 0)
                debugger("Deleting a SATGroup which is not empty");
        //while (fSATWindowList.CountItems() > 0)
        //      RemoveWindow(fSATWindowList.ItemAt(0));

        fLinearSpec->ReleaseReference();
}


bool
SATGroup::AddWindow(SATWindow* window, Tab* left, Tab* top, Tab* right,
        Tab* bottom)
{
        STRACE_SAT("SATGroup::AddWindow\n");

        // first check if we have to create tabs and missing corners.
        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;
        // the area register itself in our area list
        if (area->Init(this) == false) {
                delete area;
                return false;
        }
        // delete the area if AddWindow failed / release our reference on it
        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;

        // We need the area a little bit longer because the area could hold the
        // last reference to the group.
        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)
{
        // create new group
        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];

                // adding windows to area
                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;
        // don't delete the tab it is reference counted
        return true;
}


bool
SATGroup::_RemoveVerticalTab(Tab* tab)
{
        if (!fVerticalTabs.RemoveItem(tab))
                return false;
        fVerticalTabsSorted = false;
        // don't delete the tab it is reference counted
        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 there are windows stacked in the area we don't need to split
        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);
                        // if we get a area from the seed list it is not a seed any more
                        seedList.RemoveItem(currentArea);
                } else {
                        // don't _FollowSeed of invalid areas
                        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;
}