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)) { 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)) { 263 if (fWindow->Flags() & B_NOT_V_RESIZABLE) 264 delta.y = 0; 265 if (fWindow->Flags() & B_NOT_H_RESIZABLE) 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 > 332 neighbourTab->tabOffset + neighbourTab->tabRect.Width() / 2) 333 return; 334 } else { 335 if (location + movingTab->tabRect.Width() < 336 neighbourTab->tabOffset + neighbourTab->tabRect.Width() / 2) 337 return; 338 } 339 340 fWindow->MoveToStackPosition(neighbourIndex, isShifting); 341 } 342 }; 343 344 345 // #pragma mark - ResizeBorderState 346 347 348 struct DefaultWindowBehaviour::ResizeBorderState : MouseTrackingState { 349 ResizeBorderState(DefaultWindowBehaviour& behavior, BPoint where, 350 Decorator::Region region) 351 : 352 MouseTrackingState(behavior, where, true, false, 353 B_SECONDARY_MOUSE_BUTTON), 354 fHorizontal(NONE), 355 fVertical(NONE) 356 { 357 switch (region) { 358 case Decorator::REGION_TAB: 359 // TODO: Handle like the border it is attached to (top/left)? 360 break; 361 case Decorator::REGION_LEFT_BORDER: 362 fHorizontal = LEFT; 363 break; 364 case Decorator::REGION_RIGHT_BORDER: 365 fHorizontal = RIGHT; 366 break; 367 case Decorator::REGION_TOP_BORDER: 368 fVertical = TOP; 369 break; 370 case Decorator::REGION_BOTTOM_BORDER: 371 fVertical = BOTTOM; 372 break; 373 case Decorator::REGION_LEFT_TOP_CORNER: 374 fHorizontal = LEFT; 375 fVertical = TOP; 376 break; 377 case Decorator::REGION_LEFT_BOTTOM_CORNER: 378 fHorizontal = LEFT; 379 fVertical = BOTTOM; 380 break; 381 case Decorator::REGION_RIGHT_TOP_CORNER: 382 fHorizontal = RIGHT; 383 fVertical = TOP; 384 break; 385 case Decorator::REGION_RIGHT_BOTTOM_CORNER: 386 fHorizontal = RIGHT; 387 fVertical = BOTTOM; 388 break; 389 default: 390 break; 391 } 392 } 393 394 ResizeBorderState(DefaultWindowBehaviour& behavior, BPoint where, 395 int8 horizontal, int8 vertical) 396 : 397 MouseTrackingState(behavior, where, true, false, 398 B_SECONDARY_MOUSE_BUTTON), 399 fHorizontal(horizontal), 400 fVertical(vertical) 401 { 402 } 403 404 virtual void EnterState(State* previousState) 405 { 406 if ((fWindow->Flags() & B_NOT_RESIZABLE) != 0) 407 fHorizontal = fVertical = NONE; 408 else { 409 if ((fWindow->Flags() & B_NOT_H_RESIZABLE) != 0) 410 fHorizontal = NONE; 411 412 if ((fWindow->Flags() & B_NOT_V_RESIZABLE) != 0) 413 fVertical = NONE; 414 } 415 fBehavior._SetResizeCursor(fHorizontal, fVertical); 416 } 417 418 virtual void ExitState(State* nextState) 419 { 420 fBehavior._ResetResizeCursor(); 421 } 422 423 virtual void MouseMovedAction(BPoint& delta, bigtime_t now) 424 { 425 if (fHorizontal == NONE) 426 delta.x = 0; 427 if (fVertical == NONE) 428 delta.y = 0; 429 430 if (delta.x == 0 && delta.y == 0) 431 return; 432 433 // Resize first -- due to the window size limits this is not unlikely 434 // to turn out differently from what we request. 435 BPoint oldRightBottom = fWindow->Frame().RightBottom(); 436 437 fDesktop->ResizeWindowBy(fWindow, delta.x * fHorizontal, 438 delta.y * fVertical); 439 440 // constrain delta to true change in size 441 delta = fWindow->Frame().RightBottom() - oldRightBottom; 442 delta.x *= fHorizontal; 443 delta.y *= fVertical; 444 445 // see, if we have to move, too 446 float moveX = fHorizontal == LEFT ? delta.x : 0; 447 float moveY = fVertical == TOP ? delta.y : 0; 448 449 if (moveX != 0 || moveY != 0) 450 fDesktop->MoveWindowBy(fWindow, moveX, moveY); 451 } 452 453 virtual void MouseUpWindowAction() 454 { 455 fDesktop->SendWindowBehind(fWindow); 456 } 457 458 private: 459 int8 fHorizontal; 460 int8 fVertical; 461 }; 462 463 464 // #pragma mark - DecoratorButtonState 465 466 467 struct DefaultWindowBehaviour::DecoratorButtonState : State { 468 DecoratorButtonState(DefaultWindowBehaviour& behavior, 469 int32 tab, Decorator::Region button) 470 : 471 State(behavior), 472 fTab(tab), 473 fButton(button) 474 { 475 } 476 477 virtual void EnterState(State* previousState) 478 { 479 _RedrawDecorator(NULL); 480 } 481 482 virtual void MouseUp(BMessage* message, BPoint where) 483 { 484 // ignore, if it's not the primary mouse button 485 int32 buttons = message->FindInt32("buttons"); 486 if ((buttons & B_PRIMARY_MOUSE_BUTTON) != 0) 487 return; 488 489 // redraw the decorator 490 if (Decorator* decorator = fWindow->Decorator()) { 491 BRegion* visibleBorder = fWindow->RegionPool()->GetRegion(); 492 fWindow->GetBorderRegion(visibleBorder); 493 visibleBorder->IntersectWith(&fWindow->VisibleRegion()); 494 495 DrawingEngine* engine = decorator->GetDrawingEngine(); 496 engine->LockParallelAccess(); 497 engine->ConstrainClippingRegion(visibleBorder); 498 499 int32 tab; 500 switch (fButton) { 501 case Decorator::REGION_CLOSE_BUTTON: 502 decorator->SetClose(fTab, false); 503 if (fBehavior._RegionFor(message, tab) == fButton) 504 fWindow->ServerWindow()->NotifyQuitRequested(); 505 break; 506 507 case Decorator::REGION_ZOOM_BUTTON: 508 decorator->SetZoom(fTab, false); 509 if (fBehavior._RegionFor(message, tab) == fButton) 510 fWindow->ServerWindow()->NotifyZoom(); 511 break; 512 513 case Decorator::REGION_MINIMIZE_BUTTON: 514 decorator->SetMinimize(fTab, false); 515 if (fBehavior._RegionFor(message, tab) == fButton) 516 fWindow->ServerWindow()->NotifyMinimize(true); 517 break; 518 519 default: 520 break; 521 } 522 523 engine->UnlockParallelAccess(); 524 525 fWindow->RegionPool()->Recycle(visibleBorder); 526 } 527 528 fBehavior._NextState(NULL); 529 } 530 531 virtual void MouseMoved(BMessage* message, BPoint where, bool isFake) 532 { 533 _RedrawDecorator(message); 534 } 535 536 private: 537 void _RedrawDecorator(const BMessage* message) 538 { 539 if (Decorator* decorator = fWindow->Decorator()) { 540 BRegion* visibleBorder = fWindow->RegionPool()->GetRegion(); 541 fWindow->GetBorderRegion(visibleBorder); 542 visibleBorder->IntersectWith(&fWindow->VisibleRegion()); 543 544 DrawingEngine* engine = decorator->GetDrawingEngine(); 545 engine->LockParallelAccess(); 546 engine->ConstrainClippingRegion(visibleBorder); 547 548 int32 tab; 549 Decorator::Region hitRegion = message != NULL 550 ? fBehavior._RegionFor(message, tab) : fButton; 551 552 switch (fButton) { 553 case Decorator::REGION_CLOSE_BUTTON: 554 decorator->SetClose(fTab, hitRegion == fButton); 555 break; 556 557 case Decorator::REGION_ZOOM_BUTTON: 558 decorator->SetZoom(fTab, hitRegion == fButton); 559 break; 560 561 case Decorator::REGION_MINIMIZE_BUTTON: 562 decorator->SetMinimize(fTab, hitRegion == fButton); 563 break; 564 565 default: 566 break; 567 } 568 569 engine->UnlockParallelAccess(); 570 fWindow->RegionPool()->Recycle(visibleBorder); 571 } 572 } 573 574 protected: 575 int32 fTab; 576 Decorator::Region fButton; 577 }; 578 579 580 // #pragma mark - ManageWindowState 581 582 583 struct DefaultWindowBehaviour::ManageWindowState : State { 584 ManageWindowState(DefaultWindowBehaviour& behavior, BPoint where) 585 : 586 State(behavior), 587 fLastMousePosition(where), 588 fHorizontal(NONE), 589 fVertical(NONE) 590 { 591 } 592 593 virtual void EnterState(State* previousState) 594 { 595 _UpdateBorders(fLastMousePosition); 596 } 597 598 virtual void ExitState(State* nextState) 599 { 600 fBehavior._SetBorderHighlights(fHorizontal, fVertical, false); 601 } 602 603 virtual bool MouseDown(BMessage* message, BPoint where, bool& _unhandled) 604 { 605 // We're only interested, if the secondary mouse button was pressed. 606 // Othewise let the our caller handle the event. 607 int32 buttons = message->FindInt32("buttons"); 608 if ((buttons & B_SECONDARY_MOUSE_BUTTON) == 0) { 609 _unhandled = true; 610 return true; 611 } 612 613 fBehavior._NextState(new (std::nothrow) ResizeBorderState(fBehavior, 614 where, fHorizontal, fVertical)); 615 return true; 616 } 617 618 virtual void MouseMoved(BMessage* message, BPoint where, bool isFake) 619 { 620 // If the mouse is still over our window, update the borders. Otherwise 621 // leave the state. 622 if (fDesktop->WindowAt(where) == fWindow) { 623 fLastMousePosition = where; 624 _UpdateBorders(fLastMousePosition); 625 } else 626 fBehavior._NextState(NULL); 627 } 628 629 virtual void ModifiersChanged(BPoint where, int32 modifiers) 630 { 631 if (!fBehavior._IsWindowModifier(modifiers)) 632 fBehavior._NextState(NULL); 633 } 634 635 private: 636 void _UpdateBorders(BPoint where) 637 { 638 if ((fWindow->Flags() & B_NOT_RESIZABLE) != 0) 639 return; 640 641 // Compute the window center relative location of where. We divide by 642 // the width respective the height, so we compensate for the window's 643 // aspect ratio. 644 BRect frame(fWindow->Frame()); 645 if (frame.Width() + 1 == 0 || frame.Height() + 1 == 0) 646 return; 647 648 float x = (where.x - (frame.left + frame.right) / 2) 649 / (frame.Width() + 1); 650 float y = (where.y - (frame.top + frame.bottom) / 2) 651 / (frame.Height() + 1); 652 653 // compute the resize direction 654 int8 horizontal; 655 int8 vertical; 656 _ComputeResizeDirection(x, y, horizontal, vertical); 657 658 if ((fWindow->Flags() & B_NOT_H_RESIZABLE) != 0) 659 horizontal = NONE; 660 if ((fWindow->Flags() & B_NOT_V_RESIZABLE) != 0) 661 vertical = NONE; 662 663 // update the highlight, if necessary 664 if (horizontal != fHorizontal || vertical != fVertical) { 665 fBehavior._SetBorderHighlights(fHorizontal, fVertical, false); 666 fHorizontal = horizontal; 667 fVertical = vertical; 668 fBehavior._SetBorderHighlights(fHorizontal, fVertical, true); 669 } 670 } 671 672 private: 673 BPoint fLastMousePosition; 674 int8 fHorizontal; 675 int8 fVertical; 676 }; 677 678 679 // #pragma mark - DefaultWindowBehaviour 680 681 682 DefaultWindowBehaviour::DefaultWindowBehaviour(Window* window) 683 : 684 fWindow(window), 685 fDesktop(window->Desktop()), 686 fState(NULL), 687 fLastModifiers(0) 688 { 689 } 690 691 692 DefaultWindowBehaviour::~DefaultWindowBehaviour() 693 { 694 delete fState; 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 // if a state is active, let it do the job 706 if (fState != NULL) { 707 bool unhandled = false; 708 bool result = fState->MouseDown(message, where, unhandled); 709 if (!unhandled) 710 return result; 711 } 712 713 // No state active yet, or it wants us to handle the event -- determine the 714 // click region and decide what to do. 715 716 Decorator* decorator = fWindow->Decorator(); 717 718 Decorator::Region hitRegion = Decorator::REGION_NONE; 719 int32 tab = -1; 720 Action action = ACTION_NONE; 721 722 bool inBorderRegion = false; 723 if (decorator != NULL) 724 inBorderRegion = decorator->GetFootprint().Contains(where); 725 726 bool windowModifier = _IsWindowModifier(fLastModifiers); 727 728 if (windowModifier || inBorderRegion) { 729 // click on the window decorator or we have the window modifier keys 730 // held 731 732 // get the functional hit region 733 if (windowModifier) { 734 // click with window modifier keys -- let the whole window behave 735 // like the border 736 hitRegion = Decorator::REGION_LEFT_BORDER; 737 } else { 738 // click on the decorator -- get the exact region 739 hitRegion = _RegionFor(message, tab); 740 } 741 742 // translate the region into an action 743 uint32 flags = fWindow->Flags(); 744 745 if ((buttons & B_PRIMARY_MOUSE_BUTTON) != 0) { 746 // left mouse button 747 switch (hitRegion) { 748 case Decorator::REGION_TAB: { 749 // tab sliding in any case if either shift key is held down 750 // except sliding up-down by moving mouse left-right would 751 // look strange 752 if ((fLastModifiers & B_SHIFT_KEY) != 0 753 && fWindow->Look() != kLeftTitledWindowLook) { 754 action = ACTION_SLIDE_TAB; 755 break; 756 } 757 action = ACTION_DRAG; 758 break; 759 } 760 761 case Decorator::REGION_LEFT_BORDER: 762 case Decorator::REGION_RIGHT_BORDER: 763 case Decorator::REGION_TOP_BORDER: 764 case Decorator::REGION_BOTTOM_BORDER: 765 action = ACTION_DRAG; 766 break; 767 768 case Decorator::REGION_CLOSE_BUTTON: 769 action = (flags & B_NOT_CLOSABLE) == 0 770 ? ACTION_CLOSE : ACTION_DRAG; 771 break; 772 773 case Decorator::REGION_ZOOM_BUTTON: 774 action = (flags & B_NOT_ZOOMABLE) == 0 775 ? ACTION_ZOOM : ACTION_DRAG; 776 break; 777 778 case Decorator::REGION_MINIMIZE_BUTTON: 779 action = (flags & B_NOT_MINIMIZABLE) == 0 780 ? ACTION_MINIMIZE : ACTION_DRAG; 781 break; 782 783 case Decorator::REGION_LEFT_TOP_CORNER: 784 case Decorator::REGION_LEFT_BOTTOM_CORNER: 785 case Decorator::REGION_RIGHT_TOP_CORNER: 786 // TODO: Handle correctly! 787 action = ACTION_DRAG; 788 break; 789 790 case Decorator::REGION_RIGHT_BOTTOM_CORNER: 791 action = (flags & B_NOT_RESIZABLE) == 0 792 ? ACTION_RESIZE : ACTION_DRAG; 793 break; 794 795 default: 796 break; 797 } 798 } else if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0) { 799 // right mouse button 800 switch (hitRegion) { 801 case Decorator::REGION_TAB: 802 case Decorator::REGION_LEFT_BORDER: 803 case Decorator::REGION_RIGHT_BORDER: 804 case Decorator::REGION_TOP_BORDER: 805 case Decorator::REGION_BOTTOM_BORDER: 806 case Decorator::REGION_CLOSE_BUTTON: 807 case Decorator::REGION_ZOOM_BUTTON: 808 case Decorator::REGION_MINIMIZE_BUTTON: 809 case Decorator::REGION_LEFT_TOP_CORNER: 810 case Decorator::REGION_LEFT_BOTTOM_CORNER: 811 case Decorator::REGION_RIGHT_TOP_CORNER: 812 case Decorator::REGION_RIGHT_BOTTOM_CORNER: 813 action = ACTION_RESIZE_BORDER; 814 break; 815 816 default: 817 break; 818 } 819 } 820 } 821 822 _hitRegion = (int32)hitRegion; 823 824 if (action == ACTION_NONE) { 825 // No action -- if this is a click inside the window's contents, 826 // let it be forwarded to the window. 827 return inBorderRegion; 828 } 829 830 // reset the click count, if the hit region differs from the previous one 831 if (hitRegion != lastHitRegion) 832 clickCount = 1; 833 834 DesktopSettings desktopSettings(fDesktop); 835 if (!desktopSettings.AcceptFirstClick()) { 836 // Ignore clicks on decorator buttons if the 837 // non-floating window doesn't have focus 838 if (!fWindow->IsFocus() && !fWindow->IsFloating() 839 && action != ACTION_RESIZE_BORDER 840 && action != ACTION_RESIZE && action != ACTION_SLIDE_TAB) 841 action = ACTION_DRAG; 842 } 843 844 bool activateOnMouseUp = false; 845 if (action != ACTION_RESIZE_BORDER) { 846 // activate window if in click to activate mode, else only focus it 847 if (desktopSettings.MouseMode() == B_NORMAL_MOUSE) { 848 fDesktop->ActivateWindow(fWindow); 849 } else { 850 fDesktop->SetFocusWindow(fWindow); 851 activateOnMouseUp = true; 852 } 853 } 854 855 // switch to the new state 856 switch (action) { 857 case ACTION_CLOSE: 858 case ACTION_ZOOM: 859 case ACTION_MINIMIZE: 860 _NextState( 861 new (std::nothrow) DecoratorButtonState(*this, tab, hitRegion)); 862 STRACE_CLICK(("===> ACTION_CLOSE/ZOOM/MINIMIZE\n")); 863 break; 864 865 case ACTION_DRAG: 866 _NextState(new (std::nothrow) DragState(*this, where, 867 activateOnMouseUp, clickCount == 2)); 868 STRACE_CLICK(("===> ACTION_DRAG\n")); 869 break; 870 871 case ACTION_RESIZE: 872 _NextState(new (std::nothrow) ResizeState(*this, where, 873 activateOnMouseUp)); 874 STRACE_CLICK(("===> ACTION_RESIZE\n")); 875 break; 876 877 case ACTION_SLIDE_TAB: 878 _NextState(new (std::nothrow) SlideTabState(*this, where)); 879 STRACE_CLICK(("===> ACTION_SLIDE_TAB\n")); 880 break; 881 882 case ACTION_RESIZE_BORDER: 883 _NextState(new (std::nothrow) ResizeBorderState(*this, where, 884 hitRegion)); 885 STRACE_CLICK(("===> ACTION_RESIZE_BORDER\n")); 886 break; 887 888 default: 889 break; 890 } 891 892 return true; 893 } 894 895 896 void 897 DefaultWindowBehaviour::MouseUp(BMessage* message, BPoint where) 898 { 899 if (fState != NULL) 900 fState->MouseUp(message, where); 901 } 902 903 904 void 905 DefaultWindowBehaviour::MouseMoved(BMessage* message, BPoint where, bool isFake) 906 { 907 if (fState != NULL) { 908 fState->MouseMoved(message, where, isFake); 909 } else { 910 // If the window modifiers are hold, enter the window management state. 911 if (_IsWindowModifier(message->FindInt32("modifiers"))) 912 _NextState(new(std::nothrow) ManageWindowState(*this, where)); 913 } 914 915 // change focus in FFM mode 916 DesktopSettings desktopSettings(fDesktop); 917 if (desktopSettings.FocusFollowsMouse() 918 && !fWindow->IsFocus() && !(fWindow->Flags() & B_AVOID_FOCUS)) { 919 // If the mouse move is a fake one, we set the focus to NULL, which 920 // will cause the window that had focus last to retrieve it again - this 921 // makes FFM much nicer to use with the keyboard. 922 fDesktop->SetFocusWindow(isFake ? NULL : fWindow); 923 } 924 } 925 926 927 void 928 DefaultWindowBehaviour::ModifiersChanged(int32 modifiers) 929 { 930 BPoint where; 931 int32 buttons; 932 fDesktop->GetLastMouseState(&where, &buttons); 933 934 if (fState != NULL) { 935 fState->ModifiersChanged(where, modifiers); 936 } else { 937 // If the window modifiers are hold, enter the window management state. 938 if (_IsWindowModifier(modifiers)) 939 _NextState(new(std::nothrow) ManageWindowState(*this, where)); 940 } 941 } 942 943 944 bool 945 DefaultWindowBehaviour::AlterDeltaForSnap(Window* window, BPoint& delta, 946 bigtime_t now) 947 { 948 return fMagneticBorder.AlterDeltaForSnap(window, delta, now); 949 } 950 951 952 bool 953 DefaultWindowBehaviour::_IsWindowModifier(int32 modifiers) const 954 { 955 return (fWindow->Flags() & B_NO_SERVER_SIDE_WINDOW_MODIFIERS) == 0 956 && (modifiers & (B_COMMAND_KEY | B_CONTROL_KEY | B_OPTION_KEY 957 | B_SHIFT_KEY)) == (B_COMMAND_KEY | B_CONTROL_KEY); 958 } 959 960 961 Decorator::Region 962 DefaultWindowBehaviour::_RegionFor(const BMessage* message, int32& tab) const 963 { 964 Decorator* decorator = fWindow->Decorator(); 965 if (decorator == NULL) 966 return Decorator::REGION_NONE; 967 968 BPoint where; 969 if (message->FindPoint("where", &where) != B_OK) 970 return Decorator::REGION_NONE; 971 972 return decorator->RegionAt(where, tab); 973 } 974 975 976 void 977 DefaultWindowBehaviour::_SetBorderHighlights(int8 horizontal, int8 vertical, 978 bool active) 979 { 980 if (Decorator* decorator = fWindow->Decorator()) { 981 uint8 highlight = active 982 ? Decorator::HIGHLIGHT_RESIZE_BORDER 983 : Decorator::HIGHLIGHT_NONE; 984 985 // set the highlights for the borders 986 BRegion dirtyRegion; 987 switch (horizontal) { 988 case LEFT: 989 decorator->SetRegionHighlight(Decorator::REGION_LEFT_BORDER, 990 highlight, &dirtyRegion); 991 break; 992 case RIGHT: 993 decorator->SetRegionHighlight( 994 Decorator::REGION_RIGHT_BORDER, highlight, 995 &dirtyRegion); 996 break; 997 } 998 999 switch (vertical) { 1000 case TOP: 1001 decorator->SetRegionHighlight(Decorator::REGION_TOP_BORDER, 1002 highlight, &dirtyRegion); 1003 break; 1004 case BOTTOM: 1005 decorator->SetRegionHighlight( 1006 Decorator::REGION_BOTTOM_BORDER, highlight, 1007 &dirtyRegion); 1008 break; 1009 } 1010 1011 // set the highlights for the corners 1012 if (horizontal != NONE && vertical != NONE) { 1013 if (horizontal == LEFT) { 1014 if (vertical == TOP) { 1015 decorator->SetRegionHighlight( 1016 Decorator::REGION_LEFT_TOP_CORNER, highlight, 1017 &dirtyRegion); 1018 } else { 1019 decorator->SetRegionHighlight( 1020 Decorator::REGION_LEFT_BOTTOM_CORNER, highlight, 1021 &dirtyRegion); 1022 } 1023 } else { 1024 if (vertical == TOP) { 1025 decorator->SetRegionHighlight( 1026 Decorator::REGION_RIGHT_TOP_CORNER, highlight, 1027 &dirtyRegion); 1028 } else { 1029 decorator->SetRegionHighlight( 1030 Decorator::REGION_RIGHT_BOTTOM_CORNER, highlight, 1031 &dirtyRegion); 1032 } 1033 } 1034 } 1035 1036 // invalidate the affected regions 1037 fWindow->ProcessDirtyRegion(dirtyRegion); 1038 } 1039 } 1040 1041 1042 ServerCursor* 1043 DefaultWindowBehaviour::_ResizeCursorFor(int8 horizontal, int8 vertical) 1044 { 1045 // get the cursor ID corresponding to the border/corner 1046 BCursorID cursorID = B_CURSOR_ID_SYSTEM_DEFAULT; 1047 1048 if (horizontal == LEFT) { 1049 if (vertical == TOP) 1050 cursorID = B_CURSOR_ID_RESIZE_NORTH_WEST; 1051 else if (vertical == BOTTOM) 1052 cursorID = B_CURSOR_ID_RESIZE_SOUTH_WEST; 1053 else 1054 cursorID = B_CURSOR_ID_RESIZE_WEST; 1055 } else if (horizontal == RIGHT) { 1056 if (vertical == TOP) 1057 cursorID = B_CURSOR_ID_RESIZE_NORTH_EAST; 1058 else if (vertical == BOTTOM) 1059 cursorID = B_CURSOR_ID_RESIZE_SOUTH_EAST; 1060 else 1061 cursorID = B_CURSOR_ID_RESIZE_EAST; 1062 } else { 1063 if (vertical == TOP) 1064 cursorID = B_CURSOR_ID_RESIZE_NORTH; 1065 else if (vertical == BOTTOM) 1066 cursorID = B_CURSOR_ID_RESIZE_SOUTH; 1067 } 1068 1069 return fDesktop->GetCursorManager().GetCursor(cursorID); 1070 } 1071 1072 1073 void 1074 DefaultWindowBehaviour::_SetResizeCursor(int8 horizontal, int8 vertical) 1075 { 1076 fDesktop->SetManagementCursor(_ResizeCursorFor(horizontal, vertical)); 1077 } 1078 1079 1080 void 1081 DefaultWindowBehaviour::_ResetResizeCursor() 1082 { 1083 fDesktop->SetManagementCursor(NULL); 1084 } 1085 1086 1087 /*static*/ void 1088 DefaultWindowBehaviour::_ComputeResizeDirection(float x, float y, 1089 int8& _horizontal, int8& _vertical) 1090 { 1091 _horizontal = NONE; 1092 _vertical = NONE; 1093 1094 // compute the angle 1095 if (x == 0 && y == 0) 1096 return; 1097 1098 float angle = atan2f(y, x); 1099 1100 // rotate by 22.5 degree to align our sectors with 45 degree multiples 1101 angle += M_PI / 8; 1102 1103 // add 180 degree to the negative values, so we get a nice 0 to 360 1104 // degree range 1105 if (angle < 0) 1106 angle += M_PI * 2; 1107 1108 switch (int(angle / M_PI_4)) { 1109 case 0: 1110 _horizontal = RIGHT; 1111 break; 1112 case 1: 1113 _horizontal = RIGHT; 1114 _vertical = BOTTOM; 1115 break; 1116 case 2: 1117 _vertical = BOTTOM; 1118 break; 1119 case 3: 1120 _horizontal = LEFT; 1121 _vertical = BOTTOM; 1122 break; 1123 case 4: 1124 _horizontal = LEFT; 1125 break; 1126 case 5: 1127 _horizontal = LEFT; 1128 _vertical = TOP; 1129 break; 1130 case 6: 1131 _vertical = TOP; 1132 break; 1133 case 7: 1134 default: 1135 _horizontal = RIGHT; 1136 _vertical = TOP; 1137 break; 1138 } 1139 } 1140 1141 1142 void 1143 DefaultWindowBehaviour::_NextState(State* state) 1144 { 1145 // exit the old state 1146 if (fState != NULL) 1147 fState->ExitState(state); 1148 1149 // set and enter the new state 1150 State* oldState = fState; 1151 fState = state; 1152 1153 if (fState != NULL) { 1154 fState->EnterState(oldState); 1155 fDesktop->SetMouseEventWindow(fWindow); 1156 } else if (oldState != NULL) { 1157 // no state anymore -- reset the mouse event window, if it's still us 1158 if (fDesktop->MouseEventWindow() == fWindow) 1159 fDesktop->SetMouseEventWindow(NULL); 1160 } 1161 1162 delete oldState; 1163 } 1164