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