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
38static const bigtime_t kWindowActivationTimeout = 500000LL;
39
40
41// #pragma mark - State
42
43
44struct 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
82protected:
83	DefaultWindowBehaviour&	fBehavior;
84	Window*					fWindow;
85	Desktop*				fDesktop;
86};
87
88
89// #pragma mark - MouseTrackingState
90
91
92struct 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
195protected:
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
209struct 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
252struct 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
283struct 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
348struct 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
458private:
459	int8	fHorizontal;
460	int8	fVertical;
461};
462
463
464// #pragma mark - DecoratorButtonState
465
466
467struct 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
536private:
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
574protected:
575	int32				fTab;
576	Decorator::Region	fButton;
577};
578
579
580// #pragma mark - ManageWindowState
581
582
583struct 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
635private:
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
672private:
673	BPoint	fLastMousePosition;
674	int8	fHorizontal;
675	int8	fVertical;
676};
677
678
679// #pragma mark - DefaultWindowBehaviour
680
681
682DefaultWindowBehaviour::DefaultWindowBehaviour(Window* window)
683	:
684	fWindow(window),
685	fDesktop(window->Desktop()),
686	fState(NULL),
687	fLastModifiers(0)
688{
689}
690
691
692DefaultWindowBehaviour::~DefaultWindowBehaviour()
693{
694	delete fState;
695}
696
697
698bool
699DefaultWindowBehaviour::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
896void
897DefaultWindowBehaviour::MouseUp(BMessage* message, BPoint where)
898{
899	if (fState != NULL)
900		fState->MouseUp(message, where);
901}
902
903
904void
905DefaultWindowBehaviour::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
927void
928DefaultWindowBehaviour::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
944bool
945DefaultWindowBehaviour::AlterDeltaForSnap(Window* window, BPoint& delta,
946	bigtime_t now)
947{
948	return fMagneticBorder.AlterDeltaForSnap(window, delta, now);
949}
950
951
952bool
953DefaultWindowBehaviour::_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
961Decorator::Region
962DefaultWindowBehaviour::_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
976void
977DefaultWindowBehaviour::_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
1042ServerCursor*
1043DefaultWindowBehaviour::_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
1073void
1074DefaultWindowBehaviour::_SetResizeCursor(int8 horizontal, int8 vertical)
1075{
1076	fDesktop->SetManagementCursor(_ResizeCursorFor(horizontal, vertical));
1077}
1078
1079
1080void
1081DefaultWindowBehaviour::_ResetResizeCursor()
1082{
1083	fDesktop->SetManagementCursor(NULL);
1084}
1085
1086
1087/*static*/ void
1088DefaultWindowBehaviour::_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
1142void
1143DefaultWindowBehaviour::_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