xref: /haiku/src/servers/app/decorator/DefaultWindowBehaviour.cpp (revision 6894c8a7f7c7cf6bd2a787984080fd62d1545510)
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
42 static const bigtime_t kWindowActivationTimeout = 500000LL;
43 
44 
45 // #pragma mark - State
46 
47 
48 struct DefaultWindowBehaviour::State {
StateDefaultWindowBehaviour::State49 	State(DefaultWindowBehaviour& behavior)
50 		:
51 		fBehavior(behavior),
52 		fWindow(behavior.fWindow),
53 		fDesktop(behavior.fDesktop)
54 	{
55 	}
56 
~StateDefaultWindowBehaviour::State57 	virtual ~State()
58 	{
59 	}
60 
EnterStateDefaultWindowBehaviour::State61 	virtual void EnterState(State* previousState)
62 	{
63 	}
64 
ExitStateDefaultWindowBehaviour::State65 	virtual void ExitState(State* nextState)
66 	{
67 	}
68 
MouseDownDefaultWindowBehaviour::State69 	virtual bool MouseDown(BMessage* message, BPoint where, bool& _unhandled)
70 	{
71 		return true;
72 	}
73 
MouseUpDefaultWindowBehaviour::State74 	virtual void MouseUp(BMessage* message, BPoint where)
75 	{
76 	}
77 
MouseMovedDefaultWindowBehaviour::State78 	virtual void MouseMoved(BMessage* message, BPoint where, bool isFake)
79 	{
80 	}
81 
ModifiersChangedDefaultWindowBehaviour::State82 	virtual void ModifiersChanged(BPoint where, int32 modifiers)
83 	{
84 	}
85 
86 protected:
87 	DefaultWindowBehaviour&	fBehavior;
88 	Window*					fWindow;
89 	Desktop*				fDesktop;
90 };
91 
92 
93 // #pragma mark - MouseTrackingState
94 
95 
96 struct DefaultWindowBehaviour::MouseTrackingState : State {
MouseTrackingStateDefaultWindowBehaviour::MouseTrackingState97 	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 
MouseUpDefaultWindowBehaviour::MouseTrackingState111 	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 
MouseMovedDefaultWindowBehaviour::MouseTrackingState140 	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 
MouseMovedActionDefaultWindowBehaviour::MouseTrackingState189 	virtual void MouseMovedAction(BPoint& delta, bigtime_t now)
190 	{
191 	}
192 
MouseUpWindowActionDefaultWindowBehaviour::MouseTrackingState193 	virtual void MouseUpWindowAction()
194 	{
195 		// default is window activation
196 		fDesktop->ActivateWindow(fWindow);
197 	}
198 
199 protected:
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 
213 struct DefaultWindowBehaviour::DragState : MouseTrackingState {
DragStateDefaultWindowBehaviour::DragState214 	DragState(DefaultWindowBehaviour& behavior, BPoint where,
215 		bool activateOnMouseUp, bool minimizeCheckOnMouseUp)
216 		:
217 		MouseTrackingState(behavior, where, activateOnMouseUp,
218 			minimizeCheckOnMouseUp)
219 	{
220 	}
221 
MouseDownDefaultWindowBehaviour::DragState222 	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 
MouseMovedActionDefaultWindowBehaviour::DragState237 	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 
256 struct DefaultWindowBehaviour::ResizeState : MouseTrackingState {
257 	BPoint fDelta;
258 
ResizeStateDefaultWindowBehaviour::ResizeState259 	ResizeState(DefaultWindowBehaviour& behavior, BPoint where,
260 		bool activateOnMouseUp, bool minimizeCheckOnMouseUp)
261 		:
262 		MouseTrackingState(behavior, where, activateOnMouseUp, minimizeCheckOnMouseUp)
263 	{
264 		fDelta = BPoint(0, 0);
265 	}
266 
EnterStateDefaultWindowBehaviour::ResizeState267 	virtual void EnterState(State* prevState)
268 	{
269 	}
270 
ExitStateDefaultWindowBehaviour::ResizeState271 	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 
MouseMovedActionDefaultWindowBehaviour::ResizeState279 	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 
306 struct DefaultWindowBehaviour::SlideTabState : MouseTrackingState {
SlideTabStateDefaultWindowBehaviour::SlideTabState307 	SlideTabState(DefaultWindowBehaviour& behavior, BPoint where)
308 		:
309 		MouseTrackingState(behavior, where, false, false)
310 	{
311 	}
312 
313 	virtual
~SlideTabStateDefaultWindowBehaviour::SlideTabState314 	~SlideTabState()
315 	{
316 		fDesktop->SetWindowTabLocation(fWindow, fWindow->TabLocation(), false);
317 	}
318 
MouseMovedActionDefaultWindowBehaviour::SlideTabState319 	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 
AdjustMultiTabLocationDefaultWindowBehaviour::SlideTabState331 	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 
373 struct DefaultWindowBehaviour::ResizeBorderState : MouseTrackingState {
374 	BPoint fDelta;
375 
ResizeBorderStateDefaultWindowBehaviour::ResizeBorderState376 	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 
ResizeBorderStateDefaultWindowBehaviour::ResizeBorderState423 	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 
EnterStateDefaultWindowBehaviour::ResizeBorderState434 	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 
ExitStateDefaultWindowBehaviour::ResizeBorderState448 	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 
MouseMovedActionDefaultWindowBehaviour::ResizeBorderState458 	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 
MouseUpWindowActionDefaultWindowBehaviour::ResizeBorderState494 	virtual void MouseUpWindowAction()
495 	{
496 		fDesktop->SendWindowBehind(fWindow);
497 	}
498 
499 private:
500 	int8	fHorizontal;
501 	int8	fVertical;
502 };
503 
504 
505 // #pragma mark - DecoratorButtonState
506 
507 
508 struct DefaultWindowBehaviour::DecoratorButtonState : State {
DecoratorButtonStateDefaultWindowBehaviour::DecoratorButtonState509 	DecoratorButtonState(DefaultWindowBehaviour& behavior,
510 		int32 tab, Decorator::Region button)
511 		:
512 		State(behavior),
513 		fTab(tab),
514 		fButton(button)
515 	{
516 	}
517 
EnterStateDefaultWindowBehaviour::DecoratorButtonState518 	virtual void EnterState(State* previousState)
519 	{
520 		_RedrawDecorator(NULL);
521 	}
522 
MouseUpDefaultWindowBehaviour::DecoratorButtonState523 	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 
MouseMovedDefaultWindowBehaviour::DecoratorButtonState572 	virtual void MouseMoved(BMessage* message, BPoint where, bool isFake)
573 	{
574 		_RedrawDecorator(message);
575 	}
576 
577 private:
_RedrawDecoratorDefaultWindowBehaviour::DecoratorButtonState578 	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 
615 protected:
616 	int32				fTab;
617 	Decorator::Region	fButton;
618 };
619 
620 
621 // #pragma mark - ManageWindowState
622 
623 
624 struct DefaultWindowBehaviour::ManageWindowState : State {
ManageWindowStateDefaultWindowBehaviour::ManageWindowState625 	ManageWindowState(DefaultWindowBehaviour& behavior, BPoint where)
626 		:
627 		State(behavior),
628 		fLastMousePosition(where),
629 		fHorizontal(NONE),
630 		fVertical(NONE)
631 	{
632 	}
633 
EnterStateDefaultWindowBehaviour::ManageWindowState634 	virtual void EnterState(State* previousState)
635 	{
636 		_UpdateBorders(fLastMousePosition);
637 	}
638 
ExitStateDefaultWindowBehaviour::ManageWindowState639 	virtual void ExitState(State* nextState)
640 	{
641 		fBehavior._SetBorderHighlights(fHorizontal, fVertical, false);
642 	}
643 
MouseDownDefaultWindowBehaviour::ManageWindowState644 	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 
MouseMovedDefaultWindowBehaviour::ManageWindowState659 	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 
ModifiersChangedDefaultWindowBehaviour::ManageWindowState670 	virtual void ModifiersChanged(BPoint where, int32 modifiers)
671 	{
672 		if (!fBehavior._IsWindowModifier(modifiers))
673 			fBehavior._NextState(NULL);
674 	}
675 
676 private:
_UpdateBordersDefaultWindowBehaviour::ManageWindowState677 	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 
713 private:
714 	BPoint	fLastMousePosition;
715 	int8	fHorizontal;
716 	int8	fVertical;
717 };
718 
719 
720 // #pragma mark - DefaultWindowBehaviour
721 
722 
DefaultWindowBehaviour(Window * window)723 DefaultWindowBehaviour::DefaultWindowBehaviour(Window* window)
724 	:
725 	fWindow(window),
726 	fDesktop(window->Desktop()),
727 	fLastModifiers(0)
728 {
729 }
730 
731 
~DefaultWindowBehaviour()732 DefaultWindowBehaviour::~DefaultWindowBehaviour()
733 {
734 }
735 
736 
737 bool
MouseDown(BMessage * message,BPoint where,int32 lastHitRegion,int32 & clickCount,int32 & _hitRegion)738 DefaultWindowBehaviour::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, clickCount == 2));
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 
956 void
MouseUp(BMessage * message,BPoint where)957 DefaultWindowBehaviour::MouseUp(BMessage* message, BPoint where)
958 {
959 	if (fState.IsSet())
960 		fState->MouseUp(message, where);
961 }
962 
963 
964 void
MouseMoved(BMessage * message,BPoint where,bool isFake)965 DefaultWindowBehaviour::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 
987 void
ModifiersChanged(int32 modifiers)988 DefaultWindowBehaviour::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 
1004 bool
AlterDeltaForSnap(Window * window,BPoint & delta,bigtime_t now)1005 DefaultWindowBehaviour::AlterDeltaForSnap(Window* window, BPoint& delta,
1006 	bigtime_t now)
1007 {
1008 	return fMagneticBorder.AlterDeltaForSnap(window, delta, now);
1009 }
1010 
1011 
1012 bool
_IsWindowModifier(int32 modifiers) const1013 DefaultWindowBehaviour::_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 
1021 Decorator::Region
_RegionFor(const BMessage * message,int32 & tab) const1022 DefaultWindowBehaviour::_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 
1036 void
_SetBorderHighlights(int8 horizontal,int8 vertical,bool active)1037 DefaultWindowBehaviour::_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 
1102 ServerCursor*
_ResizeCursorFor(int8 horizontal,int8 vertical)1103 DefaultWindowBehaviour::_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 
1133 void
_SetResizeCursor(int8 horizontal,int8 vertical)1134 DefaultWindowBehaviour::_SetResizeCursor(int8 horizontal, int8 vertical)
1135 {
1136 	fDesktop->SetManagementCursor(_ResizeCursorFor(horizontal, vertical));
1137 }
1138 
1139 
1140 void
_ResetResizeCursor()1141 DefaultWindowBehaviour::_ResetResizeCursor()
1142 {
1143 	fDesktop->SetManagementCursor(NULL);
1144 }
1145 
1146 
1147 /*static*/ void
_ComputeResizeDirection(float x,float y,int8 & _horizontal,int8 & _vertical)1148 DefaultWindowBehaviour::_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 
1202 void
_NextState(State * state)1203 DefaultWindowBehaviour::_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