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

#include <Debug.h>

#include "StackAndTilePrivate.h"

#include "Desktop.h"
#include "SATWindow.h"
#include "Tiling.h"
#include "Window.h"


static const int32 kRightOptionKey      = 0x67;
static const int32 kTabKey                      = 0x26;
static const int32 kPageUpKey           = 0x21;
static const int32 kPageDownKey         = 0x36;
static const int32 kLeftArrowKey        = 0x61;
static const int32 kUpArrowKey          = 0x57;
static const int32 kRightArrowKey       = 0x63;
static const int32 kDownArrowKey        = 0x62;

static const int32 kModifiers = B_SHIFT_KEY | B_COMMAND_KEY
        | B_CONTROL_KEY | B_OPTION_KEY | B_MENU_KEY;


using namespace std;


//      #pragma mark - StackAndTile


StackAndTile::StackAndTile()
        :
        fDesktop(NULL),
        fSATKeyPressed(false),
        fCurrentSATWindow(NULL)
{

}


StackAndTile::~StackAndTile()
{

}


int32
StackAndTile::Identifier()
{
        return BPrivate::kMagicSATIdentifier;
}


void
StackAndTile::ListenerRegistered(Desktop* desktop)
{
        fDesktop = desktop;

        WindowList& windows = desktop->AllWindows();
        for (Window *window = windows.FirstWindow(); window != NULL;
                        window = window->NextWindow(kAllWindowList))
                WindowAdded(window);
}


void
StackAndTile::ListenerUnregistered()
{
        for (SATWindowMap::iterator it = fSATWindowMap.begin();
                it != fSATWindowMap.end(); it++) {
                SATWindow* satWindow = it->second;
                delete satWindow;
        }
        fSATWindowMap.clear();
}


bool
StackAndTile::HandleMessage(Window* sender, BPrivate::LinkReceiver& link,
        BPrivate::LinkSender& reply)
{
        if (sender == NULL)
                return _HandleMessage(link, reply);

        SATWindow* satWindow = GetSATWindow(sender);
        if (!satWindow)
                return false;

        return satWindow->HandleMessage(satWindow, link, reply);
}


void
StackAndTile::WindowAdded(Window* window)
{
        SATWindow* satWindow = new (std::nothrow)SATWindow(this, window);
        if (!satWindow)
                return;

        ASSERT(fSATWindowMap.find(window) == fSATWindowMap.end());
        fSATWindowMap[window] = satWindow;
}


void
StackAndTile::WindowRemoved(Window* window)
{
        STRACE_SAT("StackAndTile::WindowRemoved %s\n", window->Title());

        SATWindowMap::iterator it = fSATWindowMap.find(window);
        if (it == fSATWindowMap.end())
                return;

        SATWindow* satWindow = it->second;
        // delete SATWindow
        delete satWindow;
        fSATWindowMap.erase(it);
}


bool
StackAndTile::KeyPressed(uint32 what, int32 key, int32 modifiers)
{
        if (what == B_MODIFIERS_CHANGED
                || (what == B_UNMAPPED_KEY_DOWN && key == kRightOptionKey)
                || (what == B_UNMAPPED_KEY_UP && key == kRightOptionKey)) {
                // switch to and from stacking and snapping mode
                bool wasPressed = fSATKeyPressed;
                fSATKeyPressed = (what == B_MODIFIERS_CHANGED
                                && (modifiers & kModifiers) == B_OPTION_KEY)
                        || (what == B_UNMAPPED_KEY_DOWN && key == kRightOptionKey);
                if (wasPressed && !fSATKeyPressed)
                        _StopSAT();
                if (!wasPressed && fSATKeyPressed)
                        _StartSAT();
        }

        if (!SATKeyPressed() || what != B_KEY_DOWN)
                return false;

        SATWindow* frontWindow = GetSATWindow(fDesktop->FocusWindow());
        SATGroup* currentGroup = _GetSATGroup(frontWindow);

        switch (key) {
                case kLeftArrowKey:
                case kRightArrowKey:
                case kTabKey:
                {
                        // go to previous or next window tab in current window group
                        if (currentGroup == NULL)
                                return false;

                        int32 groupSize = currentGroup->CountItems();
                        if (groupSize <= 1)
                                return false;

                        for (int32 i = 0; i < groupSize; i++) {
                                SATWindow* targetWindow = currentGroup->WindowAt(i);
                                if (targetWindow == frontWindow) {
                                        if (key == kLeftArrowKey
                                                || (key == kTabKey && (modifiers & B_SHIFT_KEY) != 0)) {
                                                // Go to previous window tab (wrap around)
                                                int32 previousIndex = i > 0 ? i - 1 : groupSize - 1;
                                                targetWindow = currentGroup->WindowAt(previousIndex);
                                        } else {
                                                // Go to next window tab (wrap around)
                                                int32 nextIndex = i < groupSize - 1 ? i + 1 : 0;
                                                targetWindow = currentGroup->WindowAt(nextIndex);
                                        }

                                        _ActivateWindow(targetWindow);
                                        return true;
                                }
                        }
                        break;
                }

                case kUpArrowKey:
                case kPageUpKey:
                {
                        // go to previous window group
                        GroupIterator groups(this, fDesktop);
                        groups.SetCurrentGroup(currentGroup);
                        SATGroup* backmostGroup = NULL;

                        while (true) {
                                SATGroup* group = groups.NextGroup();
                                if (group == NULL || group == currentGroup)
                                        break;
                                else if (group->CountItems() < 1)
                                        continue;

                                if (currentGroup == NULL) {
                                        SATWindow* activeWindow = group->ActiveWindow();
                                        if (activeWindow != NULL)
                                                _ActivateWindow(activeWindow);
                                        else
                                                _ActivateWindow(group->WindowAt(0));

                                        return true;
                                }
                                backmostGroup = group;
                        }
                        if (backmostGroup != NULL && backmostGroup != currentGroup) {
                                SATWindow* activeWindow = backmostGroup->ActiveWindow();
                                if (activeWindow != NULL)
                                        _ActivateWindow(activeWindow);
                                else
                                        _ActivateWindow(backmostGroup->WindowAt(0));

                                return true;
                        }

                        break;
                }

                case kDownArrowKey:
                case kPageDownKey:
                {
                        // go to next window group
                        GroupIterator groups(this, fDesktop);
                        groups.SetCurrentGroup(currentGroup);

                        while (true) {
                                SATGroup* group = groups.NextGroup();
                                if (group == NULL || group == currentGroup)
                                        break;
                                else if (group->CountItems() < 1)
                                        continue;

                                SATWindow* activeWindow = group->ActiveWindow();
                                if (activeWindow != NULL)
                                        _ActivateWindow(activeWindow);
                                else
                                        _ActivateWindow(group->WindowAt(0));

                                if (currentGroup != NULL && frontWindow != NULL) {
                                        Window* window = frontWindow->GetWindow();
                                        fDesktop->SendWindowBehind(window);
                                        WindowSentBehind(window, NULL);
                                }
                                return true;
                        }
                        break;
                }
        }

        return false;
}


void
StackAndTile::MouseDown(Window* window, BMessage* message, const BPoint& where)
{
        SATWindow* satWindow = GetSATWindow(window);
        if (!satWindow || !satWindow->GetDecorator())
                return;

        // fCurrentSATWindow is not zero if e.g. the secondary and the primary
        // mouse button are pressed at the same time
        if ((message->FindInt32("buttons") & B_PRIMARY_MOUSE_BUTTON) == 0 ||
                fCurrentSATWindow != NULL)
                return;

        // we are only interested in single clicks
        if (message->FindInt32("clicks") == 2)
                return;

        int32 tab;
        switch (satWindow->GetDecorator()->RegionAt(where, tab)) {
                case Decorator::REGION_TAB:
                case Decorator::REGION_LEFT_BORDER:
                case Decorator::REGION_RIGHT_BORDER:
                case Decorator::REGION_TOP_BORDER:
                case Decorator::REGION_BOTTOM_BORDER:
                case Decorator::REGION_LEFT_TOP_CORNER:
                case Decorator::REGION_LEFT_BOTTOM_CORNER:
                case Decorator::REGION_RIGHT_TOP_CORNER:
                case Decorator::REGION_RIGHT_BOTTOM_CORNER:
                        break;

                default:
                        return;
        }

        ASSERT(fCurrentSATWindow == NULL);
        fCurrentSATWindow = satWindow;

        if (!SATKeyPressed())
                return;

        _StartSAT();
}


void
StackAndTile::MouseUp(Window* window, BMessage* message, const BPoint& where)
{
        if (fSATKeyPressed)
                _StopSAT();

        fCurrentSATWindow = NULL;
}


void
StackAndTile::WindowMoved(Window* window)
{
        SATWindow* satWindow = GetSATWindow(window);
        if (satWindow == NULL)
                return;

        if (SATKeyPressed() && fCurrentSATWindow)
                satWindow->FindSnappingCandidates();
        else
                satWindow->DoGroupLayout();
}


void
StackAndTile::WindowResized(Window* window)
{
        SATWindow* satWindow = GetSATWindow(window);
        if (satWindow == NULL)
                return;
        satWindow->Resized();

        if (SATKeyPressed() && fCurrentSATWindow)
                satWindow->FindSnappingCandidates();
        else
                satWindow->DoGroupLayout();
}


void
StackAndTile::WindowActivated(Window* window)
{
        SATWindow* satWindow = GetSATWindow(window);
        if (satWindow == NULL)
                return;

        _ActivateWindow(satWindow);
}


void
StackAndTile::WindowSentBehind(Window* window, Window* behindOf)
{
        SATWindow* satWindow = GetSATWindow(window);
        if (satWindow == NULL)
                return;

        SATGroup* group = satWindow->GetGroup();
        if (group == NULL)
                return;

        Desktop* desktop = satWindow->GetWindow()->Desktop();
        if (desktop == NULL)
                return;

        const WindowAreaList& areaList = group->GetAreaList();
        for (int32 i = 0; i < areaList.CountItems(); i++) {
                WindowArea* area = areaList.ItemAt(i);
                SATWindow* topWindow = area->TopWindow();
                if (topWindow == NULL || topWindow == satWindow)
                        continue;
                desktop->SendWindowBehind(topWindow->GetWindow(), behindOf);
        }
}


void
StackAndTile::WindowWorkspacesChanged(Window* window, uint32 workspaces)
{
        SATWindow* satWindow = GetSATWindow(window);
        if (satWindow == NULL)
                return;

        SATGroup* group = satWindow->GetGroup();
        if (group == NULL)
                return;

        Desktop* desktop = satWindow->GetWindow()->Desktop();
        if (desktop == NULL)
                return;

        const WindowAreaList& areaList = group->GetAreaList();
        for (int32 i = 0; i < areaList.CountItems(); i++) {
                WindowArea* area = areaList.ItemAt(i);
                if (area->WindowList().HasItem(satWindow))
                        continue;
                SATWindow* topWindow = area->TopWindow();
                desktop->SetWindowWorkspaces(topWindow->GetWindow(), workspaces);
        }
}


void
StackAndTile::WindowHidden(Window* window, bool fromMinimize)
{
        SATWindow* satWindow = GetSATWindow(window);
        if (satWindow == NULL)
                return;

        SATGroup* group = satWindow->GetGroup();
        if (group == NULL)
                return;

        if (fromMinimize == false && group->CountItems() > 1)
                group->RemoveWindow(satWindow, false);
}


void
StackAndTile::WindowMinimized(Window* window, bool minimize)
{
        SATWindow* satWindow = GetSATWindow(window);
        if (satWindow == NULL)
                return;

        SATGroup* group = satWindow->GetGroup();
        if (group == NULL)
                return;

        Desktop* desktop = satWindow->GetWindow()->Desktop();
        if (desktop == NULL)
                return;

        for (int i = 0; i < group->CountItems(); i++) {
                SATWindow* listWindow = group->WindowAt(i);
                if (listWindow != satWindow)
                        listWindow->GetWindow()->ServerWindow()->NotifyMinimize(minimize);
        }
}


void
StackAndTile::WindowTabLocationChanged(Window* window, float location,
        bool isShifting)
{

}


void
StackAndTile::SizeLimitsChanged(Window* window, int32 minWidth, int32 maxWidth,
        int32 minHeight, int32 maxHeight)
{
        SATWindow* satWindow = GetSATWindow(window);
        if (!satWindow)
                return;
        satWindow->SetOriginalSizeLimits(minWidth, maxWidth, minHeight, maxHeight);

        // trigger a relayout
        WindowMoved(window);
}


void
StackAndTile::WindowLookChanged(Window* window, window_look look)
{
        SATWindow* satWindow = GetSATWindow(window);
        if (!satWindow)
                return;
        satWindow->WindowLookChanged(look);
}


void
StackAndTile::WindowFeelChanged(Window* window, window_feel feel)
{
        // check if it is still a compatible feel
        if (feel == B_NORMAL_WINDOW_FEEL)
                return;
        SATWindow* satWindow = GetSATWindow(window);
        if (satWindow == NULL)
                return;

        SATGroup* group = satWindow->GetGroup();
        if (group == NULL)
                return;

        if (group->CountItems() > 1)
                group->RemoveWindow(satWindow, false);
}


bool
StackAndTile::SetDecoratorSettings(Window* window, const BMessage& settings)
{
        SATWindow* satWindow = GetSATWindow(window);
        if (!satWindow)
                return false;

        return satWindow->SetSettings(settings);
}


void
StackAndTile::GetDecoratorSettings(Window* window, BMessage& settings)
{
        SATWindow* satWindow = GetSATWindow(window);
        if (!satWindow)
                return;

        satWindow->GetSettings(settings);
}


SATWindow*
StackAndTile::GetSATWindow(Window* window)
{
        if (window == NULL)
                return NULL;

        SATWindowMap::const_iterator it = fSATWindowMap.find(
                window);
        if (it != fSATWindowMap.end())
                return it->second;

        // TODO fix race condition with WindowAdded this method is called before
        // WindowAdded and a SATWindow is created twice!
        return NULL;

        // If we don't know this window, memory allocation might has been failed
        // previously. Try to add the window now.
        SATWindow* satWindow = new (std::nothrow)SATWindow(this, window);
        if (satWindow)
                fSATWindowMap[window] = satWindow;

        return satWindow;
}


SATWindow*
StackAndTile::FindSATWindow(uint64 id)
{
        for (SATWindowMap::const_iterator it = fSATWindowMap.begin();
                it != fSATWindowMap.end(); it++) {
                SATWindow* window = it->second;
                if (window->Id() == id)
                        return window;
        }

        return NULL;
}


//      #pragma mark - StackAndTile private methods


void
StackAndTile::_StartSAT()
{
        STRACE_SAT("StackAndTile::_StartSAT()\n");
        if (!fCurrentSATWindow)
                return;

        // Remove window from the group.
        SATGroup* group = fCurrentSATWindow->GetGroup();
        if (group == NULL)
                return;

        group->RemoveWindow(fCurrentSATWindow, false);
        // Bring window to the front. (in focus follow mouse this is not
        // automatically the case)
        _ActivateWindow(fCurrentSATWindow);

        fCurrentSATWindow->FindSnappingCandidates();
}


void
StackAndTile::_StopSAT()
{
        STRACE_SAT("StackAndTile::_StopSAT()\n");
        if (!fCurrentSATWindow)
                return;
        if (fCurrentSATWindow->JoinCandidates())
                _ActivateWindow(fCurrentSATWindow);
}


void
StackAndTile::_ActivateWindow(SATWindow* satWindow)
{
        if (satWindow == NULL)
                return;

        SATGroup* group = satWindow->GetGroup();
        if (group == NULL)
                return;

        Desktop* desktop = satWindow->GetWindow()->Desktop();
        if (desktop == NULL)
                return;

        WindowArea* area = satWindow->GetWindowArea();
        if (area == NULL)
                return;

        area->MoveToTopLayer(satWindow);

        // save the active window of the current group
        SATWindow* frontWindow = GetSATWindow(fDesktop->FocusWindow());
        SATGroup* currentGroup = _GetSATGroup(frontWindow);
        if (currentGroup != NULL && currentGroup != group && frontWindow != NULL)
                currentGroup->SetActiveWindow(frontWindow);
        else
                group->SetActiveWindow(satWindow);

        const WindowAreaList& areas = group->GetAreaList();
        int32 areasCount = areas.CountItems();
        for (int32 i = 0; i < areasCount; i++) {
                WindowArea* currentArea = areas.ItemAt(i);
                if (currentArea == area)
                        continue;

                desktop->ActivateWindow(currentArea->TopWindow()->GetWindow());
        }

        desktop->ActivateWindow(satWindow->GetWindow());
}


bool
StackAndTile::_HandleMessage(BPrivate::LinkReceiver& link,
        BPrivate::LinkSender& reply)
{
        int32 what;
        link.Read<int32>(&what);

        switch (what) {
                case BPrivate::kSaveAllGroups:
                {
                        BMessage allGroupsArchive;
                        GroupIterator groups(this, fDesktop);
                        while (true) {
                                SATGroup* group = groups.NextGroup();
                                if (group == NULL)
                                        break;
                                if (group->CountItems() <= 1)
                                        continue;
                                BMessage groupArchive;
                                if (group->ArchiveGroup(groupArchive) != B_OK)
                                        continue;
                                allGroupsArchive.AddMessage("group", &groupArchive);
                        }
                        int32 size = allGroupsArchive.FlattenedSize();
                        char buffer[size];
                        if (allGroupsArchive.Flatten(buffer, size) == B_OK) {
                                reply.StartMessage(B_OK);
                                reply.Attach<int32>(size);
                                reply.Attach(buffer, size);
                        } else
                                reply.StartMessage(B_ERROR);
                        reply.Flush();
                        break;
                }

                case BPrivate::kRestoreGroup:
                {
                        int32 size;
                        if (link.Read<int32>(&size) == B_OK) {
                                char buffer[size];
                                BMessage group;
                                if (link.Read(buffer, size) == B_OK
                                        && group.Unflatten(buffer) == B_OK) {
                                        status_t status = SATGroup::RestoreGroup(group, this);
                                        reply.StartMessage(status);
                                        reply.Flush();
                                }
                        }
                        break;
                }

                default:
                        return false;
        }

        return true;
}


SATGroup*
StackAndTile::_GetSATGroup(SATWindow* window)
{
        if (window == NULL)
                return NULL;

        SATGroup* group = window->GetGroup();
        if (group == NULL)
                return NULL;

        if (group->CountItems() < 1)
                return NULL;

        return group;
}


//      #pragma mark - GroupIterator


GroupIterator::GroupIterator(StackAndTile* sat, Desktop* desktop)
        :
        fStackAndTile(sat),
        fDesktop(desktop),
        fCurrentGroup(NULL)
{
        RewindToFront();
}


void
GroupIterator::RewindToFront()
{
        fCurrentWindow = fDesktop->CurrentWindows().LastWindow();
}


SATGroup*
GroupIterator::NextGroup()
{
        SATGroup* group = NULL;
        do {
                Window* window = fCurrentWindow;
                if (window == NULL) {
                        group = NULL;
                        break;
                }
                fCurrentWindow = fCurrentWindow->PreviousWindow(
                        fCurrentWindow->CurrentWorkspace());
                if (window->IsHidden()
                        || strcmp(window->Title(), "Deskbar") == 0
                        || strcmp(window->Title(), "Desktop") == 0) {
                        continue;
                }

                SATWindow* satWindow = fStackAndTile->GetSATWindow(window);
                group = satWindow->GetGroup();
        } while (group == NULL || fCurrentGroup == group);

        fCurrentGroup = group;
        return fCurrentGroup;
}


//      #pragma mark - WindowIterator


WindowIterator::WindowIterator(SATGroup* group, bool reverseLayerOrder)
        :
        fGroup(group),
        fReverseLayerOrder(reverseLayerOrder)
{
        if (fReverseLayerOrder)
                _ReverseRewind();
        else
                Rewind();
}


void
WindowIterator::Rewind()
{
        fAreaIndex = 0;
        fWindowIndex = 0;
        fCurrentArea = fGroup->GetAreaList().ItemAt(fAreaIndex);
}


SATWindow*
WindowIterator::NextWindow()
{
        if (fReverseLayerOrder)
                return _ReverseNextWindow();

        if (fWindowIndex == fCurrentArea->LayerOrder().CountItems()) {
                fAreaIndex++;
                fWindowIndex = 0;
                fCurrentArea = fGroup->GetAreaList().ItemAt(fAreaIndex);
                if (!fCurrentArea)
                        return NULL;
        }
        SATWindow* window = fCurrentArea->LayerOrder().ItemAt(fWindowIndex);
        fWindowIndex++;
        return window;
}


//      #pragma mark - WindowIterator private methods


SATWindow*
WindowIterator::_ReverseNextWindow()
{
        if (fWindowIndex < 0) {
                fAreaIndex++;
                fCurrentArea = fGroup->GetAreaList().ItemAt(fAreaIndex);
                if (!fCurrentArea)
                        return NULL;
                fWindowIndex = fCurrentArea->LayerOrder().CountItems() - 1;
        }
        SATWindow* window = fCurrentArea->LayerOrder().ItemAt(fWindowIndex);
        fWindowIndex--;
        return window;
}


void
WindowIterator::_ReverseRewind()
{
        Rewind();
        if (fCurrentArea)
                fWindowIndex = fCurrentArea->LayerOrder().CountItems() - 1;
}