master chld/haikuprg / decor / MacDecorator / DefaultWindowBehaviour.cpp
   1/*
   2 * Copyright 2001-2020, Haiku, Inc.
   3 * Distributed under the terms of the MIT license.
   4 *
   5 * Authors:
   6 *		DarkWyrm, bpmagic@columbus.rr.com
   7 *		Adi Oanca, adioanca@gmail.com
   8 *		Stephan Aßmus, superstippi@gmx.de
   9 *		Axel Dörfler, axeld@pinc-software.de
  10 *		Brecht Machiels, brecht@mos6581.org
  11 *		Clemens Zeidler, haiku@clemens-zeidler.de
  12 *		Ingo Weinhold, ingo_weinhold@gmx.de
  13 *		Tri-Edge AI
  14 *		Jacob Secunda, secundja@gmail.com
  15 */
  16
  17
  18#include "DefaultWindowBehaviour.h"
  19
  20#include <math.h>
  21
  22#include <PortLink.h>
  23#include <WindowPrivate.h>
  24
  25#include "AppServer.h"
  26#include "ClickTarget.h"
  27#include "Desktop.h"
  28#include "DefaultDecorator.h"
  29#include "DrawingEngine.h"
  30#include "Window.h"
  31
  32
  33//#define DEBUG_WINDOW_CLICK
  34#ifdef DEBUG_WINDOW_CLICK
  35#	define STRACE_CLICK(x) printf x
  36#else
  37#	define STRACE_CLICK(x) ;
  38#endif
  39
  40
  41// The span between mouse down
  42static const bigtime_t kWindowActivationTimeout = 500000LL;
  43
  44
  45// #pragma mark - State
  46
  47
  48struct DefaultWindowBehaviour::State {
  49	State(DefaultWindowBehaviour& behavior)
  50		:
  51		fBehavior(behavior),
  52		fWindow(behavior.fWindow),
  53		fDesktop(behavior.fDesktop)
  54	{
  55	}
  56
  57	virtual ~State()
  58	{
  59	}
  60
  61	virtual void EnterState(State* previousState)
  62	{
  63	}
  64
  65	virtual void ExitState(State* nextState)
  66	{
  67	}
  68
  69	virtual bool MouseDown(BMessage* message, BPoint where, bool& _unhandled)
  70	{
  71		return true;
  72	}
  73
  74	virtual void MouseUp(BMessage* message, BPoint where)
  75	{
  76	}
  77
  78	virtual void MouseMoved(BMessage* message, BPoint where, bool isFake)
  79	{
  80	}
  81
  82	virtual void ModifiersChanged(BPoint where, int32 modifiers)
  83	{
  84	}
  85
  86protected:
  87	DefaultWindowBehaviour&	fBehavior;
  88	Window*					fWindow;
  89	Desktop*				fDesktop;
  90};
  91
  92
  93// #pragma mark - MouseTrackingState
  94
  95
  96struct DefaultWindowBehaviour::MouseTrackingState : State {
  97	MouseTrackingState(DefaultWindowBehaviour& behavior, BPoint where,
  98		bool windowActionOnMouseUp, bool minimizeCheckOnMouseUp,
  99		int32 mouseButton = B_PRIMARY_MOUSE_BUTTON)
 100		:
 101		State(behavior),
 102		fMouseButton(mouseButton),
 103		fWindowActionOnMouseUp(windowActionOnMouseUp),
 104		fMinimizeCheckOnMouseUp(minimizeCheckOnMouseUp),
 105		fLastMousePosition(where),
 106		fMouseMoveDistance(0),
 107		fLastMoveTime(system_time())
 108	{
 109	}
 110
 111	virtual void MouseUp(BMessage* message, BPoint where)
 112	{
 113		// ignore, if it's not our mouse button
 114		int32 buttons = message->FindInt32("buttons");
 115		if ((buttons & fMouseButton) != 0)
 116			return;
 117
 118		if (fMinimizeCheckOnMouseUp) {
 119			// If the modifiers haven't changed in the meantime and not too
 120			// much time has elapsed, we're supposed to minimize the window.
 121			fMinimizeCheckOnMouseUp = false;
 122			if (message->FindInt32("modifiers") == fBehavior.fLastModifiers
 123				&& (fWindow->Flags() & B_NOT_MINIMIZABLE) == 0
 124				&& system_time() - fLastMoveTime < kWindowActivationTimeout) {
 125				fWindow->ServerWindow()->NotifyMinimize(true);
 126			}
 127		}
 128
 129		// Perform the window action in case the mouse was not moved.
 130		if (fWindowActionOnMouseUp) {
 131			// There is a time window for this feature, i.e. click and press
 132			// too long, nothing will happen.
 133			if (system_time() - fLastMoveTime < kWindowActivationTimeout)
 134				MouseUpWindowAction();
 135		}
 136
 137		fBehavior._NextState(NULL);
 138	}
 139
 140	virtual void MouseMoved(BMessage* message, BPoint where, bool isFake)
 141	{
 142		// Limit the rate at which "mouse moved" events are handled that move
 143		// or resize the window. At the moment this affects also tab sliding,
 144		// but 1/75 s is a pretty fine granularity anyway, so don't bother.
 145		bigtime_t now = system_time();
 146		if (now - fLastMoveTime < 13333) {
 147			// TODO: add a "timed event" to query for
 148			// the then current mouse position
 149			return;
 150		}
 151		if (fWindowActionOnMouseUp || fMinimizeCheckOnMouseUp) {
 152			if (now - fLastMoveTime >= kWindowActivationTimeout) {
 153				// This click is too long already for window activation/
 154				// minimizing.
 155				fWindowActionOnMouseUp = false;
 156				fMinimizeCheckOnMouseUp = false;
 157				fLastMoveTime = now;
 158			}
 159		} else
 160			fLastMoveTime = now;
 161
 162		BPoint delta = where - fLastMousePosition;
 163		// NOTE: "delta" is later used to change fLastMousePosition.
 164		// If for some reason no change should take effect, delta
 165		// is to be set to (0, 0) so that fLastMousePosition is not
 166		// adjusted. This way the relative mouse position to the
 167		// item being changed (border during resizing, tab during
 168		// sliding...) stays fixed when the mouse is moved so that
 169		// changes are taking effect again.
 170
 171		// If the window was moved enough, it doesn't come to
 172		// the front in FFM mode when the mouse is released.
 173		if (fWindowActionOnMouseUp || fMinimizeCheckOnMouseUp) {
 174			fMouseMoveDistance += delta.x * delta.x + delta.y * delta.y;
 175			if (fMouseMoveDistance > 16.0f) {
 176				fWindowActionOnMouseUp = false;
 177				fMinimizeCheckOnMouseUp = false;
 178			} else
 179				delta = B_ORIGIN;
 180		}
 181
 182		// perform the action (this also updates the delta)
 183		MouseMovedAction(delta, now);
 184
 185		// set the new mouse position
 186		fLastMousePosition += delta;
 187	}
 188
 189	virtual void MouseMovedAction(BPoint& delta, bigtime_t now)
 190	{
 191	}
 192
 193	virtual void MouseUpWindowAction()
 194	{
 195		// default is window activation
 196		fDesktop->ActivateWindow(fWindow);
 197	}
 198
 199protected:
 200	int32				fMouseButton;
 201	bool				fWindowActionOnMouseUp : 1;
 202	bool				fMinimizeCheckOnMouseUp : 1;
 203
 204	BPoint				fLastMousePosition;
 205	float				fMouseMoveDistance;
 206	bigtime_t			fLastMoveTime;
 207};
 208
 209
 210// #pragma mark - DragState
 211
 212
 213struct DefaultWindowBehaviour::DragState : MouseTrackingState {
 214	DragState(DefaultWindowBehaviour& behavior, BPoint where,
 215		bool activateOnMouseUp, bool minimizeCheckOnMouseUp)
 216		:
 217		MouseTrackingState(behavior, where, activateOnMouseUp,
 218			minimizeCheckOnMouseUp)
 219	{
 220	}
 221
 222	virtual bool MouseDown(BMessage* message, BPoint where, bool& _unhandled)
 223	{
 224		// right-click while dragging shall bring the window to front
 225		int32 buttons = message->FindInt32("buttons");
 226		if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0) {
 227			if (fWindow == fDesktop->BackWindow())
 228				fDesktop->ActivateWindow(fWindow);
 229			else
 230				fDesktop->SendWindowBehind(fWindow);
 231			return true;
 232		}
 233
 234		return MouseTrackingState::MouseDown(message, where, _unhandled);
 235	}
 236
 237	virtual void MouseMovedAction(BPoint& delta, bigtime_t now)
 238	{
 239		if ((fWindow->Flags() & B_NOT_MOVABLE) == 0) {
 240			BPoint oldLeftTop = fWindow->Frame().LeftTop();
 241
 242			fBehavior.AlterDeltaForSnap(fWindow, delta, now);
 243			fDesktop->MoveWindowBy(fWindow, delta.x, delta.y);
 244
 245			// constrain delta to true change in position
 246			delta = fWindow->Frame().LeftTop() - oldLeftTop;
 247		} else
 248			delta = BPoint(0, 0);
 249	}
 250};
 251
 252
 253// #pragma mark - ResizeState
 254
 255
 256struct DefaultWindowBehaviour::ResizeState : MouseTrackingState {
 257	BPoint fDelta;
 258
 259	ResizeState(DefaultWindowBehaviour& behavior, BPoint where,
 260		bool activateOnMouseUp)
 261		:
 262		MouseTrackingState(behavior, where, activateOnMouseUp, true)
 263	{
 264		fDelta = BPoint(0, 0);
 265	}
 266
 267	virtual void EnterState(State* prevState)
 268	{
 269	}
 270
 271	virtual void ExitState(State* nextState)
 272	{
 273		if ((fWindow->Flags() & B_OUTLINE_RESIZE) != 0) {
 274			fDesktop->SetWindowOutlinesDelta(fWindow, BPoint(0, 0));
 275			fDesktop->ResizeWindowBy(fWindow, fDelta.x, fDelta.y);
 276		}
 277	}
 278
 279	virtual void MouseMovedAction(BPoint& delta, bigtime_t now)
 280	{
 281		if ((fWindow->Flags() & B_NOT_RESIZABLE) == 0) {
 282			if ((fWindow->Flags() & B_NOT_V_RESIZABLE) != 0)
 283				delta.y = 0;
 284			if ((fWindow->Flags() & B_NOT_H_RESIZABLE) != 0)
 285				delta.x = 0;
 286
 287			BPoint oldRightBottom = fWindow->Frame().RightBottom();
 288
 289			if ((fWindow->Flags() & B_OUTLINE_RESIZE) != 0) {
 290				fDelta = delta;
 291				fDesktop->SetWindowOutlinesDelta(fWindow, delta);
 292			} else
 293				fDesktop->ResizeWindowBy(fWindow, delta.x, delta.y);
 294
 295			// constrain delta to true change in size
 296			delta = fWindow->Frame().RightBottom() - oldRightBottom;
 297		} else
 298			delta = BPoint(0, 0);
 299	}
 300};
 301
 302
 303// #pragma mark - SlideTabState
 304
 305
 306struct DefaultWindowBehaviour::SlideTabState : MouseTrackingState {
 307	SlideTabState(DefaultWindowBehaviour& behavior, BPoint where)
 308		:
 309		MouseTrackingState(behavior, where, false, false)
 310	{
 311	}
 312
 313	virtual
 314	~SlideTabState()
 315	{
 316		fDesktop->SetWindowTabLocation(fWindow, fWindow->TabLocation(), false);
 317	}
 318
 319	virtual void MouseMovedAction(BPoint& delta, bigtime_t now)
 320	{
 321		float location = fWindow->TabLocation();
 322		// TODO: change to [0:1]
 323		location += delta.x;
 324		AdjustMultiTabLocation(location, true);
 325		if (fDesktop->SetWindowTabLocation(fWindow, location, true))
 326			delta.y = 0;
 327		else
 328			delta = BPoint(0, 0);
 329	}
 330
 331	void AdjustMultiTabLocation(float location, bool isShifting)
 332	{
 333		::Decorator* decorator = fWindow->Decorator();
 334		if (decorator == NULL || decorator->CountTabs() <= 1)
 335			return;
 336
 337		// TODO does not work for none continuous shifts
 338		int32 windowIndex = fWindow->PositionInStack();
 339		DefaultDecorator::Tab*	movingTab = static_cast<DefaultDecorator::Tab*>(
 340			decorator->TabAt(windowIndex));
 341		int32 neighbourIndex = windowIndex;
 342		if (movingTab->tabOffset > location)
 343			neighbourIndex--;
 344		else
 345			neighbourIndex++;
 346
 347		DefaultDecorator::Tab* neighbourTab
 348			= static_cast<DefaultDecorator::Tab*>(decorator->TabAt(
 349				neighbourIndex));
 350		if (neighbourTab == NULL)
 351			return;
 352
 353		if (movingTab->tabOffset > location) {
 354			if (location > neighbourTab->tabOffset
 355					+ neighbourTab->tabRect.Width() / 2) {
 356				return;
 357			}
 358		} else {
 359			if (location + movingTab->tabRect.Width() < neighbourTab->tabOffset
 360					+ neighbourTab->tabRect.Width() / 2) {
 361				return;
 362			}
 363		}
 364
 365		fWindow->MoveToStackPosition(neighbourIndex, isShifting);
 366	}
 367};
 368
 369
 370// #pragma mark - ResizeBorderState
 371
 372
 373struct DefaultWindowBehaviour::ResizeBorderState : MouseTrackingState {
 374	BPoint fDelta;
 375
 376	ResizeBorderState(DefaultWindowBehaviour& behavior, BPoint where,
 377		Decorator::Region region)
 378		:
 379		MouseTrackingState(behavior, where, true, false,
 380			B_SECONDARY_MOUSE_BUTTON),
 381		fHorizontal(NONE),
 382		fVertical(NONE)
 383	{
 384		switch (region) {
 385			case Decorator::REGION_TAB:
 386				// TODO: Handle like the border it is attached to (top/left)?
 387				break;
 388			case Decorator::REGION_LEFT_BORDER:
 389				fHorizontal = LEFT;
 390				break;
 391			case Decorator::REGION_RIGHT_BORDER:
 392				fHorizontal = RIGHT;
 393				break;
 394			case Decorator::REGION_TOP_BORDER:
 395				fVertical = TOP;
 396				break;
 397			case Decorator::REGION_BOTTOM_BORDER:
 398				fVertical = BOTTOM;
 399				break;
 400			case Decorator::REGION_LEFT_TOP_CORNER:
 401				fHorizontal = LEFT;
 402				fVertical = TOP;
 403				break;
 404			case Decorator::REGION_LEFT_BOTTOM_CORNER:
 405				fHorizontal = LEFT;
 406				fVertical = BOTTOM;
 407				break;
 408			case Decorator::REGION_RIGHT_TOP_CORNER:
 409				fHorizontal = RIGHT;
 410				fVertical = TOP;
 411				break;
 412			case Decorator::REGION_RIGHT_BOTTOM_CORNER:
 413				fHorizontal = RIGHT;
 414				fVertical = BOTTOM;
 415				break;
 416			default:
 417				break;
 418		}
 419
 420		fDelta = B_ORIGIN;
 421	}
 422
 423	ResizeBorderState(DefaultWindowBehaviour& behavior, BPoint where,
 424		int8 horizontal, int8 vertical)
 425		:
 426		MouseTrackingState(behavior, where, true, false,
 427			B_SECONDARY_MOUSE_BUTTON),
 428		fHorizontal(horizontal),
 429		fVertical(vertical)
 430	{
 431		fDelta = B_ORIGIN;
 432	}
 433
 434	virtual void EnterState(State* previousState)
 435	{
 436		if ((fWindow->Flags() & B_NOT_RESIZABLE) != 0)
 437			fHorizontal = fVertical = NONE;
 438		else {
 439			if ((fWindow->Flags() & B_NOT_H_RESIZABLE) != 0)
 440				fHorizontal = NONE;
 441
 442			if ((fWindow->Flags() & B_NOT_V_RESIZABLE) != 0)
 443				fVertical = NONE;
 444		}
 445		fBehavior._SetResizeCursor(fHorizontal, fVertical);
 446	}
 447
 448	virtual void ExitState(State* nextState)
 449	{
 450		fBehavior._ResetResizeCursor();
 451
 452		if (fWindow->Flags() & B_OUTLINE_RESIZE) {
 453			fDesktop->SetWindowOutlinesDelta(fWindow, B_ORIGIN);
 454			fDesktop->ResizeWindowBy(fWindow, fDelta.x, fDelta.y);
 455		}
 456	}
 457
 458	virtual void MouseMovedAction(BPoint& delta, bigtime_t now)
 459	{
 460		if (fHorizontal == NONE)
 461			delta.x = 0;
 462		if (fVertical == NONE)
 463			delta.y = 0;
 464
 465		if (delta.x == 0 && delta.y == 0)
 466			return;
 467
 468		// Resize first -- due to the window size limits this is not unlikely
 469		// to turn out differently from what we request.
 470		BPoint oldRightBottom = fWindow->Frame().RightBottom();
 471
 472		if (fWindow->Flags() & B_OUTLINE_RESIZE) {
 473			fDelta = delta;
 474			fDesktop->SetWindowOutlinesDelta(fWindow, BPoint(
 475				delta.x * fHorizontal, delta.y * fVertical));
 476		} else {
 477			fDesktop->ResizeWindowBy(fWindow, delta.x * fHorizontal,
 478				delta.y * fVertical);
 479		}
 480
 481		// constrain delta to true change in size
 482		delta = fWindow->Frame().RightBottom() - oldRightBottom;
 483		delta.x *= fHorizontal;
 484		delta.y *= fVertical;
 485
 486		// see, if we have to move, too
 487		float moveX = fHorizontal == LEFT ? delta.x : 0;
 488		float moveY = fVertical == TOP ? delta.y : 0;
 489
 490		if (moveX != 0 || moveY != 0)
 491			fDesktop->MoveWindowBy(fWindow, moveX, moveY);
 492	}
 493
 494	virtual void MouseUpWindowAction()
 495	{
 496		fDesktop->SendWindowBehind(fWindow);
 497	}
 498
 499private:
 500	int8	fHorizontal;
 501	int8	fVertical;
 502};
 503
 504
 505// #pragma mark - DecoratorButtonState
 506
 507
 508struct DefaultWindowBehaviour::DecoratorButtonState : State {
 509	DecoratorButtonState(DefaultWindowBehaviour& behavior,
 510		int32 tab, Decorator::Region button)
 511		:
 512		State(behavior),
 513		fTab(tab),
 514		fButton(button)
 515	{
 516	}
 517
 518	virtual void EnterState(State* previousState)
 519	{
 520		_RedrawDecorator(NULL);
 521	}
 522
 523	virtual void MouseUp(BMessage* message, BPoint where)
 524	{
 525		// ignore, if it's not the primary mouse button
 526		int32 buttons = message->FindInt32("buttons");
 527		if ((buttons & B_PRIMARY_MOUSE_BUTTON) != 0)
 528			return;
 529
 530		// redraw the decorator
 531		if (Decorator* decorator = fWindow->Decorator()) {
 532			BRegion* visibleBorder = fWindow->RegionPool()->GetRegion();
 533			fWindow->GetBorderRegion(visibleBorder);
 534			visibleBorder->IntersectWith(&fWindow->VisibleRegion());
 535
 536			DrawingEngine* engine = decorator->GetDrawingEngine();
 537			engine->LockParallelAccess();
 538			engine->ConstrainClippingRegion(visibleBorder);
 539
 540			int32 tab;
 541			switch (fButton) {
 542				case Decorator::REGION_CLOSE_BUTTON:
 543					decorator->SetClose(fTab, false);
 544					if (fBehavior._RegionFor(message, tab) == fButton)
 545						fWindow->ServerWindow()->NotifyQuitRequested();
 546					break;
 547
 548				case Decorator::REGION_ZOOM_BUTTON:
 549					decorator->SetZoom(fTab, false);
 550					if (fBehavior._RegionFor(message, tab) == fButton)
 551						fWindow->ServerWindow()->NotifyZoom();
 552					break;
 553
 554				case Decorator::REGION_MINIMIZE_BUTTON:
 555					decorator->SetMinimize(fTab, false);
 556					if (fBehavior._RegionFor(message, tab) == fButton)
 557						fWindow->ServerWindow()->NotifyMinimize(true);
 558					break;
 559
 560				default:
 561					break;
 562			}
 563
 564			engine->UnlockParallelAccess();
 565
 566			fWindow->RegionPool()->Recycle(visibleBorder);
 567		}
 568
 569		fBehavior._NextState(NULL);
 570	}
 571
 572	virtual void MouseMoved(BMessage* message, BPoint where, bool isFake)
 573	{
 574		_RedrawDecorator(message);
 575	}
 576
 577private:
 578	void _RedrawDecorator(const BMessage* message)
 579	{
 580		if (Decorator* decorator = fWindow->Decorator()) {
 581			BRegion* visibleBorder = fWindow->RegionPool()->GetRegion();
 582			fWindow->GetBorderRegion(visibleBorder);
 583			visibleBorder->IntersectWith(&fWindow->VisibleRegion());
 584
 585			DrawingEngine* engine = decorator->GetDrawingEngine();
 586			engine->LockParallelAccess();
 587			engine->ConstrainClippingRegion(visibleBorder);
 588
 589			int32 tab;
 590			Decorator::Region hitRegion = message != NULL
 591				? fBehavior._RegionFor(message, tab) : fButton;
 592
 593			switch (fButton) {
 594				case Decorator::REGION_CLOSE_BUTTON:
 595					decorator->SetClose(fTab, hitRegion == fButton);
 596					break;
 597
 598				case Decorator::REGION_ZOOM_BUTTON:
 599					decorator->SetZoom(fTab, hitRegion == fButton);
 600					break;
 601
 602				case Decorator::REGION_MINIMIZE_BUTTON:
 603					decorator->SetMinimize(fTab, hitRegion == fButton);
 604					break;
 605
 606				default:
 607					break;
 608			}
 609
 610			engine->UnlockParallelAccess();
 611			fWindow->RegionPool()->Recycle(visibleBorder);
 612		}
 613	}
 614
 615protected:
 616	int32				fTab;
 617	Decorator::Region	fButton;
 618};
 619
 620
 621// #pragma mark - ManageWindowState
 622
 623
 624struct DefaultWindowBehaviour::ManageWindowState : State {
 625	ManageWindowState(DefaultWindowBehaviour& behavior, BPoint where)
 626		:
 627		State(behavior),
 628		fLastMousePosition(where),
 629		fHorizontal(NONE),
 630		fVertical(NONE)
 631	{
 632	}
 633
 634	virtual void EnterState(State* previousState)
 635	{
 636		_UpdateBorders(fLastMousePosition);
 637	}
 638
 639	virtual void ExitState(State* nextState)
 640	{
 641		fBehavior._SetBorderHighlights(fHorizontal, fVertical, false);
 642	}
 643
 644	virtual bool MouseDown(BMessage* message, BPoint where, bool& _unhandled)
 645	{
 646		// We're only interested if the secondary mouse button was pressed,
 647		// otherwise let the caller handle the event.
 648		int32 buttons = message->FindInt32("buttons");
 649		if ((buttons & B_SECONDARY_MOUSE_BUTTON) == 0) {
 650			_unhandled = true;
 651			return true;
 652		}
 653
 654		fBehavior._NextState(new (std::nothrow) ResizeBorderState(fBehavior,
 655			where, fHorizontal, fVertical));
 656		return true;
 657	}
 658
 659	virtual void MouseMoved(BMessage* message, BPoint where, bool isFake)
 660	{
 661		// If the mouse is still over our window, update the borders. Otherwise
 662		// leave the state.
 663		if (fDesktop->WindowAt(where) == fWindow) {
 664			fLastMousePosition = where;
 665			_UpdateBorders(fLastMousePosition);
 666		} else
 667			fBehavior._NextState(NULL);
 668	}
 669
 670	virtual void ModifiersChanged(BPoint where, int32 modifiers)
 671	{
 672		if (!fBehavior._IsWindowModifier(modifiers))
 673			fBehavior._NextState(NULL);
 674	}
 675
 676private:
 677	void _UpdateBorders(BPoint where)
 678	{
 679		if ((fWindow->Flags() & B_NOT_RESIZABLE) != 0)
 680			return;
 681
 682		// Compute the window center relative location of where. We divide by
 683		// the width respective the height, so we compensate for the window's
 684		// aspect ratio.
 685		BRect frame(fWindow->Frame());
 686		if (frame.Width() + 1 == 0 || frame.Height() + 1 == 0)
 687			return;
 688
 689		float x = (where.x - (frame.left + frame.right) / 2)
 690			/ (frame.Width() + 1);
 691		float y = (where.y - (frame.top + frame.bottom) / 2)
 692			/ (frame.Height() + 1);
 693
 694		// compute the resize direction
 695		int8 horizontal;
 696		int8 vertical;
 697		_ComputeResizeDirection(x, y, horizontal, vertical);
 698
 699		if ((fWindow->Flags() & B_NOT_H_RESIZABLE) != 0)
 700			horizontal = NONE;
 701		if ((fWindow->Flags() & B_NOT_V_RESIZABLE) != 0)
 702			vertical = NONE;
 703
 704		// update the highlight, if necessary
 705		if (horizontal != fHorizontal || vertical != fVertical) {
 706			fBehavior._SetBorderHighlights(fHorizontal, fVertical, false);
 707			fHorizontal = horizontal;
 708			fVertical = vertical;
 709			fBehavior._SetBorderHighlights(fHorizontal, fVertical, true);
 710		}
 711	}
 712
 713private:
 714	BPoint	fLastMousePosition;
 715	int8	fHorizontal;
 716	int8	fVertical;
 717};
 718
 719
 720// #pragma mark - DefaultWindowBehaviour
 721
 722
 723DefaultWindowBehaviour::DefaultWindowBehaviour(Window* window)
 724	:
 725	fWindow(window),
 726	fDesktop(window->Desktop()),
 727	fLastModifiers(0)
 728{
 729}
 730
 731
 732DefaultWindowBehaviour::~DefaultWindowBehaviour()
 733{
 734}
 735
 736
 737bool
 738DefaultWindowBehaviour::MouseDown(BMessage* message, BPoint where,
 739	int32 lastHitRegion, int32& clickCount, int32& _hitRegion)
 740{
 741	fLastModifiers = message->FindInt32("modifiers");
 742	int32 buttons = message->FindInt32("buttons");
 743
 744	int32 numButtons;
 745	if (get_mouse_type(&numButtons) == B_OK) {
 746		switch (numButtons) {
 747			case 1:
 748				// 1 button mouse
 749				if ((fLastModifiers & B_CONTROL_KEY) != 0) {
 750					// emulate right click by holding control
 751					buttons = B_SECONDARY_MOUSE_BUTTON;
 752					message->ReplaceInt32("buttons", buttons);
 753				}
 754				break;
 755
 756			case 2:
 757				// TODO: 2 button mouse, pressing both buttons simultaneously
 758				// emulates middle click
 759
 760			default:
 761				break;
 762		}
 763	}
 764
 765	// if a state is active, let it do the job
 766	if (fState.IsSet()) {
 767		bool unhandled = false;
 768		bool result = fState->MouseDown(message, where, unhandled);
 769		if (!unhandled)
 770			return result;
 771	}
 772
 773	// No state active yet, or it wants us to handle the event -- determine the
 774	// click region and decide what to do.
 775
 776	Decorator* decorator = fWindow->Decorator();
 777
 778	Decorator::Region hitRegion = Decorator::REGION_NONE;
 779	int32 tab = -1;
 780	Action action = ACTION_NONE;
 781
 782	bool inBorderRegion = false;
 783	if (decorator != NULL)
 784		inBorderRegion = decorator->GetFootprint().Contains(where);
 785
 786	bool windowModifier = _IsWindowModifier(fLastModifiers);
 787
 788	if (windowModifier || inBorderRegion) {
 789		// click on the window decorator or we have the window modifier keys
 790		// held
 791
 792		// get the functional hit region
 793		if (windowModifier) {
 794			// click with window modifier keys -- let the whole window behave
 795			// like the border
 796			hitRegion = Decorator::REGION_LEFT_BORDER;
 797		} else {
 798			// click on the decorator -- get the exact region
 799			hitRegion = _RegionFor(message, tab);
 800		}
 801
 802		// translate the region into an action
 803		uint32 flags = fWindow->Flags();
 804
 805		if ((buttons & B_PRIMARY_MOUSE_BUTTON) != 0) {
 806			// left mouse button
 807			switch (hitRegion) {
 808				case Decorator::REGION_TAB: {
 809					// tab sliding in any case if either shift key is held down
 810					// except sliding up-down by moving mouse left-right would
 811					// look strange
 812					if ((fLastModifiers & B_SHIFT_KEY) != 0
 813						&& fWindow->Look() != kLeftTitledWindowLook) {
 814						action = ACTION_SLIDE_TAB;
 815						break;
 816					}
 817					action = ACTION_DRAG;
 818					break;
 819				}
 820
 821				case Decorator::REGION_LEFT_BORDER:
 822				case Decorator::REGION_RIGHT_BORDER:
 823				case Decorator::REGION_TOP_BORDER:
 824				case Decorator::REGION_BOTTOM_BORDER:
 825					action = ACTION_DRAG;
 826					break;
 827
 828				case Decorator::REGION_CLOSE_BUTTON:
 829					action = (flags & B_NOT_CLOSABLE) == 0
 830						? ACTION_CLOSE : ACTION_DRAG;
 831					break;
 832
 833				case Decorator::REGION_ZOOM_BUTTON:
 834					action = (flags & B_NOT_ZOOMABLE) == 0
 835						? ACTION_ZOOM : ACTION_DRAG;
 836					break;
 837
 838				case Decorator::REGION_MINIMIZE_BUTTON:
 839					action = (flags & B_NOT_MINIMIZABLE) == 0
 840						? ACTION_MINIMIZE : ACTION_DRAG;
 841					break;
 842
 843				case Decorator::REGION_LEFT_TOP_CORNER:
 844				case Decorator::REGION_LEFT_BOTTOM_CORNER:
 845				case Decorator::REGION_RIGHT_TOP_CORNER:
 846					// TODO: Handle correctly!
 847					action = ACTION_DRAG;
 848					break;
 849
 850				case Decorator::REGION_RIGHT_BOTTOM_CORNER:
 851					action = (flags & B_NOT_RESIZABLE) == 0
 852						? ACTION_RESIZE : ACTION_DRAG;
 853					break;
 854
 855				default:
 856					break;
 857			}
 858		} else if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0) {
 859			// right mouse button
 860			switch (hitRegion) {
 861				case Decorator::REGION_TAB:
 862				case Decorator::REGION_LEFT_BORDER:
 863				case Decorator::REGION_RIGHT_BORDER:
 864				case Decorator::REGION_TOP_BORDER:
 865				case Decorator::REGION_BOTTOM_BORDER:
 866				case Decorator::REGION_CLOSE_BUTTON:
 867				case Decorator::REGION_ZOOM_BUTTON:
 868				case Decorator::REGION_MINIMIZE_BUTTON:
 869				case Decorator::REGION_LEFT_TOP_CORNER:
 870				case Decorator::REGION_LEFT_BOTTOM_CORNER:
 871				case Decorator::REGION_RIGHT_TOP_CORNER:
 872				case Decorator::REGION_RIGHT_BOTTOM_CORNER:
 873					action = ACTION_RESIZE_BORDER;
 874					break;
 875
 876				default:
 877					break;
 878			}
 879		}
 880	}
 881
 882	_hitRegion = (int32)hitRegion;
 883
 884	if (action == ACTION_NONE) {
 885		// No action -- if this is a click inside the window's contents,
 886		// let it be forwarded to the window.
 887		return inBorderRegion;
 888	}
 889
 890	// reset the click count, if the hit region differs from the previous one
 891	if (hitRegion != lastHitRegion)
 892		clickCount = 1;
 893
 894	DesktopSettings desktopSettings(fDesktop);
 895	if (!desktopSettings.AcceptFirstClick()) {
 896		// Ignore clicks on decorator buttons if the
 897		// non-floating window doesn't have focus
 898		if (!fWindow->IsFocus() && !fWindow->IsFloating()
 899			&& action != ACTION_RESIZE_BORDER
 900			&& action != ACTION_RESIZE && action != ACTION_SLIDE_TAB)
 901			action = ACTION_DRAG;
 902	}
 903
 904	bool activateOnMouseUp = false;
 905	if (action != ACTION_RESIZE_BORDER) {
 906		// activate window if in click to activate mode, else only focus it
 907		if (desktopSettings.MouseMode() == B_NORMAL_MOUSE) {
 908			fDesktop->ActivateWindow(fWindow);
 909		} else {
 910			fDesktop->SetFocusWindow(fWindow);
 911			activateOnMouseUp = true;
 912		}
 913	}
 914
 915	// switch to the new state
 916	switch (action) {
 917		case ACTION_CLOSE:
 918		case ACTION_ZOOM:
 919		case ACTION_MINIMIZE:
 920			_NextState(
 921				new (std::nothrow) DecoratorButtonState(*this, tab, hitRegion));
 922			STRACE_CLICK(("===> ACTION_CLOSE/ZOOM/MINIMIZE\n"));
 923			break;
 924
 925		case ACTION_DRAG:
 926			_NextState(new (std::nothrow) DragState(*this, where,
 927				activateOnMouseUp, clickCount == 2));
 928			STRACE_CLICK(("===> ACTION_DRAG\n"));
 929			break;
 930
 931		case ACTION_RESIZE:
 932			_NextState(new (std::nothrow) ResizeState(*this, where,
 933				activateOnMouseUp));
 934			STRACE_CLICK(("===> ACTION_RESIZE\n"));
 935			break;
 936
 937		case ACTION_SLIDE_TAB:
 938			_NextState(new (std::nothrow) SlideTabState(*this, where));
 939			STRACE_CLICK(("===> ACTION_SLIDE_TAB\n"));
 940			break;
 941
 942		case ACTION_RESIZE_BORDER:
 943			_NextState(new (std::nothrow) ResizeBorderState(*this, where,
 944				hitRegion));
 945			STRACE_CLICK(("===> ACTION_RESIZE_BORDER\n"));
 946			break;
 947
 948		default:
 949			break;
 950	}
 951
 952	return true;
 953}
 954
 955
 956void
 957DefaultWindowBehaviour::MouseUp(BMessage* message, BPoint where)
 958{
 959	if (fState.IsSet())
 960		fState->MouseUp(message, where);
 961}
 962
 963
 964void
 965DefaultWindowBehaviour::MouseMoved(BMessage* message, BPoint where, bool isFake)
 966{
 967	if (fState.IsSet()) {
 968		fState->MouseMoved(message, where, isFake);
 969	} else {
 970		// If the window modifiers are hold, enter the window management state.
 971		if (_IsWindowModifier(message->FindInt32("modifiers")))
 972			_NextState(new(std::nothrow) ManageWindowState(*this, where));
 973	}
 974
 975	// change focus in FFM mode
 976	DesktopSettings desktopSettings(fDesktop);
 977	if (desktopSettings.FocusFollowsMouse()
 978		&& !fWindow->IsFocus() && (fWindow->Flags() & B_AVOID_FOCUS) == 0) {
 979		// If the mouse move is a fake one, we set the focus to NULL, which
 980		// will cause the window that had focus last to retrieve it again - this
 981		// makes FFM much nicer to use with the keyboard.
 982		fDesktop->SetFocusWindow(isFake ? NULL : fWindow);
 983	}
 984}
 985
 986
 987void
 988DefaultWindowBehaviour::ModifiersChanged(int32 modifiers)
 989{
 990	BPoint where;
 991	int32 buttons;
 992	fDesktop->GetLastMouseState(&where, &buttons);
 993
 994	if (fState.IsSet()) {
 995		fState->ModifiersChanged(where, modifiers);
 996	} else {
 997		// If the window modifiers are hold, enter the window management state.
 998		if (_IsWindowModifier(modifiers))
 999			_NextState(new(std::nothrow) ManageWindowState(*this, where));
1000	}
1001}
1002
1003
1004bool
1005DefaultWindowBehaviour::AlterDeltaForSnap(Window* window, BPoint& delta,
1006	bigtime_t now)
1007{
1008	return fMagneticBorder.AlterDeltaForSnap(window, delta, now);
1009}
1010
1011
1012bool
1013DefaultWindowBehaviour::_IsWindowModifier(int32 modifiers) const
1014{
1015	return (fWindow->Flags() & B_NO_SERVER_SIDE_WINDOW_MODIFIERS) == 0
1016		&& (modifiers & (B_COMMAND_KEY | B_CONTROL_KEY | B_OPTION_KEY
1017				| B_SHIFT_KEY)) == (B_COMMAND_KEY | B_CONTROL_KEY);
1018}
1019
1020
1021Decorator::Region
1022DefaultWindowBehaviour::_RegionFor(const BMessage* message, int32& tab) const
1023{
1024	Decorator* decorator = fWindow->Decorator();
1025	if (decorator == NULL)
1026		return Decorator::REGION_NONE;
1027
1028	BPoint where;
1029	if (message->FindPoint("where", &where) != B_OK)
1030		return Decorator::REGION_NONE;
1031
1032	return decorator->RegionAt(where, tab);
1033}
1034
1035
1036void
1037DefaultWindowBehaviour::_SetBorderHighlights(int8 horizontal, int8 vertical,
1038	bool active)
1039{
1040	if (Decorator* decorator = fWindow->Decorator()) {
1041		uint8 highlight = active
1042			? Decorator::HIGHLIGHT_RESIZE_BORDER
1043			: Decorator::HIGHLIGHT_NONE;
1044
1045		// set the highlights for the borders
1046		BRegion dirtyRegion;
1047		switch (horizontal) {
1048			case LEFT:
1049				decorator->SetRegionHighlight(Decorator::REGION_LEFT_BORDER,
1050					highlight, &dirtyRegion);
1051				break;
1052			case RIGHT:
1053				decorator->SetRegionHighlight(
1054					Decorator::REGION_RIGHT_BORDER, highlight,
1055					&dirtyRegion);
1056				break;
1057		}
1058
1059		switch (vertical) {
1060			case TOP:
1061				decorator->SetRegionHighlight(Decorator::REGION_TOP_BORDER,
1062					highlight, &dirtyRegion);
1063				break;
1064			case BOTTOM:
1065				decorator->SetRegionHighlight(
1066					Decorator::REGION_BOTTOM_BORDER, highlight,
1067					&dirtyRegion);
1068				break;
1069		}
1070
1071		// set the highlights for the corners
1072		if (horizontal != NONE && vertical != NONE) {
1073			if (horizontal == LEFT) {
1074				if (vertical == TOP) {
1075					decorator->SetRegionHighlight(
1076						Decorator::REGION_LEFT_TOP_CORNER, highlight,
1077						&dirtyRegion);
1078				} else {
1079					decorator->SetRegionHighlight(
1080						Decorator::REGION_LEFT_BOTTOM_CORNER, highlight,
1081						&dirtyRegion);
1082				}
1083			} else {
1084				if (vertical == TOP) {
1085					decorator->SetRegionHighlight(
1086						Decorator::REGION_RIGHT_TOP_CORNER, highlight,
1087						&dirtyRegion);
1088				} else {
1089					decorator->SetRegionHighlight(
1090						Decorator::REGION_RIGHT_BOTTOM_CORNER, highlight,
1091						&dirtyRegion);
1092				}
1093			}
1094		}
1095
1096		// invalidate the affected regions
1097		fWindow->ProcessDirtyRegion(dirtyRegion);
1098	}
1099}
1100
1101
1102ServerCursor*
1103DefaultWindowBehaviour::_ResizeCursorFor(int8 horizontal, int8 vertical)
1104{
1105	// get the cursor ID corresponding to the border/corner
1106	BCursorID cursorID = B_CURSOR_ID_SYSTEM_DEFAULT;
1107
1108	if (horizontal == LEFT) {
1109		if (vertical == TOP)
1110			cursorID = B_CURSOR_ID_RESIZE_NORTH_WEST;
1111		else if (vertical == BOTTOM)
1112			cursorID = B_CURSOR_ID_RESIZE_SOUTH_WEST;
1113		else
1114			cursorID = B_CURSOR_ID_RESIZE_WEST;
1115	} else if (horizontal == RIGHT) {
1116		if (vertical == TOP)
1117			cursorID = B_CURSOR_ID_RESIZE_NORTH_EAST;
1118		else if (vertical == BOTTOM)
1119			cursorID = B_CURSOR_ID_RESIZE_SOUTH_EAST;
1120		else
1121			cursorID = B_CURSOR_ID_RESIZE_EAST;
1122	} else {
1123		if (vertical == TOP)
1124			cursorID = B_CURSOR_ID_RESIZE_NORTH;
1125		else if (vertical == BOTTOM)
1126			cursorID = B_CURSOR_ID_RESIZE_SOUTH;
1127	}
1128
1129	return fDesktop->GetCursorManager().GetCursor(cursorID);
1130}
1131
1132
1133void
1134DefaultWindowBehaviour::_SetResizeCursor(int8 horizontal, int8 vertical)
1135{
1136	fDesktop->SetManagementCursor(_ResizeCursorFor(horizontal, vertical));
1137}
1138
1139
1140void
1141DefaultWindowBehaviour::_ResetResizeCursor()
1142{
1143	fDesktop->SetManagementCursor(NULL);
1144}
1145
1146
1147/*static*/ void
1148DefaultWindowBehaviour::_ComputeResizeDirection(float x, float y,
1149	int8& _horizontal, int8& _vertical)
1150{
1151	_horizontal = NONE;
1152	_vertical = NONE;
1153
1154	// compute the angle
1155	if (x == 0 && y == 0)
1156		return;
1157
1158	float angle = atan2f(y, x);
1159
1160	// rotate by 22.5 degree to align our sectors with 45 degree multiples
1161	angle += M_PI / 8;
1162
1163	// add 180 degree to the negative values, so we get a nice 0 to 360
1164	// degree range
1165	if (angle < 0)
1166		angle += M_PI * 2;
1167
1168	switch (int(angle / M_PI_4)) {
1169		case 0:
1170			_horizontal = RIGHT;
1171			break;
1172		case 1:
1173			_horizontal = RIGHT;
1174			_vertical = BOTTOM;
1175			break;
1176		case 2:
1177			_vertical = BOTTOM;
1178			break;
1179		case 3:
1180			_horizontal = LEFT;
1181			_vertical = BOTTOM;
1182			break;
1183		case 4:
1184			_horizontal = LEFT;
1185			break;
1186		case 5:
1187			_horizontal = LEFT;
1188			_vertical = TOP;
1189			break;
1190		case 6:
1191			_vertical = TOP;
1192			break;
1193		case 7:
1194		default:
1195			_horizontal = RIGHT;
1196			_vertical = TOP;
1197			break;
1198	}
1199}
1200
1201
1202void
1203DefaultWindowBehaviour::_NextState(State* state)
1204{
1205	// exit the old state
1206	if (fState.IsSet())
1207		fState->ExitState(state);
1208
1209	// set and enter the new state
1210	ObjectDeleter<State> oldState(fState.Detach());
1211	fState.SetTo(state);
1212
1213	if (fState.IsSet()) {
1214		fState->EnterState(oldState.Get());
1215		fDesktop->SetMouseEventWindow(fWindow);
1216	} else if (oldState.IsSet()) {
1217		// no state anymore -- reset the mouse event window, if it's still us
1218		if (fDesktop->MouseEventWindow() == fWindow)
1219			fDesktop->SetMouseEventWindow(NULL);
1220	}
1221}