xref: /haiku/src/servers/app/decorator/DefaultWindowBehaviour.cpp (revision 0044a8c39ab5721051b6279506d1a8c511e20453)
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)) {
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)) {
263 			if (fWindow->Flags() & B_NOT_V_RESIZABLE)
264 				delta.y = 0;
265 			if (fWindow->Flags() & B_NOT_H_RESIZABLE)
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 >
332 				neighbourTab->tabOffset + neighbourTab->tabRect.Width() / 2)
333 				return;
334 		} else {
335 			if (location + movingTab->tabRect.Width() <
336 				neighbourTab->tabOffset + neighbourTab->tabRect.Width() / 2)
337 				return;
338 		}
339 
340 		fWindow->MoveToStackPosition(neighbourIndex, isShifting);
341 	}
342 };
343 
344 
345 // #pragma mark - ResizeBorderState
346 
347 
348 struct DefaultWindowBehaviour::ResizeBorderState : MouseTrackingState {
349 	ResizeBorderState(DefaultWindowBehaviour& behavior, BPoint where,
350 		Decorator::Region region)
351 		:
352 		MouseTrackingState(behavior, where, true, false,
353 			B_SECONDARY_MOUSE_BUTTON),
354 		fHorizontal(NONE),
355 		fVertical(NONE)
356 	{
357 		switch (region) {
358 			case Decorator::REGION_TAB:
359 				// TODO: Handle like the border it is attached to (top/left)?
360 				break;
361 			case Decorator::REGION_LEFT_BORDER:
362 				fHorizontal = LEFT;
363 				break;
364 			case Decorator::REGION_RIGHT_BORDER:
365 				fHorizontal = RIGHT;
366 				break;
367 			case Decorator::REGION_TOP_BORDER:
368 				fVertical = TOP;
369 				break;
370 			case Decorator::REGION_BOTTOM_BORDER:
371 				fVertical = BOTTOM;
372 				break;
373 			case Decorator::REGION_LEFT_TOP_CORNER:
374 				fHorizontal = LEFT;
375 				fVertical = TOP;
376 				break;
377 			case Decorator::REGION_LEFT_BOTTOM_CORNER:
378 				fHorizontal = LEFT;
379 				fVertical = BOTTOM;
380 				break;
381 			case Decorator::REGION_RIGHT_TOP_CORNER:
382 				fHorizontal = RIGHT;
383 				fVertical = TOP;
384 				break;
385 			case Decorator::REGION_RIGHT_BOTTOM_CORNER:
386 				fHorizontal = RIGHT;
387 				fVertical = BOTTOM;
388 				break;
389 			default:
390 				break;
391 		}
392 	}
393 
394 	ResizeBorderState(DefaultWindowBehaviour& behavior, BPoint where,
395 		int8 horizontal, int8 vertical)
396 		:
397 		MouseTrackingState(behavior, where, true, false,
398 			B_SECONDARY_MOUSE_BUTTON),
399 		fHorizontal(horizontal),
400 		fVertical(vertical)
401 	{
402 	}
403 
404 	virtual void EnterState(State* previousState)
405 	{
406 		if ((fWindow->Flags() & B_NOT_RESIZABLE) != 0)
407 			fHorizontal = fVertical = NONE;
408 		else {
409 			if ((fWindow->Flags() & B_NOT_H_RESIZABLE) != 0)
410 				fHorizontal = NONE;
411 
412 			if ((fWindow->Flags() & B_NOT_V_RESIZABLE) != 0)
413 				fVertical = NONE;
414 		}
415 		fBehavior._SetResizeCursor(fHorizontal, fVertical);
416 	}
417 
418 	virtual void ExitState(State* nextState)
419 	{
420 		fBehavior._ResetResizeCursor();
421 	}
422 
423 	virtual void MouseMovedAction(BPoint& delta, bigtime_t now)
424 	{
425 		if (fHorizontal == NONE)
426 			delta.x = 0;
427 		if (fVertical == NONE)
428 			delta.y = 0;
429 
430 		if (delta.x == 0 && delta.y == 0)
431 			return;
432 
433 		// Resize first -- due to the window size limits this is not unlikely
434 		// to turn out differently from what we request.
435 		BPoint oldRightBottom = fWindow->Frame().RightBottom();
436 
437 		fDesktop->ResizeWindowBy(fWindow, delta.x * fHorizontal,
438 			delta.y * fVertical);
439 
440 		// constrain delta to true change in size
441 		delta = fWindow->Frame().RightBottom() - oldRightBottom;
442 		delta.x *= fHorizontal;
443 		delta.y *= fVertical;
444 
445 		// see, if we have to move, too
446 		float moveX = fHorizontal == LEFT ? delta.x : 0;
447 		float moveY = fVertical == TOP ? delta.y : 0;
448 
449 		if (moveX != 0 || moveY != 0)
450 			fDesktop->MoveWindowBy(fWindow, moveX, moveY);
451 	}
452 
453 	virtual void MouseUpWindowAction()
454 	{
455 		fDesktop->SendWindowBehind(fWindow);
456 	}
457 
458 private:
459 	int8	fHorizontal;
460 	int8	fVertical;
461 };
462 
463 
464 // #pragma mark - DecoratorButtonState
465 
466 
467 struct DefaultWindowBehaviour::DecoratorButtonState : State {
468 	DecoratorButtonState(DefaultWindowBehaviour& behavior,
469 		int32 tab, Decorator::Region button)
470 		:
471 		State(behavior),
472 		fTab(tab),
473 		fButton(button)
474 	{
475 	}
476 
477 	virtual void EnterState(State* previousState)
478 	{
479 		_RedrawDecorator(NULL);
480 	}
481 
482 	virtual void MouseUp(BMessage* message, BPoint where)
483 	{
484 		// ignore, if it's not the primary mouse button
485 		int32 buttons = message->FindInt32("buttons");
486 		if ((buttons & B_PRIMARY_MOUSE_BUTTON) != 0)
487 			return;
488 
489 		// redraw the decorator
490 		if (Decorator* decorator = fWindow->Decorator()) {
491 			BRegion* visibleBorder = fWindow->RegionPool()->GetRegion();
492 			fWindow->GetBorderRegion(visibleBorder);
493 			visibleBorder->IntersectWith(&fWindow->VisibleRegion());
494 
495 			DrawingEngine* engine = decorator->GetDrawingEngine();
496 			engine->LockParallelAccess();
497 			engine->ConstrainClippingRegion(visibleBorder);
498 
499 			int32 tab;
500 			switch (fButton) {
501 				case Decorator::REGION_CLOSE_BUTTON:
502 					decorator->SetClose(fTab, false);
503 					if (fBehavior._RegionFor(message, tab) == fButton)
504 						fWindow->ServerWindow()->NotifyQuitRequested();
505 					break;
506 
507 				case Decorator::REGION_ZOOM_BUTTON:
508 					decorator->SetZoom(fTab, false);
509 					if (fBehavior._RegionFor(message, tab) == fButton)
510 						fWindow->ServerWindow()->NotifyZoom();
511 					break;
512 
513 				case Decorator::REGION_MINIMIZE_BUTTON:
514 					decorator->SetMinimize(fTab, false);
515 					if (fBehavior._RegionFor(message, tab) == fButton)
516 						fWindow->ServerWindow()->NotifyMinimize(true);
517 					break;
518 
519 				default:
520 					break;
521 			}
522 
523 			engine->UnlockParallelAccess();
524 
525 			fWindow->RegionPool()->Recycle(visibleBorder);
526 		}
527 
528 		fBehavior._NextState(NULL);
529 	}
530 
531 	virtual void MouseMoved(BMessage* message, BPoint where, bool isFake)
532 	{
533 		_RedrawDecorator(message);
534 	}
535 
536 private:
537 	void _RedrawDecorator(const BMessage* message)
538 	{
539 		if (Decorator* decorator = fWindow->Decorator()) {
540 			BRegion* visibleBorder = fWindow->RegionPool()->GetRegion();
541 			fWindow->GetBorderRegion(visibleBorder);
542 			visibleBorder->IntersectWith(&fWindow->VisibleRegion());
543 
544 			DrawingEngine* engine = decorator->GetDrawingEngine();
545 			engine->LockParallelAccess();
546 			engine->ConstrainClippingRegion(visibleBorder);
547 
548 			int32 tab;
549 			Decorator::Region hitRegion = message != NULL
550 				? fBehavior._RegionFor(message, tab) : fButton;
551 
552 			switch (fButton) {
553 				case Decorator::REGION_CLOSE_BUTTON:
554 					decorator->SetClose(fTab, hitRegion == fButton);
555 					break;
556 
557 				case Decorator::REGION_ZOOM_BUTTON:
558 					decorator->SetZoom(fTab, hitRegion == fButton);
559 					break;
560 
561 				case Decorator::REGION_MINIMIZE_BUTTON:
562 					decorator->SetMinimize(fTab, hitRegion == fButton);
563 					break;
564 
565 				default:
566 					break;
567 			}
568 
569 			engine->UnlockParallelAccess();
570 			fWindow->RegionPool()->Recycle(visibleBorder);
571 		}
572 	}
573 
574 protected:
575 	int32				fTab;
576 	Decorator::Region	fButton;
577 };
578 
579 
580 // #pragma mark - ManageWindowState
581 
582 
583 struct DefaultWindowBehaviour::ManageWindowState : State {
584 	ManageWindowState(DefaultWindowBehaviour& behavior, BPoint where)
585 		:
586 		State(behavior),
587 		fLastMousePosition(where),
588 		fHorizontal(NONE),
589 		fVertical(NONE)
590 	{
591 	}
592 
593 	virtual void EnterState(State* previousState)
594 	{
595 		_UpdateBorders(fLastMousePosition);
596 	}
597 
598 	virtual void ExitState(State* nextState)
599 	{
600 		fBehavior._SetBorderHighlights(fHorizontal, fVertical, false);
601 	}
602 
603 	virtual bool MouseDown(BMessage* message, BPoint where, bool& _unhandled)
604 	{
605 		// We're only interested, if the secondary mouse button was pressed.
606 		// Othewise let the our caller handle the event.
607 		int32 buttons = message->FindInt32("buttons");
608 		if ((buttons & B_SECONDARY_MOUSE_BUTTON) == 0) {
609 			_unhandled = true;
610 			return true;
611 		}
612 
613 		fBehavior._NextState(new (std::nothrow) ResizeBorderState(fBehavior,
614 			where, fHorizontal, fVertical));
615 		return true;
616 	}
617 
618 	virtual void MouseMoved(BMessage* message, BPoint where, bool isFake)
619 	{
620 		// If the mouse is still over our window, update the borders. Otherwise
621 		// leave the state.
622 		if (fDesktop->WindowAt(where) == fWindow) {
623 			fLastMousePosition = where;
624 			_UpdateBorders(fLastMousePosition);
625 		} else
626 			fBehavior._NextState(NULL);
627 	}
628 
629 	virtual void ModifiersChanged(BPoint where, int32 modifiers)
630 	{
631 		if (!fBehavior._IsWindowModifier(modifiers))
632 			fBehavior._NextState(NULL);
633 	}
634 
635 private:
636 	void _UpdateBorders(BPoint where)
637 	{
638 		if ((fWindow->Flags() & B_NOT_RESIZABLE) != 0)
639 			return;
640 
641 		// Compute the window center relative location of where. We divide by
642 		// the width respective the height, so we compensate for the window's
643 		// aspect ratio.
644 		BRect frame(fWindow->Frame());
645 		if (frame.Width() + 1 == 0 || frame.Height() + 1 == 0)
646 			return;
647 
648 		float x = (where.x - (frame.left + frame.right) / 2)
649 			/ (frame.Width() + 1);
650 		float y = (where.y - (frame.top + frame.bottom) / 2)
651 			/ (frame.Height() + 1);
652 
653 		// compute the resize direction
654 		int8 horizontal;
655 		int8 vertical;
656 		_ComputeResizeDirection(x, y, horizontal, vertical);
657 
658 		if ((fWindow->Flags() & B_NOT_H_RESIZABLE) != 0)
659 			horizontal = NONE;
660 		if ((fWindow->Flags() & B_NOT_V_RESIZABLE) != 0)
661 			vertical = NONE;
662 
663 		// update the highlight, if necessary
664 		if (horizontal != fHorizontal || vertical != fVertical) {
665 			fBehavior._SetBorderHighlights(fHorizontal, fVertical, false);
666 			fHorizontal = horizontal;
667 			fVertical = vertical;
668 			fBehavior._SetBorderHighlights(fHorizontal, fVertical, true);
669 		}
670 	}
671 
672 private:
673 	BPoint	fLastMousePosition;
674 	int8	fHorizontal;
675 	int8	fVertical;
676 };
677 
678 
679 // #pragma mark - DefaultWindowBehaviour
680 
681 
682 DefaultWindowBehaviour::DefaultWindowBehaviour(Window* window)
683 	:
684 	fWindow(window),
685 	fDesktop(window->Desktop()),
686 	fState(NULL),
687 	fLastModifiers(0)
688 {
689 }
690 
691 
692 DefaultWindowBehaviour::~DefaultWindowBehaviour()
693 {
694 	delete fState;
695 }
696 
697 
698 bool
699 DefaultWindowBehaviour::MouseDown(BMessage* message, BPoint where,
700 	int32 lastHitRegion, int32& clickCount, int32& _hitRegion)
701 {
702 	fLastModifiers = message->FindInt32("modifiers");
703 	int32 buttons = message->FindInt32("buttons");
704 
705 	// if a state is active, let it do the job
706 	if (fState != NULL) {
707 		bool unhandled = false;
708 		bool result = fState->MouseDown(message, where, unhandled);
709 		if (!unhandled)
710 			return result;
711 	}
712 
713 	// No state active yet, or it wants us to handle the event -- determine the
714 	// click region and decide what to do.
715 
716 	Decorator* decorator = fWindow->Decorator();
717 
718 	Decorator::Region hitRegion = Decorator::REGION_NONE;
719 	int32 tab = -1;
720 	Action action = ACTION_NONE;
721 
722 	bool inBorderRegion = false;
723 	if (decorator != NULL)
724 		inBorderRegion = decorator->GetFootprint().Contains(where);
725 
726 	bool windowModifier = _IsWindowModifier(fLastModifiers);
727 
728 	if (windowModifier || inBorderRegion) {
729 		// click on the window decorator or we have the window modifier keys
730 		// held
731 
732 		// get the functional hit region
733 		if (windowModifier) {
734 			// click with window modifier keys -- let the whole window behave
735 			// like the border
736 			hitRegion = Decorator::REGION_LEFT_BORDER;
737 		} else {
738 			// click on the decorator -- get the exact region
739 			hitRegion = _RegionFor(message, tab);
740 		}
741 
742 		// translate the region into an action
743 		uint32 flags = fWindow->Flags();
744 
745 		if ((buttons & B_PRIMARY_MOUSE_BUTTON) != 0) {
746 			// left mouse button
747 			switch (hitRegion) {
748 				case Decorator::REGION_TAB: {
749 					// tab sliding in any case if either shift key is held down
750 					// except sliding up-down by moving mouse left-right would
751 					// look strange
752 					if ((fLastModifiers & B_SHIFT_KEY) != 0
753 						&& fWindow->Look() != kLeftTitledWindowLook) {
754 						action = ACTION_SLIDE_TAB;
755 						break;
756 					}
757 					action = ACTION_DRAG;
758 					break;
759 				}
760 
761 				case Decorator::REGION_LEFT_BORDER:
762 				case Decorator::REGION_RIGHT_BORDER:
763 				case Decorator::REGION_TOP_BORDER:
764 				case Decorator::REGION_BOTTOM_BORDER:
765 					action = ACTION_DRAG;
766 					break;
767 
768 				case Decorator::REGION_CLOSE_BUTTON:
769 					action = (flags & B_NOT_CLOSABLE) == 0
770 						? ACTION_CLOSE : ACTION_DRAG;
771 					break;
772 
773 				case Decorator::REGION_ZOOM_BUTTON:
774 					action = (flags & B_NOT_ZOOMABLE) == 0
775 						? ACTION_ZOOM : ACTION_DRAG;
776 					break;
777 
778 				case Decorator::REGION_MINIMIZE_BUTTON:
779 					action = (flags & B_NOT_MINIMIZABLE) == 0
780 						? ACTION_MINIMIZE : ACTION_DRAG;
781 					break;
782 
783 				case Decorator::REGION_LEFT_TOP_CORNER:
784 				case Decorator::REGION_LEFT_BOTTOM_CORNER:
785 				case Decorator::REGION_RIGHT_TOP_CORNER:
786 					// TODO: Handle correctly!
787 					action = ACTION_DRAG;
788 					break;
789 
790 				case Decorator::REGION_RIGHT_BOTTOM_CORNER:
791 					action = (flags & B_NOT_RESIZABLE) == 0
792 						? ACTION_RESIZE : ACTION_DRAG;
793 					break;
794 
795 				default:
796 					break;
797 			}
798 		} else if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0) {
799 			// right mouse button
800 			switch (hitRegion) {
801 				case Decorator::REGION_TAB:
802 				case Decorator::REGION_LEFT_BORDER:
803 				case Decorator::REGION_RIGHT_BORDER:
804 				case Decorator::REGION_TOP_BORDER:
805 				case Decorator::REGION_BOTTOM_BORDER:
806 				case Decorator::REGION_CLOSE_BUTTON:
807 				case Decorator::REGION_ZOOM_BUTTON:
808 				case Decorator::REGION_MINIMIZE_BUTTON:
809 				case Decorator::REGION_LEFT_TOP_CORNER:
810 				case Decorator::REGION_LEFT_BOTTOM_CORNER:
811 				case Decorator::REGION_RIGHT_TOP_CORNER:
812 				case Decorator::REGION_RIGHT_BOTTOM_CORNER:
813 					action = ACTION_RESIZE_BORDER;
814 					break;
815 
816 				default:
817 					break;
818 			}
819 		}
820 	}
821 
822 	_hitRegion = (int32)hitRegion;
823 
824 	if (action == ACTION_NONE) {
825 		// No action -- if this is a click inside the window's contents,
826 		// let it be forwarded to the window.
827 		return inBorderRegion;
828 	}
829 
830 	// reset the click count, if the hit region differs from the previous one
831 	if (hitRegion != lastHitRegion)
832 		clickCount = 1;
833 
834 	DesktopSettings desktopSettings(fDesktop);
835 	if (!desktopSettings.AcceptFirstClick()) {
836 		// Ignore clicks on decorator buttons if the
837 		// non-floating window doesn't have focus
838 		if (!fWindow->IsFocus() && !fWindow->IsFloating()
839 			&& action != ACTION_RESIZE_BORDER
840 			&& action != ACTION_RESIZE && action != ACTION_SLIDE_TAB)
841 			action = ACTION_DRAG;
842 	}
843 
844 	bool activateOnMouseUp = false;
845 	if (action != ACTION_RESIZE_BORDER) {
846 		// activate window if in click to activate mode, else only focus it
847 		if (desktopSettings.MouseMode() == B_NORMAL_MOUSE) {
848 			fDesktop->ActivateWindow(fWindow);
849 		} else {
850 			fDesktop->SetFocusWindow(fWindow);
851 			activateOnMouseUp = true;
852 		}
853 	}
854 
855 	// switch to the new state
856 	switch (action) {
857 		case ACTION_CLOSE:
858 		case ACTION_ZOOM:
859 		case ACTION_MINIMIZE:
860 			_NextState(
861 				new (std::nothrow) DecoratorButtonState(*this, tab, hitRegion));
862 			STRACE_CLICK(("===> ACTION_CLOSE/ZOOM/MINIMIZE\n"));
863 			break;
864 
865 		case ACTION_DRAG:
866 			_NextState(new (std::nothrow) DragState(*this, where,
867 				activateOnMouseUp, clickCount == 2));
868 			STRACE_CLICK(("===> ACTION_DRAG\n"));
869 			break;
870 
871 		case ACTION_RESIZE:
872 			_NextState(new (std::nothrow) ResizeState(*this, where,
873 				activateOnMouseUp));
874 			STRACE_CLICK(("===> ACTION_RESIZE\n"));
875 			break;
876 
877 		case ACTION_SLIDE_TAB:
878 			_NextState(new (std::nothrow) SlideTabState(*this, where));
879 			STRACE_CLICK(("===> ACTION_SLIDE_TAB\n"));
880 			break;
881 
882 		case ACTION_RESIZE_BORDER:
883 			_NextState(new (std::nothrow) ResizeBorderState(*this, where,
884 				hitRegion));
885 			STRACE_CLICK(("===> ACTION_RESIZE_BORDER\n"));
886 			break;
887 
888 		default:
889 			break;
890 	}
891 
892 	return true;
893 }
894 
895 
896 void
897 DefaultWindowBehaviour::MouseUp(BMessage* message, BPoint where)
898 {
899 	if (fState != NULL)
900 		fState->MouseUp(message, where);
901 }
902 
903 
904 void
905 DefaultWindowBehaviour::MouseMoved(BMessage* message, BPoint where, bool isFake)
906 {
907 	if (fState != NULL) {
908 		fState->MouseMoved(message, where, isFake);
909 	} else {
910 		// If the window modifiers are hold, enter the window management state.
911 		if (_IsWindowModifier(message->FindInt32("modifiers")))
912 			_NextState(new(std::nothrow) ManageWindowState(*this, where));
913 	}
914 
915 	// change focus in FFM mode
916 	DesktopSettings desktopSettings(fDesktop);
917 	if (desktopSettings.FocusFollowsMouse()
918 		&& !fWindow->IsFocus() && !(fWindow->Flags() & B_AVOID_FOCUS)) {
919 		// If the mouse move is a fake one, we set the focus to NULL, which
920 		// will cause the window that had focus last to retrieve it again - this
921 		// makes FFM much nicer to use with the keyboard.
922 		fDesktop->SetFocusWindow(isFake ? NULL : fWindow);
923 	}
924 }
925 
926 
927 void
928 DefaultWindowBehaviour::ModifiersChanged(int32 modifiers)
929 {
930 	BPoint where;
931 	int32 buttons;
932 	fDesktop->GetLastMouseState(&where, &buttons);
933 
934 	if (fState != NULL) {
935 		fState->ModifiersChanged(where, modifiers);
936 	} else {
937 		// If the window modifiers are hold, enter the window management state.
938 		if (_IsWindowModifier(modifiers))
939 			_NextState(new(std::nothrow) ManageWindowState(*this, where));
940 	}
941 }
942 
943 
944 bool
945 DefaultWindowBehaviour::AlterDeltaForSnap(Window* window, BPoint& delta,
946 	bigtime_t now)
947 {
948 	return fMagneticBorder.AlterDeltaForSnap(window, delta, now);
949 }
950 
951 
952 bool
953 DefaultWindowBehaviour::_IsWindowModifier(int32 modifiers) const
954 {
955 	return (fWindow->Flags() & B_NO_SERVER_SIDE_WINDOW_MODIFIERS) == 0
956 		&& (modifiers & (B_COMMAND_KEY | B_CONTROL_KEY | B_OPTION_KEY
957 				| B_SHIFT_KEY)) == (B_COMMAND_KEY | B_CONTROL_KEY);
958 }
959 
960 
961 Decorator::Region
962 DefaultWindowBehaviour::_RegionFor(const BMessage* message, int32& tab) const
963 {
964 	Decorator* decorator = fWindow->Decorator();
965 	if (decorator == NULL)
966 		return Decorator::REGION_NONE;
967 
968 	BPoint where;
969 	if (message->FindPoint("where", &where) != B_OK)
970 		return Decorator::REGION_NONE;
971 
972 	return decorator->RegionAt(where, tab);
973 }
974 
975 
976 void
977 DefaultWindowBehaviour::_SetBorderHighlights(int8 horizontal, int8 vertical,
978 	bool active)
979 {
980 	if (Decorator* decorator = fWindow->Decorator()) {
981 		uint8 highlight = active
982 			? Decorator::HIGHLIGHT_RESIZE_BORDER
983 			: Decorator::HIGHLIGHT_NONE;
984 
985 		// set the highlights for the borders
986 		BRegion dirtyRegion;
987 		switch (horizontal) {
988 			case LEFT:
989 				decorator->SetRegionHighlight(Decorator::REGION_LEFT_BORDER,
990 					highlight, &dirtyRegion);
991 				break;
992 			case RIGHT:
993 				decorator->SetRegionHighlight(
994 					Decorator::REGION_RIGHT_BORDER, highlight,
995 					&dirtyRegion);
996 				break;
997 		}
998 
999 		switch (vertical) {
1000 			case TOP:
1001 				decorator->SetRegionHighlight(Decorator::REGION_TOP_BORDER,
1002 					highlight, &dirtyRegion);
1003 				break;
1004 			case BOTTOM:
1005 				decorator->SetRegionHighlight(
1006 					Decorator::REGION_BOTTOM_BORDER, highlight,
1007 					&dirtyRegion);
1008 				break;
1009 		}
1010 
1011 		// set the highlights for the corners
1012 		if (horizontal != NONE && vertical != NONE) {
1013 			if (horizontal == LEFT) {
1014 				if (vertical == TOP) {
1015 					decorator->SetRegionHighlight(
1016 						Decorator::REGION_LEFT_TOP_CORNER, highlight,
1017 						&dirtyRegion);
1018 				} else {
1019 					decorator->SetRegionHighlight(
1020 						Decorator::REGION_LEFT_BOTTOM_CORNER, highlight,
1021 						&dirtyRegion);
1022 				}
1023 			} else {
1024 				if (vertical == TOP) {
1025 					decorator->SetRegionHighlight(
1026 						Decorator::REGION_RIGHT_TOP_CORNER, highlight,
1027 						&dirtyRegion);
1028 				} else {
1029 					decorator->SetRegionHighlight(
1030 						Decorator::REGION_RIGHT_BOTTOM_CORNER, highlight,
1031 						&dirtyRegion);
1032 				}
1033 			}
1034 		}
1035 
1036 		// invalidate the affected regions
1037 		fWindow->ProcessDirtyRegion(dirtyRegion);
1038 	}
1039 }
1040 
1041 
1042 ServerCursor*
1043 DefaultWindowBehaviour::_ResizeCursorFor(int8 horizontal, int8 vertical)
1044 {
1045 	// get the cursor ID corresponding to the border/corner
1046 	BCursorID cursorID = B_CURSOR_ID_SYSTEM_DEFAULT;
1047 
1048 	if (horizontal == LEFT) {
1049 		if (vertical == TOP)
1050 			cursorID = B_CURSOR_ID_RESIZE_NORTH_WEST;
1051 		else if (vertical == BOTTOM)
1052 			cursorID = B_CURSOR_ID_RESIZE_SOUTH_WEST;
1053 		else
1054 			cursorID = B_CURSOR_ID_RESIZE_WEST;
1055 	} else if (horizontal == RIGHT) {
1056 		if (vertical == TOP)
1057 			cursorID = B_CURSOR_ID_RESIZE_NORTH_EAST;
1058 		else if (vertical == BOTTOM)
1059 			cursorID = B_CURSOR_ID_RESIZE_SOUTH_EAST;
1060 		else
1061 			cursorID = B_CURSOR_ID_RESIZE_EAST;
1062 	} else {
1063 		if (vertical == TOP)
1064 			cursorID = B_CURSOR_ID_RESIZE_NORTH;
1065 		else if (vertical == BOTTOM)
1066 			cursorID = B_CURSOR_ID_RESIZE_SOUTH;
1067 	}
1068 
1069 	return fDesktop->GetCursorManager().GetCursor(cursorID);
1070 }
1071 
1072 
1073 void
1074 DefaultWindowBehaviour::_SetResizeCursor(int8 horizontal, int8 vertical)
1075 {
1076 	fDesktop->SetManagementCursor(_ResizeCursorFor(horizontal, vertical));
1077 }
1078 
1079 
1080 void
1081 DefaultWindowBehaviour::_ResetResizeCursor()
1082 {
1083 	fDesktop->SetManagementCursor(NULL);
1084 }
1085 
1086 
1087 /*static*/ void
1088 DefaultWindowBehaviour::_ComputeResizeDirection(float x, float y,
1089 	int8& _horizontal, int8& _vertical)
1090 {
1091 	_horizontal = NONE;
1092 	_vertical = NONE;
1093 
1094 	// compute the angle
1095 	if (x == 0 && y == 0)
1096 		return;
1097 
1098 	float angle = atan2f(y, x);
1099 
1100 	// rotate by 22.5 degree to align our sectors with 45 degree multiples
1101 	angle += M_PI / 8;
1102 
1103 	// add 180 degree to the negative values, so we get a nice 0 to 360
1104 	// degree range
1105 	if (angle < 0)
1106 		angle += M_PI * 2;
1107 
1108 	switch (int(angle / M_PI_4)) {
1109 		case 0:
1110 			_horizontal = RIGHT;
1111 			break;
1112 		case 1:
1113 			_horizontal = RIGHT;
1114 			_vertical = BOTTOM;
1115 			break;
1116 		case 2:
1117 			_vertical = BOTTOM;
1118 			break;
1119 		case 3:
1120 			_horizontal = LEFT;
1121 			_vertical = BOTTOM;
1122 			break;
1123 		case 4:
1124 			_horizontal = LEFT;
1125 			break;
1126 		case 5:
1127 			_horizontal = LEFT;
1128 			_vertical = TOP;
1129 			break;
1130 		case 6:
1131 			_vertical = TOP;
1132 			break;
1133 		case 7:
1134 		default:
1135 			_horizontal = RIGHT;
1136 			_vertical = TOP;
1137 			break;
1138 	}
1139 }
1140 
1141 
1142 void
1143 DefaultWindowBehaviour::_NextState(State* state)
1144 {
1145 	// exit the old state
1146 	if (fState != NULL)
1147 		fState->ExitState(state);
1148 
1149 	// set and enter the new state
1150 	State* oldState = fState;
1151 	fState = state;
1152 
1153 	if (fState != NULL) {
1154 		fState->EnterState(oldState);
1155 		fDesktop->SetMouseEventWindow(fWindow);
1156 	} else if (oldState != NULL) {
1157 		// no state anymore -- reset the mouse event window, if it's still us
1158 		if (fDesktop->MouseEventWindow() == fWindow)
1159 			fDesktop->SetMouseEventWindow(NULL);
1160 	}
1161 
1162 	delete oldState;
1163 }
1164