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) == 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
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) == 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
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 > 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
350struct 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
460private:
461	int8	fHorizontal;
462	int8	fVertical;
463};
464
465
466// #pragma mark - DecoratorButtonState
467
468
469struct 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
538private:
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
576protected:
577	int32				fTab;
578	Decorator::Region	fButton;
579};
580
581
582// #pragma mark - ManageWindowState
583
584
585struct 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
637private:
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
674private:
675	BPoint	fLastMousePosition;
676	int8	fHorizontal;
677	int8	fVertical;
678};
679
680
681// #pragma mark - DefaultWindowBehaviour
682
683
684DefaultWindowBehaviour::DefaultWindowBehaviour(Window* window)
685	:
686	fWindow(window),
687	fDesktop(window->Desktop()),
688	fState(NULL),
689	fLastModifiers(0)
690{
691}
692
693
694DefaultWindowBehaviour::~DefaultWindowBehaviour()
695{
696	delete fState;
697}
698
699
700bool
701DefaultWindowBehaviour::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
919void
920DefaultWindowBehaviour::MouseUp(BMessage* message, BPoint where)
921{
922	if (fState != NULL)
923		fState->MouseUp(message, where);
924}
925
926
927void
928DefaultWindowBehaviour::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
950void
951DefaultWindowBehaviour::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
967bool
968DefaultWindowBehaviour::AlterDeltaForSnap(Window* window, BPoint& delta,
969	bigtime_t now)
970{
971	return fMagneticBorder.AlterDeltaForSnap(window, delta, now);
972}
973
974
975bool
976DefaultWindowBehaviour::_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
984Decorator::Region
985DefaultWindowBehaviour::_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
999void
1000DefaultWindowBehaviour::_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
1065ServerCursor*
1066DefaultWindowBehaviour::_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
1096void
1097DefaultWindowBehaviour::_SetResizeCursor(int8 horizontal, int8 vertical)
1098{
1099	fDesktop->SetManagementCursor(_ResizeCursorFor(horizontal, vertical));
1100}
1101
1102
1103void
1104DefaultWindowBehaviour::_ResetResizeCursor()
1105{
1106	fDesktop->SetManagementCursor(NULL);
1107}
1108
1109
1110/*static*/ void
1111DefaultWindowBehaviour::_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
1165void
1166DefaultWindowBehaviour::_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