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