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