root/src/servers/app/decorator/DefaultWindowBehaviour.cpp
/*
 * Copyright 2001-2020, Haiku, Inc.
 * Distributed under the terms of the MIT license.
 *
 * Authors:
 *              DarkWyrm, bpmagic@columbus.rr.com
 *              Adi Oanca, adioanca@gmail.com
 *              Stephan Aßmus, superstippi@gmx.de
 *              Axel Dörfler, axeld@pinc-software.de
 *              Brecht Machiels, brecht@mos6581.org
 *              Clemens Zeidler, haiku@clemens-zeidler.de
 *              Ingo Weinhold, ingo_weinhold@gmx.de
 *              Tri-Edge AI
 *              Jacob Secunda, secundja@gmail.com
 */


#include "DefaultWindowBehaviour.h"

#include <math.h>

#include <PortLink.h>
#include <WindowPrivate.h>

#include "AppServer.h"
#include "ClickTarget.h"
#include "Desktop.h"
#include "DefaultDecorator.h"
#include "DrawingEngine.h"
#include "Window.h"


//#define DEBUG_WINDOW_CLICK
#ifdef DEBUG_WINDOW_CLICK
#       define STRACE_CLICK(x) printf x
#else
#       define STRACE_CLICK(x) ;
#endif


// The span between mouse down
static const bigtime_t kWindowActivationTimeout = 500000LL;


// #pragma mark - State


struct DefaultWindowBehaviour::State {
        State(DefaultWindowBehaviour& behavior)
                :
                fBehavior(behavior),
                fWindow(behavior.fWindow),
                fDesktop(behavior.fDesktop)
        {
        }

        virtual ~State()
        {
        }

        virtual void EnterState(State* previousState)
        {
        }

        virtual void ExitState(State* nextState)
        {
        }

        virtual bool MouseDown(BMessage* message, BPoint where, bool& _unhandled)
        {
                return true;
        }

        virtual void MouseUp(BMessage* message, BPoint where)
        {
        }

        virtual void MouseMoved(BMessage* message, BPoint where, bool isFake)
        {
        }

        virtual void ModifiersChanged(BPoint where, int32 modifiers)
        {
        }

protected:
        DefaultWindowBehaviour& fBehavior;
        Window*                                 fWindow;
        Desktop*                                fDesktop;
};


// #pragma mark - MouseTrackingState


struct DefaultWindowBehaviour::MouseTrackingState : State {
        MouseTrackingState(DefaultWindowBehaviour& behavior, BPoint where,
                bool windowActionOnMouseUp, bool minimizeCheckOnMouseUp,
                int32 mouseButton = B_PRIMARY_MOUSE_BUTTON)
                :
                State(behavior),
                fMouseButton(mouseButton),
                fWindowActionOnMouseUp(windowActionOnMouseUp),
                fMinimizeCheckOnMouseUp(minimizeCheckOnMouseUp),
                fLastMousePosition(where),
                fMouseMoveDistance(0),
                fLastMoveTime(system_time())
        {
        }

        virtual void MouseUp(BMessage* message, BPoint where)
        {
                // ignore, if it's not our mouse button
                int32 buttons = message->FindInt32("buttons");
                if ((buttons & fMouseButton) != 0)
                        return;

                if (fMinimizeCheckOnMouseUp) {
                        // If the modifiers haven't changed in the meantime and not too
                        // much time has elapsed, we're supposed to minimize the window.
                        fMinimizeCheckOnMouseUp = false;
                        if (message->FindInt32("modifiers") == fBehavior.fLastModifiers
                                && (fWindow->Flags() & B_NOT_MINIMIZABLE) == 0
                                && system_time() - fLastMoveTime < kWindowActivationTimeout) {
                                fWindow->ServerWindow()->NotifyMinimize(true);
                        }
                }

                // Perform the window action in case the mouse was not moved.
                if (fWindowActionOnMouseUp) {
                        // There is a time window for this feature, i.e. click and press
                        // too long, nothing will happen.
                        if (system_time() - fLastMoveTime < kWindowActivationTimeout)
                                MouseUpWindowAction();
                }

                fBehavior._NextState(NULL);
        }

        virtual void MouseMoved(BMessage* message, BPoint where, bool isFake)
        {
                // Limit the rate at which "mouse moved" events are handled that move
                // or resize the window. At the moment this affects also tab sliding,
                // but 1/75 s is a pretty fine granularity anyway, so don't bother.
                bigtime_t now = system_time();
                if (now - fLastMoveTime < 13333) {
                        // TODO: add a "timed event" to query for
                        // the then current mouse position
                        return;
                }
                if (fWindowActionOnMouseUp || fMinimizeCheckOnMouseUp) {
                        if (now - fLastMoveTime >= kWindowActivationTimeout) {
                                // This click is too long already for window activation/
                                // minimizing.
                                fWindowActionOnMouseUp = false;
                                fMinimizeCheckOnMouseUp = false;
                                fLastMoveTime = now;
                        }
                } else
                        fLastMoveTime = now;

                BPoint delta = where - fLastMousePosition;
                // NOTE: "delta" is later used to change fLastMousePosition.
                // If for some reason no change should take effect, delta
                // is to be set to (0, 0) so that fLastMousePosition is not
                // adjusted. This way the relative mouse position to the
                // item being changed (border during resizing, tab during
                // sliding...) stays fixed when the mouse is moved so that
                // changes are taking effect again.

                // If the window was moved enough, it doesn't come to
                // the front in FFM mode when the mouse is released.
                if (fWindowActionOnMouseUp || fMinimizeCheckOnMouseUp) {
                        fMouseMoveDistance += delta.x * delta.x + delta.y * delta.y;
                        if (fMouseMoveDistance > 16.0f) {
                                fWindowActionOnMouseUp = false;
                                fMinimizeCheckOnMouseUp = false;
                        } else
                                delta = B_ORIGIN;
                }

                // perform the action (this also updates the delta)
                MouseMovedAction(delta, now);

                // set the new mouse position
                fLastMousePosition += delta;
        }

        virtual void MouseMovedAction(BPoint& delta, bigtime_t now)
        {
        }

        virtual void MouseUpWindowAction()
        {
                // default is window activation
                fDesktop->ActivateWindow(fWindow);
        }

protected:
        int32                           fMouseButton;
        bool                            fWindowActionOnMouseUp : 1;
        bool                            fMinimizeCheckOnMouseUp : 1;

        BPoint                          fLastMousePosition;
        float                           fMouseMoveDistance;
        bigtime_t                       fLastMoveTime;
};


// #pragma mark - DragState


struct DefaultWindowBehaviour::DragState : MouseTrackingState {
        DragState(DefaultWindowBehaviour& behavior, BPoint where,
                bool activateOnMouseUp, bool minimizeCheckOnMouseUp)
                :
                MouseTrackingState(behavior, where, activateOnMouseUp,
                        minimizeCheckOnMouseUp)
        {
        }

        virtual bool MouseDown(BMessage* message, BPoint where, bool& _unhandled)
        {
                // right-click while dragging shall bring the window to front
                int32 buttons = message->FindInt32("buttons");
                if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0) {
                        if (fWindow == fDesktop->BackWindow())
                                fDesktop->ActivateWindow(fWindow);
                        else
                                fDesktop->SendWindowBehind(fWindow);
                        return true;
                }

                return MouseTrackingState::MouseDown(message, where, _unhandled);
        }

        virtual void MouseMovedAction(BPoint& delta, bigtime_t now)
        {
                if ((fWindow->Flags() & B_NOT_MOVABLE) == 0) {
                        BPoint oldLeftTop = fWindow->Frame().LeftTop();

                        fBehavior.AlterDeltaForSnap(fWindow, delta, now);
                        fDesktop->MoveWindowBy(fWindow, delta.x, delta.y);

                        // constrain delta to true change in position
                        delta = fWindow->Frame().LeftTop() - oldLeftTop;
                } else
                        delta = BPoint(0, 0);
        }
};


// #pragma mark - ResizeState


struct DefaultWindowBehaviour::ResizeState : MouseTrackingState {
        BPoint fDelta;

        ResizeState(DefaultWindowBehaviour& behavior, BPoint where,
                bool activateOnMouseUp, bool minimizeCheckOnMouseUp)
                :
                MouseTrackingState(behavior, where, activateOnMouseUp, minimizeCheckOnMouseUp)
        {
                fDelta = BPoint(0, 0);
        }

        virtual void EnterState(State* prevState)
        {
        }

        virtual void ExitState(State* nextState)
        {
                if ((fWindow->Flags() & B_OUTLINE_RESIZE) != 0) {
                        fDesktop->SetWindowOutlinesDelta(fWindow, BPoint(0, 0));
                        fDesktop->ResizeWindowBy(fWindow, fDelta.x, fDelta.y);
                }
        }

        virtual void MouseMovedAction(BPoint& delta, bigtime_t now)
        {
                if ((fWindow->Flags() & B_NOT_RESIZABLE) == 0) {
                        if ((fWindow->Flags() & B_NOT_V_RESIZABLE) != 0)
                                delta.y = 0;
                        if ((fWindow->Flags() & B_NOT_H_RESIZABLE) != 0)
                                delta.x = 0;

                        BPoint oldRightBottom = fWindow->Frame().RightBottom();

                        if ((fWindow->Flags() & B_OUTLINE_RESIZE) != 0) {
                                fDelta = delta;
                                fDesktop->SetWindowOutlinesDelta(fWindow, delta);
                        } else
                                fDesktop->ResizeWindowBy(fWindow, delta.x, delta.y);

                        // constrain delta to true change in size
                        delta = fWindow->Frame().RightBottom() - oldRightBottom;
                } else
                        delta = BPoint(0, 0);
        }
};


// #pragma mark - SlideTabState


struct DefaultWindowBehaviour::SlideTabState : MouseTrackingState {
        SlideTabState(DefaultWindowBehaviour& behavior, BPoint where)
                :
                MouseTrackingState(behavior, where, false, false)
        {
        }

        virtual
        ~SlideTabState()
        {
                fDesktop->SetWindowTabLocation(fWindow, fWindow->TabLocation(), false);
        }

        virtual void MouseMovedAction(BPoint& delta, bigtime_t now)
        {
                float location = fWindow->TabLocation();
                // TODO: change to [0:1]
                location += delta.x;
                AdjustMultiTabLocation(location, true);
                if (fDesktop->SetWindowTabLocation(fWindow, location, true))
                        delta.y = 0;
                else
                        delta = BPoint(0, 0);
        }

        void AdjustMultiTabLocation(float location, bool isShifting)
        {
                ::Decorator* decorator = fWindow->Decorator();
                if (decorator == NULL || decorator->CountTabs() <= 1)
                        return;

                // TODO does not work for none continuous shifts
                int32 windowIndex = fWindow->PositionInStack();
                DefaultDecorator::Tab*  movingTab = static_cast<DefaultDecorator::Tab*>(
                        decorator->TabAt(windowIndex));
                int32 neighbourIndex = windowIndex;
                if (movingTab->tabOffset > location)
                        neighbourIndex--;
                else
                        neighbourIndex++;

                DefaultDecorator::Tab* neighbourTab
                        = static_cast<DefaultDecorator::Tab*>(decorator->TabAt(
                                neighbourIndex));
                if (neighbourTab == NULL)
                        return;

                if (movingTab->tabOffset > location) {
                        if (location > neighbourTab->tabOffset
                                        + neighbourTab->tabRect.Width() / 2) {
                                return;
                        }
                } else {
                        if (location + movingTab->tabRect.Width() < neighbourTab->tabOffset
                                        + neighbourTab->tabRect.Width() / 2) {
                                return;
                        }
                }

                fWindow->MoveToStackPosition(neighbourIndex, isShifting);
        }
};


// #pragma mark - ResizeBorderState


struct DefaultWindowBehaviour::ResizeBorderState : MouseTrackingState {
        BPoint fDelta;

        ResizeBorderState(DefaultWindowBehaviour& behavior, BPoint where,
                Decorator::Region region)
                :
                MouseTrackingState(behavior, where, true, false,
                        B_SECONDARY_MOUSE_BUTTON),
                fHorizontal(NONE),
                fVertical(NONE)
        {
                switch (region) {
                        case Decorator::REGION_TAB:
                                // TODO: Handle like the border it is attached to (top/left)?
                                break;
                        case Decorator::REGION_LEFT_BORDER:
                                fHorizontal = LEFT;
                                break;
                        case Decorator::REGION_RIGHT_BORDER:
                                fHorizontal = RIGHT;
                                break;
                        case Decorator::REGION_TOP_BORDER:
                                fVertical = TOP;
                                break;
                        case Decorator::REGION_BOTTOM_BORDER:
                                fVertical = BOTTOM;
                                break;
                        case Decorator::REGION_LEFT_TOP_CORNER:
                                fHorizontal = LEFT;
                                fVertical = TOP;
                                break;
                        case Decorator::REGION_LEFT_BOTTOM_CORNER:
                                fHorizontal = LEFT;
                                fVertical = BOTTOM;
                                break;
                        case Decorator::REGION_RIGHT_TOP_CORNER:
                                fHorizontal = RIGHT;
                                fVertical = TOP;
                                break;
                        case Decorator::REGION_RIGHT_BOTTOM_CORNER:
                                fHorizontal = RIGHT;
                                fVertical = BOTTOM;
                                break;
                        default:
                                break;
                }

                fDelta = B_ORIGIN;
        }

        ResizeBorderState(DefaultWindowBehaviour& behavior, BPoint where,
                int8 horizontal, int8 vertical)
                :
                MouseTrackingState(behavior, where, true, false,
                        B_SECONDARY_MOUSE_BUTTON),
                fHorizontal(horizontal),
                fVertical(vertical)
        {
                fDelta = B_ORIGIN;
        }

        virtual void EnterState(State* previousState)
        {
                if ((fWindow->Flags() & B_NOT_RESIZABLE) != 0)
                        fHorizontal = fVertical = NONE;
                else {
                        if ((fWindow->Flags() & B_NOT_H_RESIZABLE) != 0)
                                fHorizontal = NONE;

                        if ((fWindow->Flags() & B_NOT_V_RESIZABLE) != 0)
                                fVertical = NONE;
                }
                fBehavior._SetResizeCursor(fHorizontal, fVertical);
        }

        virtual void ExitState(State* nextState)
        {
                fBehavior._ResetResizeCursor();

                if (fWindow->Flags() & B_OUTLINE_RESIZE) {
                        fDesktop->SetWindowOutlinesDelta(fWindow, B_ORIGIN);
                        fDesktop->ResizeWindowBy(fWindow, fDelta.x, fDelta.y);
                }
        }

        virtual void MouseMovedAction(BPoint& delta, bigtime_t now)
        {
                if (fHorizontal == NONE)
                        delta.x = 0;
                if (fVertical == NONE)
                        delta.y = 0;

                if (delta.x == 0 && delta.y == 0)
                        return;

                // Resize first -- due to the window size limits this is not unlikely
                // to turn out differently from what we request.
                BPoint oldRightBottom = fWindow->Frame().RightBottom();

                if (fWindow->Flags() & B_OUTLINE_RESIZE) {
                        fDelta = delta;
                        fDesktop->SetWindowOutlinesDelta(fWindow, BPoint(
                                delta.x * fHorizontal, delta.y * fVertical));
                } else {
                        fDesktop->ResizeWindowBy(fWindow, delta.x * fHorizontal,
                                delta.y * fVertical);
                }

                // constrain delta to true change in size
                delta = fWindow->Frame().RightBottom() - oldRightBottom;
                delta.x *= fHorizontal;
                delta.y *= fVertical;

                // see, if we have to move, too
                float moveX = fHorizontal == LEFT ? delta.x : 0;
                float moveY = fVertical == TOP ? delta.y : 0;

                if (moveX != 0 || moveY != 0)
                        fDesktop->MoveWindowBy(fWindow, moveX, moveY);
        }

        virtual void MouseUpWindowAction()
        {
                fDesktop->SendWindowBehind(fWindow);
        }

private:
        int8    fHorizontal;
        int8    fVertical;
};


// #pragma mark - DecoratorButtonState


struct DefaultWindowBehaviour::DecoratorButtonState : State {
        DecoratorButtonState(DefaultWindowBehaviour& behavior,
                int32 tab, Decorator::Region button)
                :
                State(behavior),
                fTab(tab),
                fButton(button)
        {
        }

        virtual void EnterState(State* previousState)
        {
                _RedrawDecorator(NULL);
        }

        virtual void MouseUp(BMessage* message, BPoint where)
        {
                // ignore, if it's not the primary mouse button
                int32 buttons = message->FindInt32("buttons");
                if ((buttons & B_PRIMARY_MOUSE_BUTTON) != 0)
                        return;

                // redraw the decorator
                if (Decorator* decorator = fWindow->Decorator()) {
                        BRegion* visibleBorder = fWindow->RegionPool()->GetRegion();
                        fWindow->GetBorderRegion(visibleBorder);
                        visibleBorder->IntersectWith(&fWindow->VisibleRegion());

                        DrawingEngine* engine = decorator->GetDrawingEngine();
                        engine->LockParallelAccess();
                        engine->ConstrainClippingRegion(visibleBorder);

                        int32 tab;
                        switch (fButton) {
                                case Decorator::REGION_CLOSE_BUTTON:
                                        decorator->SetClose(fTab, false);
                                        if (fBehavior._RegionFor(message, tab) == fButton)
                                                fWindow->ServerWindow()->NotifyQuitRequested();
                                        break;

                                case Decorator::REGION_ZOOM_BUTTON:
                                        decorator->SetZoom(fTab, false);
                                        if (fBehavior._RegionFor(message, tab) == fButton)
                                                fWindow->ServerWindow()->NotifyZoom();
                                        break;

                                case Decorator::REGION_MINIMIZE_BUTTON:
                                        decorator->SetMinimize(fTab, false);
                                        if (fBehavior._RegionFor(message, tab) == fButton)
                                                fWindow->ServerWindow()->NotifyMinimize(true);
                                        break;

                                default:
                                        break;
                        }

                        engine->UnlockParallelAccess();

                        fWindow->RegionPool()->Recycle(visibleBorder);
                }

                fBehavior._NextState(NULL);
        }

        virtual void MouseMoved(BMessage* message, BPoint where, bool isFake)
        {
                _RedrawDecorator(message);
        }

private:
        void _RedrawDecorator(const BMessage* message)
        {
                if (Decorator* decorator = fWindow->Decorator()) {
                        BRegion* visibleBorder = fWindow->RegionPool()->GetRegion();
                        fWindow->GetBorderRegion(visibleBorder);
                        visibleBorder->IntersectWith(&fWindow->VisibleRegion());

                        DrawingEngine* engine = decorator->GetDrawingEngine();
                        engine->LockParallelAccess();
                        engine->ConstrainClippingRegion(visibleBorder);

                        int32 tab;
                        Decorator::Region hitRegion = message != NULL
                                ? fBehavior._RegionFor(message, tab) : fButton;

                        switch (fButton) {
                                case Decorator::REGION_CLOSE_BUTTON:
                                        decorator->SetClose(fTab, hitRegion == fButton);
                                        break;

                                case Decorator::REGION_ZOOM_BUTTON:
                                        decorator->SetZoom(fTab, hitRegion == fButton);
                                        break;

                                case Decorator::REGION_MINIMIZE_BUTTON:
                                        decorator->SetMinimize(fTab, hitRegion == fButton);
                                        break;

                                default:
                                        break;
                        }

                        engine->UnlockParallelAccess();
                        fWindow->RegionPool()->Recycle(visibleBorder);
                }
        }

protected:
        int32                           fTab;
        Decorator::Region       fButton;
};


// #pragma mark - ManageWindowState


struct DefaultWindowBehaviour::ManageWindowState : State {
        ManageWindowState(DefaultWindowBehaviour& behavior, BPoint where)
                :
                State(behavior),
                fLastMousePosition(where),
                fHorizontal(NONE),
                fVertical(NONE)
        {
        }

        virtual void EnterState(State* previousState)
        {
                _UpdateBorders(fLastMousePosition);
        }

        virtual void ExitState(State* nextState)
        {
                fBehavior._SetBorderHighlights(fHorizontal, fVertical, false);
        }

        virtual bool MouseDown(BMessage* message, BPoint where, bool& _unhandled)
        {
                // We're only interested if the secondary mouse button was pressed,
                // otherwise let the caller handle the event.
                int32 buttons = message->FindInt32("buttons");
                if ((buttons & B_SECONDARY_MOUSE_BUTTON) == 0) {
                        _unhandled = true;
                        return true;
                }

                fBehavior._NextState(new (std::nothrow) ResizeBorderState(fBehavior,
                        where, fHorizontal, fVertical));
                return true;
        }

        virtual void MouseMoved(BMessage* message, BPoint where, bool isFake)
        {
                // If the mouse is still over our window, update the borders. Otherwise
                // leave the state.
                if (fDesktop->WindowAt(where) == fWindow) {
                        fLastMousePosition = where;
                        _UpdateBorders(fLastMousePosition);
                } else
                        fBehavior._NextState(NULL);
        }

        virtual void ModifiersChanged(BPoint where, int32 modifiers)
        {
                if (!fBehavior._IsWindowModifier(modifiers))
                        fBehavior._NextState(NULL);
        }

private:
        void _UpdateBorders(BPoint where)
        {
                if ((fWindow->Flags() & B_NOT_RESIZABLE) != 0)
                        return;

                // Compute the window center relative location of where. We divide by
                // the width respective the height, so we compensate for the window's
                // aspect ratio.
                BRect frame(fWindow->Frame());
                if (frame.Width() + 1 == 0 || frame.Height() + 1 == 0)
                        return;

                float x = (where.x - (frame.left + frame.right) / 2)
                        / (frame.Width() + 1);
                float y = (where.y - (frame.top + frame.bottom) / 2)
                        / (frame.Height() + 1);

                // compute the resize direction
                int8 horizontal;
                int8 vertical;
                _ComputeResizeDirection(x, y, horizontal, vertical);

                if ((fWindow->Flags() & B_NOT_H_RESIZABLE) != 0)
                        horizontal = NONE;
                if ((fWindow->Flags() & B_NOT_V_RESIZABLE) != 0)
                        vertical = NONE;

                // update the highlight, if necessary
                if (horizontal != fHorizontal || vertical != fVertical) {
                        fBehavior._SetBorderHighlights(fHorizontal, fVertical, false);
                        fHorizontal = horizontal;
                        fVertical = vertical;
                        fBehavior._SetBorderHighlights(fHorizontal, fVertical, true);
                }
        }

private:
        BPoint  fLastMousePosition;
        int8    fHorizontal;
        int8    fVertical;
};


// #pragma mark - DefaultWindowBehaviour


DefaultWindowBehaviour::DefaultWindowBehaviour(Window* window)
        :
        fWindow(window),
        fDesktop(window->Desktop()),
        fLastModifiers(0)
{
}


DefaultWindowBehaviour::~DefaultWindowBehaviour()
{
}


bool
DefaultWindowBehaviour::MouseDown(BMessage* message, BPoint where,
        int32 lastHitRegion, int32& clickCount, int32& _hitRegion)
{
        fLastModifiers = message->FindInt32("modifiers");
        int32 buttons = message->FindInt32("buttons");

        int32 numButtons;
        if (get_mouse_type(&numButtons) == B_OK) {
                switch (numButtons) {
                        case 1:
                                // 1 button mouse
                                if ((fLastModifiers & B_CONTROL_KEY) != 0) {
                                        // emulate right click by holding control
                                        buttons = B_SECONDARY_MOUSE_BUTTON;
                                        message->ReplaceInt32("buttons", buttons);
                                }
                                break;

                        case 2:
                                // TODO: 2 button mouse, pressing both buttons simultaneously
                                // emulates middle click

                        default:
                                break;
                }
        }

        // if a state is active, let it do the job
        if (fState.IsSet()) {
                bool unhandled = false;
                bool result = fState->MouseDown(message, where, unhandled);
                if (!unhandled)
                        return result;
        }

        // No state active yet, or it wants us to handle the event -- determine the
        // click region and decide what to do.

        Decorator* decorator = fWindow->Decorator();

        Decorator::Region hitRegion = Decorator::REGION_NONE;
        int32 tab = -1;
        Action action = ACTION_NONE;

        bool inBorderRegion = false;
        if (decorator != NULL)
                inBorderRegion = decorator->GetFootprint().Contains(where);

        bool windowModifier = _IsWindowModifier(fLastModifiers);

        if (windowModifier || inBorderRegion) {
                // click on the window decorator or we have the window modifier keys
                // held

                // get the functional hit region
                if (windowModifier) {
                        // click with window modifier keys -- let the whole window behave
                        // like the border
                        hitRegion = Decorator::REGION_LEFT_BORDER;
                } else {
                        // click on the decorator -- get the exact region
                        hitRegion = _RegionFor(message, tab);
                }

                // translate the region into an action
                uint32 flags = fWindow->Flags();

                if ((buttons & B_PRIMARY_MOUSE_BUTTON) != 0) {
                        // left mouse button
                        switch (hitRegion) {
                                case Decorator::REGION_TAB: {
                                        // tab sliding in any case if either shift key is held down
                                        // except sliding up-down by moving mouse left-right would
                                        // look strange
                                        if ((fLastModifiers & B_SHIFT_KEY) != 0
                                                && fWindow->Look() != kLeftTitledWindowLook) {
                                                action = ACTION_SLIDE_TAB;
                                                break;
                                        }
                                        action = ACTION_DRAG;
                                        break;
                                }

                                case Decorator::REGION_LEFT_BORDER:
                                case Decorator::REGION_RIGHT_BORDER:
                                case Decorator::REGION_TOP_BORDER:
                                case Decorator::REGION_BOTTOM_BORDER:
                                        action = ACTION_DRAG;
                                        break;

                                case Decorator::REGION_CLOSE_BUTTON:
                                        action = (flags & B_NOT_CLOSABLE) == 0
                                                ? ACTION_CLOSE : ACTION_DRAG;
                                        break;

                                case Decorator::REGION_ZOOM_BUTTON:
                                        action = (flags & B_NOT_ZOOMABLE) == 0
                                                ? ACTION_ZOOM : ACTION_DRAG;
                                        break;

                                case Decorator::REGION_MINIMIZE_BUTTON:
                                        action = (flags & B_NOT_MINIMIZABLE) == 0
                                                ? ACTION_MINIMIZE : ACTION_DRAG;
                                        break;

                                case Decorator::REGION_LEFT_TOP_CORNER:
                                case Decorator::REGION_LEFT_BOTTOM_CORNER:
                                case Decorator::REGION_RIGHT_TOP_CORNER:
                                        // TODO: Handle correctly!
                                        action = ACTION_DRAG;
                                        break;

                                case Decorator::REGION_RIGHT_BOTTOM_CORNER:
                                        action = (flags & B_NOT_RESIZABLE) == 0
                                                ? ACTION_RESIZE : ACTION_DRAG;
                                        break;

                                default:
                                        break;
                        }
                } else if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0) {
                        // right mouse button
                        switch (hitRegion) {
                                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_CLOSE_BUTTON:
                                case Decorator::REGION_ZOOM_BUTTON:
                                case Decorator::REGION_MINIMIZE_BUTTON:
                                case Decorator::REGION_LEFT_TOP_CORNER:
                                case Decorator::REGION_LEFT_BOTTOM_CORNER:
                                case Decorator::REGION_RIGHT_TOP_CORNER:
                                case Decorator::REGION_RIGHT_BOTTOM_CORNER:
                                        action = ACTION_RESIZE_BORDER;
                                        break;

                                default:
                                        break;
                        }
                }
        }

        _hitRegion = (int32)hitRegion;

        if (action == ACTION_NONE) {
                // No action -- if this is a click inside the window's contents,
                // let it be forwarded to the window.
                return inBorderRegion;
        }

        // reset the click count, if the hit region differs from the previous one
        if (hitRegion != lastHitRegion)
                clickCount = 1;

        DesktopSettings desktopSettings(fDesktop);
        if (!desktopSettings.AcceptFirstClick()) {
                // Ignore clicks on decorator buttons if the
                // non-floating window doesn't have focus
                if (!fWindow->IsFocus() && !fWindow->IsFloating()
                        && action != ACTION_RESIZE_BORDER
                        && action != ACTION_RESIZE && action != ACTION_SLIDE_TAB)
                        action = ACTION_DRAG;
        }

        bool activateOnMouseUp = false;
        if (action != ACTION_RESIZE_BORDER) {
                // activate window if in click to activate mode, else only focus it
                if (desktopSettings.MouseMode() == B_NORMAL_MOUSE) {
                        fDesktop->ActivateWindow(fWindow);
                } else {
                        fDesktop->SetFocusWindow(fWindow);
                        activateOnMouseUp = true;
                }
        }

        // switch to the new state
        switch (action) {
                case ACTION_CLOSE:
                case ACTION_ZOOM:
                case ACTION_MINIMIZE:
                        _NextState(
                                new (std::nothrow) DecoratorButtonState(*this, tab, hitRegion));
                        STRACE_CLICK(("===> ACTION_CLOSE/ZOOM/MINIMIZE\n"));
                        break;

                case ACTION_DRAG:
                        _NextState(new (std::nothrow) DragState(*this, where,
                                activateOnMouseUp, clickCount == 2));
                        STRACE_CLICK(("===> ACTION_DRAG\n"));
                        break;

                case ACTION_RESIZE:
                        _NextState(new (std::nothrow) ResizeState(*this, where,
                                activateOnMouseUp, clickCount == 2));
                        STRACE_CLICK(("===> ACTION_RESIZE\n"));
                        break;

                case ACTION_SLIDE_TAB:
                        _NextState(new (std::nothrow) SlideTabState(*this, where));
                        STRACE_CLICK(("===> ACTION_SLIDE_TAB\n"));
                        break;

                case ACTION_RESIZE_BORDER:
                        _NextState(new (std::nothrow) ResizeBorderState(*this, where,
                                hitRegion));
                        STRACE_CLICK(("===> ACTION_RESIZE_BORDER\n"));
                        break;

                default:
                        break;
        }

        return true;
}


void
DefaultWindowBehaviour::MouseUp(BMessage* message, BPoint where)
{
        if (fState.IsSet())
                fState->MouseUp(message, where);
}


void
DefaultWindowBehaviour::MouseMoved(BMessage* message, BPoint where, bool isFake)
{
        if (fState.IsSet()) {
                fState->MouseMoved(message, where, isFake);
        } else {
                // If the window modifiers are hold, enter the window management state.
                if (_IsWindowModifier(message->FindInt32("modifiers")))
                        _NextState(new(std::nothrow) ManageWindowState(*this, where));
        }

        // change focus in FFM mode
        DesktopSettings desktopSettings(fDesktop);
        if (desktopSettings.FocusFollowsMouse()
                && !fWindow->IsFocus() && (fWindow->Flags() & B_AVOID_FOCUS) == 0) {
                // If the mouse move is a fake one, we set the focus to NULL, which
                // will cause the window that had focus last to retrieve it again - this
                // makes FFM much nicer to use with the keyboard.
                fDesktop->SetFocusWindow(isFake ? NULL : fWindow);
        }
}


void
DefaultWindowBehaviour::ModifiersChanged(int32 modifiers)
{
        BPoint where;
        int32 buttons;
        fDesktop->GetLastMouseState(&where, &buttons);

        if (fState.IsSet()) {
                fState->ModifiersChanged(where, modifiers);
        } else {
                // If the window modifiers are hold, enter the window management state.
                if (_IsWindowModifier(modifiers))
                        _NextState(new(std::nothrow) ManageWindowState(*this, where));
        }
}


bool
DefaultWindowBehaviour::AlterDeltaForSnap(Window* window, BPoint& delta,
        bigtime_t now)
{
        return fMagneticBorder.AlterDeltaForSnap(window, delta, now);
}


bool
DefaultWindowBehaviour::_IsWindowModifier(int32 modifiers) const
{
        return (fWindow->Flags() & B_NO_SERVER_SIDE_WINDOW_MODIFIERS) == 0
                && (modifiers & (B_COMMAND_KEY | B_CONTROL_KEY | B_OPTION_KEY
                                | B_SHIFT_KEY)) == (B_COMMAND_KEY | B_CONTROL_KEY);
}


Decorator::Region
DefaultWindowBehaviour::_RegionFor(const BMessage* message, int32& tab) const
{
        Decorator* decorator = fWindow->Decorator();
        if (decorator == NULL)
                return Decorator::REGION_NONE;

        BPoint where;
        if (message->FindPoint("where", &where) != B_OK)
                return Decorator::REGION_NONE;

        return decorator->RegionAt(where, tab);
}


void
DefaultWindowBehaviour::_SetBorderHighlights(int8 horizontal, int8 vertical,
        bool active)
{
        if (Decorator* decorator = fWindow->Decorator()) {
                uint8 highlight = active
                        ? Decorator::HIGHLIGHT_RESIZE_BORDER
                        : Decorator::HIGHLIGHT_NONE;

                // set the highlights for the borders
                BRegion dirtyRegion;
                switch (horizontal) {
                        case LEFT:
                                decorator->SetRegionHighlight(Decorator::REGION_LEFT_BORDER,
                                        highlight, &dirtyRegion);
                                break;
                        case RIGHT:
                                decorator->SetRegionHighlight(
                                        Decorator::REGION_RIGHT_BORDER, highlight,
                                        &dirtyRegion);
                                break;
                }

                switch (vertical) {
                        case TOP:
                                decorator->SetRegionHighlight(Decorator::REGION_TOP_BORDER,
                                        highlight, &dirtyRegion);
                                break;
                        case BOTTOM:
                                decorator->SetRegionHighlight(
                                        Decorator::REGION_BOTTOM_BORDER, highlight,
                                        &dirtyRegion);
                                break;
                }

                // set the highlights for the corners
                if (horizontal != NONE && vertical != NONE) {
                        if (horizontal == LEFT) {
                                if (vertical == TOP) {
                                        decorator->SetRegionHighlight(
                                                Decorator::REGION_LEFT_TOP_CORNER, highlight,
                                                &dirtyRegion);
                                } else {
                                        decorator->SetRegionHighlight(
                                                Decorator::REGION_LEFT_BOTTOM_CORNER, highlight,
                                                &dirtyRegion);
                                }
                        } else {
                                if (vertical == TOP) {
                                        decorator->SetRegionHighlight(
                                                Decorator::REGION_RIGHT_TOP_CORNER, highlight,
                                                &dirtyRegion);
                                } else {
                                        decorator->SetRegionHighlight(
                                                Decorator::REGION_RIGHT_BOTTOM_CORNER, highlight,
                                                &dirtyRegion);
                                }
                        }
                }

                // invalidate the affected regions
                fWindow->ProcessDirtyRegion(dirtyRegion);
        }
}


ServerCursor*
DefaultWindowBehaviour::_ResizeCursorFor(int8 horizontal, int8 vertical)
{
        // get the cursor ID corresponding to the border/corner
        BCursorID cursorID = B_CURSOR_ID_SYSTEM_DEFAULT;

        if (horizontal == LEFT) {
                if (vertical == TOP)
                        cursorID = B_CURSOR_ID_RESIZE_NORTH_WEST;
                else if (vertical == BOTTOM)
                        cursorID = B_CURSOR_ID_RESIZE_SOUTH_WEST;
                else
                        cursorID = B_CURSOR_ID_RESIZE_WEST;
        } else if (horizontal == RIGHT) {
                if (vertical == TOP)
                        cursorID = B_CURSOR_ID_RESIZE_NORTH_EAST;
                else if (vertical == BOTTOM)
                        cursorID = B_CURSOR_ID_RESIZE_SOUTH_EAST;
                else
                        cursorID = B_CURSOR_ID_RESIZE_EAST;
        } else {
                if (vertical == TOP)
                        cursorID = B_CURSOR_ID_RESIZE_NORTH;
                else if (vertical == BOTTOM)
                        cursorID = B_CURSOR_ID_RESIZE_SOUTH;
        }

        return fDesktop->GetCursorManager().GetCursor(cursorID);
}


void
DefaultWindowBehaviour::_SetResizeCursor(int8 horizontal, int8 vertical)
{
        fDesktop->SetManagementCursor(_ResizeCursorFor(horizontal, vertical));
}


void
DefaultWindowBehaviour::_ResetResizeCursor()
{
        fDesktop->SetManagementCursor(NULL);
}


/*static*/ void
DefaultWindowBehaviour::_ComputeResizeDirection(float x, float y,
        int8& _horizontal, int8& _vertical)
{
        _horizontal = NONE;
        _vertical = NONE;

        // compute the angle
        if (x == 0 && y == 0)
                return;

        float angle = atan2f(y, x);

        // rotate by 22.5 degree to align our sectors with 45 degree multiples
        angle += M_PI / 8;

        // add 180 degree to the negative values, so we get a nice 0 to 360
        // degree range
        if (angle < 0)
                angle += M_PI * 2;

        switch (int(angle / M_PI_4)) {
                case 0:
                        _horizontal = RIGHT;
                        break;
                case 1:
                        _horizontal = RIGHT;
                        _vertical = BOTTOM;
                        break;
                case 2:
                        _vertical = BOTTOM;
                        break;
                case 3:
                        _horizontal = LEFT;
                        _vertical = BOTTOM;
                        break;
                case 4:
                        _horizontal = LEFT;
                        break;
                case 5:
                        _horizontal = LEFT;
                        _vertical = TOP;
                        break;
                case 6:
                        _vertical = TOP;
                        break;
                case 7:
                default:
                        _horizontal = RIGHT;
                        _vertical = TOP;
                        break;
        }
}


void
DefaultWindowBehaviour::_NextState(State* state)
{
        // exit the old state
        if (fState.IsSet())
                fState->ExitState(state);

        // set and enter the new state
        ObjectDeleter<State> oldState(fState.Detach());
        fState.SetTo(state);

        if (fState.IsSet()) {
                fState->EnterState(oldState.Get());
                fDesktop->SetMouseEventWindow(fWindow);
        } else if (oldState.IsSet()) {
                // no state anymore -- reset the mouse event window, if it's still us
                if (fDesktop->MouseEventWindow() == fWindow)
                        fDesktop->SetMouseEventWindow(NULL);
        }
}