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 { 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 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 { 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 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 { 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 256 struct DefaultWindowBehaviour::ResizeState : MouseTrackingState { 257 BPoint fDelta; 258 259 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 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 306 struct 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 373 struct 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 499 private: 500 int8 fHorizontal; 501 int8 fVertical; 502 }; 503 504 505 // #pragma mark - DecoratorButtonState 506 507 508 struct 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 577 private: 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 615 protected: 616 int32 fTab; 617 Decorator::Region fButton; 618 }; 619 620 621 // #pragma mark - ManageWindowState 622 623 624 struct 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 676 private: 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 713 private: 714 BPoint fLastMousePosition; 715 int8 fHorizontal; 716 int8 fVertical; 717 }; 718 719 720 // #pragma mark - DefaultWindowBehaviour 721 722 723 DefaultWindowBehaviour::DefaultWindowBehaviour(Window* window) 724 : 725 fWindow(window), 726 fDesktop(window->Desktop()), 727 fLastModifiers(0) 728 { 729 } 730 731 732 DefaultWindowBehaviour::~DefaultWindowBehaviour() 733 { 734 } 735 736 737 bool 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 957 DefaultWindowBehaviour::MouseUp(BMessage* message, BPoint where) 958 { 959 if (fState.IsSet()) 960 fState->MouseUp(message, where); 961 } 962 963 964 void 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 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 1005 DefaultWindowBehaviour::AlterDeltaForSnap(Window* window, BPoint& delta, 1006 bigtime_t now) 1007 { 1008 return fMagneticBorder.AlterDeltaForSnap(window, delta, now); 1009 } 1010 1011 1012 bool 1013 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 1022 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 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* 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 1134 DefaultWindowBehaviour::_SetResizeCursor(int8 horizontal, int8 vertical) 1135 { 1136 fDesktop->SetManagementCursor(_ResizeCursorFor(horizontal, vertical)); 1137 } 1138 1139 1140 void 1141 DefaultWindowBehaviour::_ResetResizeCursor() 1142 { 1143 fDesktop->SetManagementCursor(NULL); 1144 } 1145 1146 1147 /*static*/ void 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 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