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