root/src/servers/app/stackandtile/SATWindow.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 "SATWindow.h"

#include <Debug.h>

#include "StackAndTilePrivate.h"

#include "Desktop.h"
#include "SATGroup.h"
#include "ServerApp.h"
#include "Window.h"


using namespace BPrivate;


// #pragma mark -


SATWindow::SATWindow(StackAndTile* sat, Window* window)
        :
        fWindow(window),
        fStackAndTile(sat),

        fWindowArea(NULL),

        fOngoingSnapping(NULL),
        fSATStacking(this),
        fSATTiling(this)
{
        fId = _GenerateId();

        fDesktop = fWindow->Desktop();

        // read initial limit values
        fWindow->GetSizeLimits(&fOriginalMinWidth, &fOriginalMaxWidth,
                &fOriginalMinHeight, &fOriginalMaxHeight);
        BRect frame = fWindow->Frame();
        fOriginalWidth = frame.Width();
        fOriginalHeight = frame.Height();

        fSATSnappingBehaviourList.AddItem(&fSATStacking);
        fSATSnappingBehaviourList.AddItem(&fSATTiling);
}


SATWindow::~SATWindow()
{
        if (fWindowArea != NULL)
                fWindowArea->Group()->RemoveWindow(this);
}


SATDecorator*
SATWindow::GetDecorator() const
{
        return static_cast<SATDecorator*>(fWindow->Decorator());
}


SATGroup*
SATWindow::GetGroup()
{
        if (fWindowArea == NULL) {
                SATGroup* group = new (std::nothrow)SATGroup;
                if (group == NULL)
                        return group;
                BReference<SATGroup> groupRef;
                groupRef.SetTo(group, true);

                /* AddWindow also will trigger the window to hold a reference on the new
                group. */
                if (group->AddWindow(this, NULL, NULL, NULL, NULL) == false)
                        return NULL;
        }

        ASSERT(fWindowArea != NULL);

         // manually set the tabs of the single window
        if (PositionManagedBySAT() == false) {
                BRect frame = CompleteWindowFrame();
                fWindowArea->LeftTopCrossing()->VerticalTab()->SetPosition(frame.left);
                fWindowArea->LeftTopCrossing()->HorizontalTab()->SetPosition(frame.top);
                fWindowArea->RightBottomCrossing()->VerticalTab()->SetPosition(
                        frame.right);
                fWindowArea->RightBottomCrossing()->HorizontalTab()->SetPosition(
                        frame.bottom);
        }

        return fWindowArea->Group();
}


bool
SATWindow::HandleMessage(SATWindow* sender, BPrivate::LinkReceiver& link,
        BPrivate::LinkSender& reply)
{
        int32 target;
        link.Read<int32>(&target);
        if (target == kStacking)
                return StackingEventHandler::HandleMessage(sender, link, reply);

        return false;
}


bool
SATWindow::PropagateToGroup(SATGroup* group)
{
        if (fWindowArea == NULL)
                return false;
        return fWindowArea->PropagateToGroup(group);
}


bool
SATWindow::AddedToGroup(SATGroup* group, WindowArea* area)
{
        STRACE_SAT("SATWindow::AddedToGroup group: %p window %s\n", group,
                fWindow->Title());
        fWindowArea = area;
        return true;
}


bool
SATWindow::RemovedFromGroup(SATGroup* group, bool stayBelowMouse)
{
        STRACE_SAT("SATWindow::RemovedFromGroup group: %p window %s\n", group,
                fWindow->Title());

        _RestoreOriginalSize(stayBelowMouse);
        if (group->CountItems() == 1)
                group->WindowAt(0)->_RestoreOriginalSize(false);

        return true;
}


bool
SATWindow::StackWindow(SATWindow* child)
{
        SATGroup* group = GetGroup();
        WindowArea* area = GetWindowArea();
        if (!group || !area)
                return false;

        if (group->AddWindow(child, area, this) == false)
                return false;

        DoGroupLayout();

        if (fWindow->AddWindowToStack(child->GetWindow()) == false) {
                group->RemoveWindow(child);
                DoGroupLayout();
                return false;
        }

        return true;
}


void
SATWindow::RemovedFromArea(WindowArea* area)
{
        SATDecorator* decorator = GetDecorator();
        if (decorator != NULL)
                fOldTabLocatiom = decorator->TabRect(fWindow->PositionInStack()).left;

        fWindow->DetachFromWindowStack(true);
        for (int i = 0; i < fSATSnappingBehaviourList.CountItems(); i++)
                fSATSnappingBehaviourList.ItemAt(i)->RemovedFromArea(area);

        fWindowArea = NULL;
}


void
SATWindow::WindowLookChanged(window_look look)
{
        for (int i = 0; i < fSATSnappingBehaviourList.CountItems(); i++)
                fSATSnappingBehaviourList.ItemAt(i)->WindowLookChanged(look);
}


void
SATWindow::FindSnappingCandidates()
{
        fOngoingSnapping = NULL;

        if (fWindow->Feel() != B_NORMAL_WINDOW_FEEL)
                return;

        GroupIterator groupIterator(fStackAndTile, GetWindow()->Desktop());
        for (SATGroup* group = groupIterator.NextGroup(); group;
                group = groupIterator.NextGroup()) {
                if (group->CountItems() == 1
                        && group->WindowAt(0)->GetWindow()->Feel() != B_NORMAL_WINDOW_FEEL)
                        continue;
                for (int i = 0; i < fSATSnappingBehaviourList.CountItems(); i++) {
                        if (fSATSnappingBehaviourList.ItemAt(i)->FindSnappingCandidates(
                                group)) {
                                fOngoingSnapping = fSATSnappingBehaviourList.ItemAt(i);
                                return;
                        }
                }
        }
}


bool
SATWindow::JoinCandidates()
{
        if (!fOngoingSnapping)
                return false;
        bool status = fOngoingSnapping->JoinCandidates();
        fOngoingSnapping = NULL;

        return status;
}


void
SATWindow::DoGroupLayout()
{
        if (!PositionManagedBySAT())
                return;

        if (fWindowArea != NULL)
                fWindowArea->DoGroupLayout();
}


void
SATWindow::AdjustSizeLimits(BRect targetFrame)
{
        SATDecorator* decorator = GetDecorator();
        if (decorator == NULL)
                return;

        targetFrame.right -= 2 * (int32)decorator->BorderWidth();
        targetFrame.bottom -= 2 * (int32)decorator->BorderWidth()
                + (int32)decorator->TabHeight() + 1;

        int32 minWidth, maxWidth;
        int32 minHeight, maxHeight;
        GetSizeLimits(&minWidth, &maxWidth, &minHeight, &maxHeight);

        if (maxWidth < targetFrame.Width())
                maxWidth = targetFrame.IntegerWidth();
        if (maxHeight < targetFrame.Height())
                maxHeight = targetFrame.IntegerHeight();

        fWindow->SetSizeLimits(minWidth, maxWidth, minHeight, maxHeight);
}


void
SATWindow::GetSizeLimits(int32* minWidth, int32* maxWidth, int32* minHeight,
        int32* maxHeight) const
{
        *minWidth = fOriginalMinWidth;
        *minHeight = fOriginalMinHeight;
        *maxWidth = fOriginalMaxWidth;
        *maxHeight = fOriginalMaxHeight;

        SATDecorator* decorator = GetDecorator();
        if (decorator == NULL)
                return;

        int32 minDecorWidth = 1, maxDecorWidth = 1;
        int32 minDecorHeight = 1, maxDecorHeight = 1;
        decorator->GetSizeLimits(&minDecorWidth, &minDecorHeight,
                &maxDecorWidth, &maxDecorHeight);

        // if no size limit is set but the window is not resizeable choose the
        // current size as limit
        if (IsHResizeable() == false && fOriginalMinWidth <= minDecorWidth)
                *minWidth = (int32)fOriginalWidth;
        if (IsVResizeable() == false && fOriginalMinHeight <= minDecorHeight)
                *minHeight = (int32)fOriginalHeight;

        if (*minWidth > *maxWidth)
                *maxWidth = *minWidth;
        if (*minHeight > *maxHeight)
                *maxHeight = *minHeight;
}


void
SATWindow::AddDecorator(int32* minWidth, int32* maxWidth, int32* minHeight,
        int32* maxHeight)
{
        SATDecorator* decorator = GetDecorator();
        if (decorator == NULL)
                return;

        *minWidth += 2 * (int32)decorator->BorderWidth();
        *minHeight += 2 * (int32)decorator->BorderWidth()
                + (int32)decorator->TabHeight() + 1;
        *maxWidth += 2 * (int32)decorator->BorderWidth();
        *maxHeight += 2 * (int32)decorator->BorderWidth()
                + (int32)decorator->TabHeight() + 1;
}


void
SATWindow::AddDecorator(BRect& frame)
{
        SATDecorator* decorator = GetDecorator();
        if (!decorator)
                return;
        frame.left -= decorator->BorderWidth();
        frame.right += decorator->BorderWidth() + 1;
        frame.top -= decorator->BorderWidth() + decorator->TabHeight() + 1;
        frame.bottom += decorator->BorderWidth();
}


void
SATWindow::SetOriginalSizeLimits(int32 minWidth, int32 maxWidth,
        int32 minHeight, int32 maxHeight)
{
        fOriginalMinWidth = minWidth;
        fOriginalMaxWidth = maxWidth;
        fOriginalMinHeight = minHeight;
        fOriginalMaxHeight = maxHeight;

        if (fWindowArea != NULL)
                fWindowArea->UpdateSizeLimits();
}


void
SATWindow::Resized()
{
        bool hResizeable = IsHResizeable();
        bool vResizeable = IsVResizeable();
        if (hResizeable == false && vResizeable == false)
                return;

        BRect frame = fWindow->Frame();
        if (hResizeable)
                fOriginalWidth = frame.Width();
        if (vResizeable)
                fOriginalHeight = frame.Height();

        if (fWindowArea != NULL)
                fWindowArea->UpdateSizeConstaints(CompleteWindowFrame());
}


bool
SATWindow::IsHResizeable() const
{
        if (fWindow->Look() == B_MODAL_WINDOW_LOOK
                || fWindow->Look() == B_BORDERED_WINDOW_LOOK
                || fWindow->Look() == B_NO_BORDER_WINDOW_LOOK
                || (fWindow->Flags() & B_NOT_RESIZABLE) != 0
                || (fWindow->Flags() & B_NOT_H_RESIZABLE) != 0)
                return false;
        return true;
}


bool
SATWindow::IsVResizeable() const
{
        if (fWindow->Look() == B_MODAL_WINDOW_LOOK
                || fWindow->Look() == B_BORDERED_WINDOW_LOOK
                || fWindow->Look() == B_NO_BORDER_WINDOW_LOOK
                || (fWindow->Flags() & B_NOT_RESIZABLE) != 0
                || (fWindow->Flags() & B_NOT_V_RESIZABLE) != 0)
                return false;
        return true;
}


BRect
SATWindow::CompleteWindowFrame()
{
        BRect frame = fWindow->Frame();
        if (fDesktop && fWindow->IsVisible()
                && fDesktop->CurrentWorkspace() != fWindow->CurrentWorkspace()) {
                window_anchor& anchor = fWindow->Anchor(fWindow->CurrentWorkspace());
                if (anchor.position != kInvalidWindowPosition)
                        frame.OffsetTo(anchor.position);
        } else if (fDesktop && !fWindow->IsVisible() && fWindow->PriorWorkspace() >= 0
                && fDesktop->CurrentWorkspace() != fWindow->PriorWorkspace()) {
                window_anchor& anchor = fWindow->Anchor(fWindow->PriorWorkspace());
                if (anchor.position != kInvalidWindowPosition)
                        frame.OffsetTo(anchor.position);
        }

        AddDecorator(frame);
        return frame;
}


bool
SATWindow::PositionManagedBySAT()
{
        if (fWindowArea == NULL || fWindowArea->Group()->CountItems() == 1)
                return false;

        return true;
}


bool
SATWindow::HighlightTab(bool active)
{
        SATDecorator* decorator = GetDecorator();
        if (!decorator)
                return false;

        int32 tabIndex = fWindow->PositionInStack();
        BRegion dirty;
        uint8 highlight = active ?  SATDecorator::HIGHLIGHT_STACK_AND_TILE : 0;
        decorator->SetRegionHighlight(Decorator::REGION_TAB, highlight, &dirty,
                tabIndex);
        decorator->SetRegionHighlight(Decorator::REGION_CLOSE_BUTTON, highlight,
                &dirty, tabIndex);
        decorator->SetRegionHighlight(Decorator::REGION_ZOOM_BUTTON, highlight,
                &dirty, tabIndex);

        fWindow->TopLayerStackWindow()->ProcessDirtyRegion(dirty);
        return true;
}


bool
SATWindow::HighlightBorders(Decorator::Region region, bool active)
{
        SATDecorator* decorator = GetDecorator();
        if (!decorator)
                return false;

        BRegion dirty;
        uint8 highlight = active ? SATDecorator::HIGHLIGHT_STACK_AND_TILE : 0;
        decorator->SetRegionHighlight(region, highlight, &dirty);

        fWindow->ProcessDirtyRegion(dirty);
        return true;
}


uint64
SATWindow::Id()
{
        return fId;
}


bool
SATWindow::SetSettings(const BMessage& message)
{
        uint64 id;
        if (message.FindInt64("window_id", (int64*)&id) != B_OK)
                return false;
        fId = id;
        return true;
}


void
SATWindow::GetSettings(BMessage& message)
{
        message.AddInt64("window_id", fId);
}


uint64
SATWindow::_GenerateId()
{
        bigtime_t time = real_time_clock_usecs();
        srand(time);
        int16 randNumber = rand();
        return (time & ~0xFFFF) | randNumber;
}


void
SATWindow::_RestoreOriginalSize(bool stayBelowMouse)
{
        // restore size
        fWindow->SetSizeLimits(fOriginalMinWidth, fOriginalMaxWidth,
                fOriginalMinHeight, fOriginalMaxHeight);
        BRect frame = fWindow->Frame();
        float x = 0, y = 0;
        if (IsHResizeable() == false)
                x = fOriginalWidth - frame.Width();
        if (IsVResizeable() == false)
                y = fOriginalHeight - frame.Height();
        fDesktop->ResizeWindowBy(fWindow, x, y);

        if (!stayBelowMouse)
                return;
        // verify that the window stays below the mouse
        BPoint mousePosition;
        int32 buttons;
        fDesktop->GetLastMouseState(&mousePosition, &buttons);
        SATDecorator* decorator = GetDecorator();
        if (decorator == NULL)
                return;
        BRect tabRect = decorator->TitleBarRect();
        if (mousePosition.y < tabRect.bottom && mousePosition.y > tabRect.top
                && mousePosition.x <= frame.right + decorator->BorderWidth() +1
                && mousePosition.x >= frame.left + decorator->BorderWidth()) {
                // verify mouse stays on the tab
                float oldOffset = mousePosition.x - fOldTabLocatiom;
                float deltaX = mousePosition.x - (tabRect.left + oldOffset);
                fDesktop->MoveWindowBy(fWindow, deltaX, 0);
        } else {
                // verify mouse stays on the border
                float deltaX = 0;
                float deltaY = 0;
                BRect newFrame = fWindow->Frame();
                if (x != 0 && mousePosition.x > frame.left
                        && mousePosition.x > newFrame.right) {
                        deltaX = mousePosition.x - newFrame.right;
                        if (mousePosition.x > frame.right)
                                deltaX -= mousePosition.x - frame.right;
                }
                if (y != 0 && mousePosition.y > frame.top
                        && mousePosition.y > newFrame.bottom) {
                        deltaY = mousePosition.y - newFrame.bottom;
                        if (mousePosition.y > frame.bottom)
                                deltaY -= mousePosition.y - frame.bottom;
                }
                        fDesktop->MoveWindowBy(fWindow, deltaX, deltaY);
        }
}