/* * 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 #include #include #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) : MouseTrackingState(behavior, where, activateOnMouseUp, true) { 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( decorator->TabAt(windowIndex)); int32 neighbourIndex = windowIndex; if (movingTab->tabOffset > location) neighbourIndex--; else neighbourIndex++; DefaultDecorator::Tab* neighbourTab = static_cast(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)); 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 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); } }