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