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 fState(NULL), 689 fLastModifiers(0) 690 { 691 } 692 693 694 DefaultWindowBehaviour::~DefaultWindowBehaviour() 695 { 696 delete fState; 697 } 698 699 700 bool 701 DefaultWindowBehaviour::MouseDown(BMessage* message, BPoint where, 702 int32 lastHitRegion, int32& clickCount, int32& _hitRegion) 703 { 704 fLastModifiers = message->FindInt32("modifiers"); 705 int32 buttons = message->FindInt32("buttons"); 706 707 // if a state is active, let it do the job 708 if (fState != NULL) { 709 bool unhandled = false; 710 bool result = fState->MouseDown(message, where, unhandled); 711 if (!unhandled) 712 return result; 713 } 714 715 // No state active yet, or it wants us to handle the event -- determine the 716 // click region and decide what to do. 717 718 Decorator* decorator = fWindow->Decorator(); 719 720 Decorator::Region hitRegion = Decorator::REGION_NONE; 721 int32 tab = -1; 722 Action action = ACTION_NONE; 723 724 bool inBorderRegion = false; 725 if (decorator != NULL) 726 inBorderRegion = decorator->GetFootprint().Contains(where); 727 728 bool windowModifier = _IsWindowModifier(fLastModifiers); 729 730 if (windowModifier || inBorderRegion) { 731 // click on the window decorator or we have the window modifier keys 732 // held 733 734 // get the functional hit region 735 if (windowModifier) { 736 // click with window modifier keys -- let the whole window behave 737 // like the border 738 hitRegion = Decorator::REGION_LEFT_BORDER; 739 } else { 740 // click on the decorator -- get the exact region 741 hitRegion = _RegionFor(message, tab); 742 } 743 744 // translate the region into an action 745 uint32 flags = fWindow->Flags(); 746 747 if ((buttons & B_PRIMARY_MOUSE_BUTTON) != 0) { 748 // left mouse button 749 switch (hitRegion) { 750 case Decorator::REGION_TAB: { 751 // tab sliding in any case if either shift key is held down 752 // except sliding up-down by moving mouse left-right would 753 // look strange 754 if ((fLastModifiers & B_SHIFT_KEY) != 0 755 && fWindow->Look() != kLeftTitledWindowLook) { 756 action = ACTION_SLIDE_TAB; 757 break; 758 } 759 action = ACTION_DRAG; 760 break; 761 } 762 763 case Decorator::REGION_LEFT_BORDER: 764 case Decorator::REGION_RIGHT_BORDER: 765 case Decorator::REGION_TOP_BORDER: 766 case Decorator::REGION_BOTTOM_BORDER: 767 action = ACTION_DRAG; 768 break; 769 770 case Decorator::REGION_CLOSE_BUTTON: 771 action = (flags & B_NOT_CLOSABLE) == 0 772 ? ACTION_CLOSE : ACTION_DRAG; 773 break; 774 775 case Decorator::REGION_ZOOM_BUTTON: 776 action = (flags & B_NOT_ZOOMABLE) == 0 777 ? ACTION_ZOOM : ACTION_DRAG; 778 break; 779 780 case Decorator::REGION_MINIMIZE_BUTTON: 781 action = (flags & B_NOT_MINIMIZABLE) == 0 782 ? ACTION_MINIMIZE : ACTION_DRAG; 783 break; 784 785 case Decorator::REGION_LEFT_TOP_CORNER: 786 case Decorator::REGION_LEFT_BOTTOM_CORNER: 787 case Decorator::REGION_RIGHT_TOP_CORNER: 788 // TODO: Handle correctly! 789 action = ACTION_DRAG; 790 break; 791 792 case Decorator::REGION_RIGHT_BOTTOM_CORNER: 793 action = (flags & B_NOT_RESIZABLE) == 0 794 ? ACTION_RESIZE : ACTION_DRAG; 795 break; 796 797 default: 798 break; 799 } 800 } else if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0) { 801 // right mouse button 802 switch (hitRegion) { 803 case Decorator::REGION_TAB: 804 case Decorator::REGION_LEFT_BORDER: 805 case Decorator::REGION_RIGHT_BORDER: 806 case Decorator::REGION_TOP_BORDER: 807 case Decorator::REGION_BOTTOM_BORDER: 808 case Decorator::REGION_CLOSE_BUTTON: 809 case Decorator::REGION_ZOOM_BUTTON: 810 case Decorator::REGION_MINIMIZE_BUTTON: 811 case Decorator::REGION_LEFT_TOP_CORNER: 812 case Decorator::REGION_LEFT_BOTTOM_CORNER: 813 case Decorator::REGION_RIGHT_TOP_CORNER: 814 case Decorator::REGION_RIGHT_BOTTOM_CORNER: 815 action = ACTION_RESIZE_BORDER; 816 break; 817 818 default: 819 break; 820 } 821 } 822 } 823 824 _hitRegion = (int32)hitRegion; 825 826 if (action == ACTION_NONE) { 827 // No action -- if this is a click inside the window's contents, 828 // let it be forwarded to the window. 829 return inBorderRegion; 830 } 831 832 // reset the click count, if the hit region differs from the previous one 833 if (hitRegion != lastHitRegion) 834 clickCount = 1; 835 836 DesktopSettings desktopSettings(fDesktop); 837 if (!desktopSettings.AcceptFirstClick()) { 838 // Ignore clicks on decorator buttons if the 839 // non-floating window doesn't have focus 840 if (!fWindow->IsFocus() && !fWindow->IsFloating() 841 && action != ACTION_RESIZE_BORDER 842 && action != ACTION_RESIZE && action != ACTION_SLIDE_TAB) 843 action = ACTION_DRAG; 844 } 845 846 bool activateOnMouseUp = false; 847 if (action != ACTION_RESIZE_BORDER) { 848 // activate window if in click to activate mode, else only focus it 849 if (desktopSettings.MouseMode() == B_NORMAL_MOUSE) { 850 fDesktop->ActivateWindow(fWindow); 851 } else { 852 fDesktop->SetFocusWindow(fWindow); 853 activateOnMouseUp = true; 854 } 855 } 856 857 // switch to the new state 858 switch (action) { 859 case ACTION_CLOSE: 860 case ACTION_ZOOM: 861 case ACTION_MINIMIZE: 862 _NextState( 863 new (std::nothrow) DecoratorButtonState(*this, tab, hitRegion)); 864 STRACE_CLICK(("===> ACTION_CLOSE/ZOOM/MINIMIZE\n")); 865 break; 866 867 case ACTION_DRAG: 868 _NextState(new (std::nothrow) DragState(*this, where, 869 activateOnMouseUp, clickCount == 2)); 870 STRACE_CLICK(("===> ACTION_DRAG\n")); 871 break; 872 873 case ACTION_RESIZE: 874 _NextState(new (std::nothrow) ResizeState(*this, where, 875 activateOnMouseUp)); 876 STRACE_CLICK(("===> ACTION_RESIZE\n")); 877 break; 878 879 case ACTION_SLIDE_TAB: 880 _NextState(new (std::nothrow) SlideTabState(*this, where)); 881 STRACE_CLICK(("===> ACTION_SLIDE_TAB\n")); 882 break; 883 884 case ACTION_RESIZE_BORDER: 885 _NextState(new (std::nothrow) ResizeBorderState(*this, where, 886 hitRegion)); 887 STRACE_CLICK(("===> ACTION_RESIZE_BORDER\n")); 888 break; 889 890 default: 891 break; 892 } 893 894 return true; 895 } 896 897 898 void 899 DefaultWindowBehaviour::MouseUp(BMessage* message, BPoint where) 900 { 901 if (fState != NULL) 902 fState->MouseUp(message, where); 903 } 904 905 906 void 907 DefaultWindowBehaviour::MouseMoved(BMessage* message, BPoint where, bool isFake) 908 { 909 if (fState != NULL) { 910 fState->MouseMoved(message, where, isFake); 911 } else { 912 // If the window modifiers are hold, enter the window management state. 913 if (_IsWindowModifier(message->FindInt32("modifiers"))) 914 _NextState(new(std::nothrow) ManageWindowState(*this, where)); 915 } 916 917 // change focus in FFM mode 918 DesktopSettings desktopSettings(fDesktop); 919 if (desktopSettings.FocusFollowsMouse() 920 && !fWindow->IsFocus() && (fWindow->Flags() & B_AVOID_FOCUS) == 0) { 921 // If the mouse move is a fake one, we set the focus to NULL, which 922 // will cause the window that had focus last to retrieve it again - this 923 // makes FFM much nicer to use with the keyboard. 924 fDesktop->SetFocusWindow(isFake ? NULL : fWindow); 925 } 926 } 927 928 929 void 930 DefaultWindowBehaviour::ModifiersChanged(int32 modifiers) 931 { 932 BPoint where; 933 int32 buttons; 934 fDesktop->GetLastMouseState(&where, &buttons); 935 936 if (fState != NULL) { 937 fState->ModifiersChanged(where, modifiers); 938 } else { 939 // If the window modifiers are hold, enter the window management state. 940 if (_IsWindowModifier(modifiers)) 941 _NextState(new(std::nothrow) ManageWindowState(*this, where)); 942 } 943 } 944 945 946 bool 947 DefaultWindowBehaviour::AlterDeltaForSnap(Window* window, BPoint& delta, 948 bigtime_t now) 949 { 950 return fMagneticBorder.AlterDeltaForSnap(window, delta, now); 951 } 952 953 954 bool 955 DefaultWindowBehaviour::_IsWindowModifier(int32 modifiers) const 956 { 957 return (fWindow->Flags() & B_NO_SERVER_SIDE_WINDOW_MODIFIERS) == 0 958 && (modifiers & (B_COMMAND_KEY | B_CONTROL_KEY | B_OPTION_KEY 959 | B_SHIFT_KEY)) == (B_COMMAND_KEY | B_CONTROL_KEY); 960 } 961 962 963 Decorator::Region 964 DefaultWindowBehaviour::_RegionFor(const BMessage* message, int32& tab) const 965 { 966 Decorator* decorator = fWindow->Decorator(); 967 if (decorator == NULL) 968 return Decorator::REGION_NONE; 969 970 BPoint where; 971 if (message->FindPoint("where", &where) != B_OK) 972 return Decorator::REGION_NONE; 973 974 return decorator->RegionAt(where, tab); 975 } 976 977 978 void 979 DefaultWindowBehaviour::_SetBorderHighlights(int8 horizontal, int8 vertical, 980 bool active) 981 { 982 if (Decorator* decorator = fWindow->Decorator()) { 983 uint8 highlight = active 984 ? Decorator::HIGHLIGHT_RESIZE_BORDER 985 : Decorator::HIGHLIGHT_NONE; 986 987 // set the highlights for the borders 988 BRegion dirtyRegion; 989 switch (horizontal) { 990 case LEFT: 991 decorator->SetRegionHighlight(Decorator::REGION_LEFT_BORDER, 992 highlight, &dirtyRegion); 993 break; 994 case RIGHT: 995 decorator->SetRegionHighlight( 996 Decorator::REGION_RIGHT_BORDER, highlight, 997 &dirtyRegion); 998 break; 999 } 1000 1001 switch (vertical) { 1002 case TOP: 1003 decorator->SetRegionHighlight(Decorator::REGION_TOP_BORDER, 1004 highlight, &dirtyRegion); 1005 break; 1006 case BOTTOM: 1007 decorator->SetRegionHighlight( 1008 Decorator::REGION_BOTTOM_BORDER, highlight, 1009 &dirtyRegion); 1010 break; 1011 } 1012 1013 // set the highlights for the corners 1014 if (horizontal != NONE && vertical != NONE) { 1015 if (horizontal == LEFT) { 1016 if (vertical == TOP) { 1017 decorator->SetRegionHighlight( 1018 Decorator::REGION_LEFT_TOP_CORNER, highlight, 1019 &dirtyRegion); 1020 } else { 1021 decorator->SetRegionHighlight( 1022 Decorator::REGION_LEFT_BOTTOM_CORNER, highlight, 1023 &dirtyRegion); 1024 } 1025 } else { 1026 if (vertical == TOP) { 1027 decorator->SetRegionHighlight( 1028 Decorator::REGION_RIGHT_TOP_CORNER, highlight, 1029 &dirtyRegion); 1030 } else { 1031 decorator->SetRegionHighlight( 1032 Decorator::REGION_RIGHT_BOTTOM_CORNER, highlight, 1033 &dirtyRegion); 1034 } 1035 } 1036 } 1037 1038 // invalidate the affected regions 1039 fWindow->ProcessDirtyRegion(dirtyRegion); 1040 } 1041 } 1042 1043 1044 ServerCursor* 1045 DefaultWindowBehaviour::_ResizeCursorFor(int8 horizontal, int8 vertical) 1046 { 1047 // get the cursor ID corresponding to the border/corner 1048 BCursorID cursorID = B_CURSOR_ID_SYSTEM_DEFAULT; 1049 1050 if (horizontal == LEFT) { 1051 if (vertical == TOP) 1052 cursorID = B_CURSOR_ID_RESIZE_NORTH_WEST; 1053 else if (vertical == BOTTOM) 1054 cursorID = B_CURSOR_ID_RESIZE_SOUTH_WEST; 1055 else 1056 cursorID = B_CURSOR_ID_RESIZE_WEST; 1057 } else if (horizontal == RIGHT) { 1058 if (vertical == TOP) 1059 cursorID = B_CURSOR_ID_RESIZE_NORTH_EAST; 1060 else if (vertical == BOTTOM) 1061 cursorID = B_CURSOR_ID_RESIZE_SOUTH_EAST; 1062 else 1063 cursorID = B_CURSOR_ID_RESIZE_EAST; 1064 } else { 1065 if (vertical == TOP) 1066 cursorID = B_CURSOR_ID_RESIZE_NORTH; 1067 else if (vertical == BOTTOM) 1068 cursorID = B_CURSOR_ID_RESIZE_SOUTH; 1069 } 1070 1071 return fDesktop->GetCursorManager().GetCursor(cursorID); 1072 } 1073 1074 1075 void 1076 DefaultWindowBehaviour::_SetResizeCursor(int8 horizontal, int8 vertical) 1077 { 1078 fDesktop->SetManagementCursor(_ResizeCursorFor(horizontal, vertical)); 1079 } 1080 1081 1082 void 1083 DefaultWindowBehaviour::_ResetResizeCursor() 1084 { 1085 fDesktop->SetManagementCursor(NULL); 1086 } 1087 1088 1089 /*static*/ void 1090 DefaultWindowBehaviour::_ComputeResizeDirection(float x, float y, 1091 int8& _horizontal, int8& _vertical) 1092 { 1093 _horizontal = NONE; 1094 _vertical = NONE; 1095 1096 // compute the angle 1097 if (x == 0 && y == 0) 1098 return; 1099 1100 float angle = atan2f(y, x); 1101 1102 // rotate by 22.5 degree to align our sectors with 45 degree multiples 1103 angle += M_PI / 8; 1104 1105 // add 180 degree to the negative values, so we get a nice 0 to 360 1106 // degree range 1107 if (angle < 0) 1108 angle += M_PI * 2; 1109 1110 switch (int(angle / M_PI_4)) { 1111 case 0: 1112 _horizontal = RIGHT; 1113 break; 1114 case 1: 1115 _horizontal = RIGHT; 1116 _vertical = BOTTOM; 1117 break; 1118 case 2: 1119 _vertical = BOTTOM; 1120 break; 1121 case 3: 1122 _horizontal = LEFT; 1123 _vertical = BOTTOM; 1124 break; 1125 case 4: 1126 _horizontal = LEFT; 1127 break; 1128 case 5: 1129 _horizontal = LEFT; 1130 _vertical = TOP; 1131 break; 1132 case 6: 1133 _vertical = TOP; 1134 break; 1135 case 7: 1136 default: 1137 _horizontal = RIGHT; 1138 _vertical = TOP; 1139 break; 1140 } 1141 } 1142 1143 1144 void 1145 DefaultWindowBehaviour::_NextState(State* state) 1146 { 1147 // exit the old state 1148 if (fState != NULL) 1149 fState->ExitState(state); 1150 1151 // set and enter the new state 1152 State* oldState = fState; 1153 fState = state; 1154 1155 if (fState != NULL) { 1156 fState->EnterState(oldState); 1157 fDesktop->SetMouseEventWindow(fWindow); 1158 } else if (oldState != NULL) { 1159 // no state anymore -- reset the mouse event window, if it's still us 1160 if (fDesktop->MouseEventWindow() == fWindow) 1161 fDesktop->SetMouseEventWindow(NULL); 1162 } 1163 1164 delete oldState; 1165 } 1166