1 /* 2 * Copyright (c) 2001-2008, 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 */ 11 12 13 #include "Window.h" 14 15 #include "Decorator.h" 16 #include "DecorManager.h" 17 #include "Desktop.h" 18 #include "DrawingEngine.h" 19 #include "HWInterface.h" 20 #include "MessagePrivate.h" 21 #include "PortLink.h" 22 #include "ServerApp.h" 23 #include "ServerWindow.h" 24 #include "Workspace.h" 25 #include "WorkspacesView.h" 26 27 #include <ViewPrivate.h> 28 #include <WindowPrivate.h> 29 30 #include <Debug.h> 31 #include <DirectWindow.h> 32 #include <PortLink.h> 33 #include <View.h> 34 35 #include <new> 36 #include <stdio.h> 37 38 39 // Toggle debug output 40 //#define DEBUG_WINDOW 41 //#define DEBUG_WINDOW_CLICK 42 43 #ifdef DEBUG_WINDOW 44 # define STRACE(x) printf x 45 #else 46 # define STRACE(x) ; 47 #endif 48 49 #ifdef DEBUG_WINDOW_CLICK 50 # define STRACE_CLICK(x) printf x 51 #else 52 # define STRACE_CLICK(x) ; 53 #endif 54 55 // IMPORTANT: nested LockSingleWindow()s are not supported (by MultiLocker) 56 57 using std::nothrow; 58 59 // if the background clearing is delayed until 60 // the client draws the view, we have less flickering 61 // when contents have to be redrawn because of resizing 62 // a window or because the client invalidates parts. 63 // when redrawing something that has been exposed from underneath 64 // other windows, the other window will be seen longer at 65 // its previous position though if the exposed parts are not 66 // cleared right away. maybe there ought to be a flag in 67 // the update session, which tells us the cause of the update 68 69 70 Window::Window(const BRect& frame, const char *name, 71 window_look look, window_feel feel, uint32 flags, uint32 workspaces, 72 ::ServerWindow* window, DrawingEngine* drawingEngine) 73 : 74 fTitle(name), 75 fFrame(frame), 76 77 fVisibleRegion(), 78 fVisibleContentRegion(), 79 fDirtyRegion(), 80 fDirtyCause(0), 81 82 fBorderRegion(), 83 fContentRegion(), 84 fEffectiveDrawingRegion(), 85 86 fVisibleContentRegionValid(false), 87 fBorderRegionValid(false), 88 fContentRegionValid(false), 89 fEffectiveDrawingRegionValid(false), 90 91 fRegionPool(), 92 93 fIsClosing(false), 94 fIsMinimizing(false), 95 fIsZooming(false), 96 fIsResizing(false), 97 fIsSlidingTab(false), 98 fIsDragging(false), 99 fActivateOnMouseUp(false), 100 101 fDecorator(NULL), 102 fTopView(NULL), 103 fWindow(window), 104 fDrawingEngine(drawingEngine), 105 fDesktop(window->Desktop()), 106 107 fLastMousePosition(0.0, 0.0), 108 fLastMoveTime(0), 109 110 fCurrentUpdateSession(&fUpdateSessions[0]), 111 fPendingUpdateSession(&fUpdateSessions[1]), 112 fUpdateRequested(false), 113 fInUpdate(false), 114 fUpdatesEnabled(true), 115 116 // windows start hidden 117 fHidden(true), 118 fMinimized(false), 119 fIsFocus(false), 120 121 fLook(look), 122 fFeel(feel), 123 fWorkspaces(workspaces), 124 fCurrentWorkspace(-1), 125 126 fMinWidth(1), 127 fMaxWidth(32768), 128 fMinHeight(1), 129 fMaxHeight(32768), 130 131 fWorkspacesViewCount(0) 132 { 133 // make sure our arguments are valid 134 if (!IsValidLook(fLook)) 135 fLook = B_TITLED_WINDOW_LOOK; 136 if (!IsValidFeel(fFeel)) 137 fFeel = B_NORMAL_WINDOW_FEEL; 138 139 SetFlags(flags, NULL); 140 141 if (fLook != B_NO_BORDER_WINDOW_LOOK) { 142 fDecorator = gDecorManager.AllocateDecorator(fDesktop, fDrawingEngine, 143 frame, name, fLook, fFlags); 144 if (fDecorator) { 145 fDecorator->GetSizeLimits(&fMinWidth, &fMinHeight, 146 &fMaxWidth, &fMaxHeight); 147 } 148 } 149 150 // do we need to change our size to let the decorator fit? 151 // _ResizeBy() will adapt the frame for validity before resizing 152 if (feel == kDesktopWindowFeel) { 153 // the desktop window spans over the whole screen 154 // ToDo: this functionality should be moved somewhere else 155 // (so that it is always used when the workspace is changed) 156 uint16 width, height; 157 uint32 colorSpace; 158 float frequency; 159 if (fDesktop->ScreenAt(0)) { 160 fDesktop->ScreenAt(0)->GetMode(width, height, 161 colorSpace, frequency); 162 // TODO: MOVE THIS AWAY!!! ResizeBy contains calls to virtual methods! 163 // Also, there is no TopView()! 164 fFrame.OffsetTo(B_ORIGIN); 165 // ResizeBy(width - frame.Width(), height - frame.Height(), NULL); 166 } 167 } 168 169 STRACE(("Window %p, %s:\n", this, Name())); 170 STRACE(("\tFrame: (%.1f, %.1f, %.1f, %.1f)\n", fFrame.left, fFrame.top, 171 fFrame.right, fFrame.bottom)); 172 STRACE(("\tWindow %s\n", window ? window->Title() : "NULL")); 173 } 174 175 176 Window::~Window() 177 { 178 if (fTopView) { 179 fTopView->DetachedFromWindow(); 180 delete fTopView; 181 } 182 183 delete fDecorator; 184 185 delete fDrawingEngine; 186 } 187 188 189 status_t 190 Window::InitCheck() const 191 { 192 if (!fDrawingEngine) 193 return B_NO_MEMORY; 194 // TODO: anything else? 195 return B_OK; 196 } 197 198 199 void 200 Window::SetClipping(BRegion* stillAvailableOnScreen) 201 { 202 // this function is only called from the Desktop thread 203 204 // start from full region (as if the window was fully visible) 205 GetFullRegion(&fVisibleRegion); 206 // clip to region still available on screen 207 fVisibleRegion.IntersectWith(stillAvailableOnScreen); 208 209 fVisibleContentRegionValid = false; 210 fEffectiveDrawingRegionValid = false; 211 212 // TODO: review this! 213 fWindow->HandleDirectConnection(B_DIRECT_MODIFY | B_CLIPPING_MODIFIED); 214 } 215 216 217 void 218 Window::GetFullRegion(BRegion* region) 219 { 220 // TODO: if someone needs to call this from 221 // the outside, the clipping needs to be readlocked! 222 223 // start from the decorator border, extend to use the frame 224 GetBorderRegion(region); 225 region->Include(fFrame); 226 } 227 228 // GetBorderRegion 229 void 230 Window::GetBorderRegion(BRegion* region) 231 { 232 // TODO: if someone needs to call this from 233 // the outside, the clipping needs to be readlocked! 234 235 if (!fBorderRegionValid) { 236 if (fDecorator) 237 fDecorator->GetFootprint(&fBorderRegion); 238 else 239 fBorderRegion.MakeEmpty(); 240 241 fBorderRegionValid = true; 242 } 243 244 *region = fBorderRegion; 245 } 246 247 248 void 249 Window::GetContentRegion(BRegion* region) 250 { 251 // TODO: if someone needs to call this from 252 // the outside, the clipping needs to be readlocked! 253 254 if (!fContentRegionValid) { 255 _UpdateContentRegion(); 256 } 257 258 *region = fContentRegion; 259 } 260 261 262 BRegion& 263 Window::VisibleContentRegion() 264 { 265 // TODO: if someone needs to call this from 266 // the outside, the clipping needs to be readlocked! 267 268 // regions expected to be locked 269 if (!fVisibleContentRegionValid) { 270 GetContentRegion(&fVisibleContentRegion); 271 fVisibleContentRegion.IntersectWith(&fVisibleRegion); 272 } 273 return fVisibleContentRegion; 274 } 275 276 277 // #pragma mark - 278 279 280 void 281 Window::_PropagatePosition() 282 { 283 if ((fFlags & B_SAME_POSITION_IN_ALL_WORKSPACES) == 0) 284 return; 285 286 for (int32 i = 0; i < kListCount; i++) { 287 Anchor(i).position = fFrame.LeftTop(); 288 } 289 } 290 291 292 void 293 Window::MoveBy(int32 x, int32 y) 294 { 295 // this function is only called from the desktop thread 296 297 if (x == 0 && y == 0) 298 return; 299 300 fFrame.OffsetBy(x, y); 301 _PropagatePosition(); 302 303 // take along the dirty region which is not 304 // processed yet 305 fDirtyRegion.OffsetBy(x, y); 306 307 if (fBorderRegionValid) 308 fBorderRegion.OffsetBy(x, y); 309 if (fContentRegionValid) 310 fContentRegion.OffsetBy(x, y); 311 312 if (fCurrentUpdateSession->IsUsed()) 313 fCurrentUpdateSession->MoveBy(x, y); 314 if (fPendingUpdateSession->IsUsed()) 315 fPendingUpdateSession->MoveBy(x, y); 316 317 fEffectiveDrawingRegionValid = false; 318 319 if (fDecorator) 320 fDecorator->MoveBy(x, y); 321 322 if (fTopView != NULL) { 323 fTopView->MoveBy(x, y, NULL); 324 fTopView->UpdateOverlay(); 325 } 326 327 // the desktop will take care of dirty regions 328 329 // dispatch a message to the client informing about the changed size 330 BMessage msg(B_WINDOW_MOVED); 331 msg.AddInt64("when", system_time()); 332 msg.AddPoint("where", fFrame.LeftTop()); 333 fWindow->SendMessageToClient(&msg); 334 } 335 336 337 void 338 Window::ResizeBy(int32 x, int32 y, BRegion* dirtyRegion) 339 { 340 // this function is only called from the desktop thread 341 342 int32 wantWidth = fFrame.IntegerWidth() + x; 343 int32 wantHeight = fFrame.IntegerHeight() + y; 344 345 // enforce size limits 346 if (wantWidth < fMinWidth) 347 wantWidth = fMinWidth; 348 if (wantWidth > fMaxWidth) 349 wantWidth = fMaxWidth; 350 351 if (wantHeight < fMinHeight) 352 wantHeight = fMinHeight; 353 if (wantHeight > fMaxHeight) 354 wantHeight = fMaxHeight; 355 356 x = wantWidth - fFrame.IntegerWidth(); 357 y = wantHeight - fFrame.IntegerHeight(); 358 359 if (x == 0 && y == 0) 360 return; 361 362 fWindow->HandleDirectConnection(B_DIRECT_STOP); 363 364 fFrame.right += x; 365 fFrame.bottom += y; 366 367 fWindow->HandleDirectConnection(B_DIRECT_START | B_BUFFER_RESIZED); 368 369 fBorderRegionValid = false; 370 fContentRegionValid = false; 371 fEffectiveDrawingRegionValid = false; 372 373 if (fDecorator) { 374 fDecorator->ResizeBy(x, y, dirtyRegion); 375 //if (dirtyRegion) { 376 //fDrawingEngine->FillRegion(*dirtyRegion, (rgb_color){ 255, 255, 0, 255 }); 377 //snooze(40000); 378 //} 379 } 380 381 if (fTopView != NULL) { 382 fTopView->ResizeBy(x, y, dirtyRegion); 383 fTopView->UpdateOverlay(); 384 } 385 386 //if (dirtyRegion) 387 //fDrawingEngine->FillRegion(*dirtyRegion, (rgb_color){ 0, 255, 255, 255 }); 388 389 // send a message to the client informing about the changed size 390 BRect frame(Frame()); 391 BMessage msg(B_WINDOW_RESIZED); 392 msg.AddInt64("when", system_time()); 393 msg.AddInt32("width", frame.IntegerWidth()); 394 msg.AddInt32("height", frame.IntegerHeight()); 395 fWindow->SendMessageToClient(&msg); 396 } 397 398 399 void 400 Window::ScrollViewBy(View* view, int32 dx, int32 dy) 401 { 402 // this is executed in ServerWindow with the Readlock 403 // held 404 405 if (!view || view == fTopView || (dx == 0 && dy == 0)) 406 return; 407 408 BRegion* dirty = fRegionPool.GetRegion(); 409 if (!dirty) 410 return; 411 412 view->ScrollBy(dx, dy, dirty); 413 414 //fDrawingEngine->FillRegion(dirty, rgb_color{ 255, 0, 255, 255 }); 415 //snooze(2000); 416 417 if (IsVisible() && view->IsVisible()) { 418 dirty->IntersectWith(&VisibleContentRegion()); 419 _TriggerContentRedraw(*dirty); 420 } 421 422 fRegionPool.Recycle(dirty); 423 } 424 425 426 //! Takes care of invalidating parts that could not be copied 427 void 428 Window::CopyContents(BRegion* region, int32 xOffset, int32 yOffset) 429 { 430 // executed in ServerWindow thread with the read lock held 431 if (!IsVisible()) 432 return; 433 434 BRegion* newDirty = fRegionPool.GetRegion(*region); 435 436 // clip the region to the visible contents at the 437 // source and destination location (note that VisibleContentRegion() 438 // is used once to make sure it is valid, then fVisibleContentRegion 439 // is used directly) 440 region->IntersectWith(&VisibleContentRegion()); 441 if (region->CountRects() > 0) { 442 region->OffsetBy(xOffset, yOffset); 443 region->IntersectWith(&fVisibleContentRegion); 444 if (region->CountRects() > 0) { 445 // if the region still contains any rects 446 // offset to source location again 447 region->OffsetBy(-xOffset, -yOffset); 448 // the part which we can copy is not dirty 449 newDirty->Exclude(region); 450 451 fDrawingEngine->CopyRegion(region, xOffset, yOffset); 452 453 // move along the already dirty regions that are common 454 // with the region that we could copy 455 _ShiftPartOfRegion(&fDirtyRegion, region, xOffset, yOffset); 456 if (fPendingUpdateSession->IsUsed()) { 457 _ShiftPartOfRegion(&(fPendingUpdateSession->DirtyRegion()), region, 458 xOffset, yOffset); 459 } 460 461 if (fCurrentUpdateSession->IsUsed()) { 462 // if there are parts in the current update session 463 // that intersect with the copied region, we cannot 464 // simply shift them as with the other dirty regions 465 // - we cannot change the update rect already told to the 466 // client, that's why we transfer those parts to the 467 // new dirty region instead 468 BRegion* common = fRegionPool.GetRegion(*region); 469 // see if there is a common part at all 470 common->IntersectWith(&fCurrentUpdateSession->DirtyRegion()); 471 if (common->CountRects() > 0) { 472 // cut the common part from the region 473 fCurrentUpdateSession->DirtyRegion().Exclude(common); 474 newDirty->Include(common); 475 } 476 fRegionPool.Recycle(common); 477 } 478 } 479 } 480 // what is left visible from the original region 481 // at the destination after the region which could be 482 // copied has been excluded, is considered dirty 483 // NOTE: it may look like dirty regions are not moved 484 // if no region could be copied, but that's alright, 485 // since these parts will now be in newDirty anyways 486 // (with the right offset) 487 newDirty->OffsetBy(xOffset, yOffset); 488 newDirty->IntersectWith(&fVisibleContentRegion); 489 if (newDirty->CountRects() > 0) 490 ProcessDirtyRegion(*newDirty); 491 492 fRegionPool.Recycle(newDirty); 493 } 494 495 496 // #pragma mark - 497 498 499 void 500 Window::SetTopView(View* topView) 501 { 502 fTopView = topView; 503 504 if (fTopView) { 505 // the top view is special, it has a coordinate system 506 // as if it was attached directly to the desktop, therefor, 507 // the coordinate conversion through the view tree works 508 // as expected, since the top view has no "parent" but has 509 // fFrame as if it had 510 511 // make sure the location of the top view on screen matches ours 512 fTopView->MoveBy(fFrame.left - fTopView->Frame().left, 513 fFrame.top - fTopView->Frame().top, NULL); 514 515 // make sure the size of the top view matches ours 516 fTopView->ResizeBy(fFrame.Width() - fTopView->Frame().Width(), 517 fFrame.Height() - fTopView->Frame().Height(), NULL); 518 519 fTopView->AttachedToWindow(this); 520 } 521 } 522 523 524 View* 525 Window::ViewAt(const BPoint& where) 526 { 527 return fTopView->ViewAt(where); 528 } 529 530 531 window_anchor& 532 Window::Anchor(int32 index) 533 { 534 return fAnchor[index]; 535 } 536 537 538 Window* 539 Window::NextWindow(int32 index) const 540 { 541 return fAnchor[index].next; 542 } 543 544 545 Window* 546 Window::PreviousWindow(int32 index) const 547 { 548 return fAnchor[index].previous; 549 } 550 551 552 // #pragma mark - 553 554 555 void 556 Window::GetEffectiveDrawingRegion(View* view, BRegion& region) 557 { 558 if (!fEffectiveDrawingRegionValid) { 559 fEffectiveDrawingRegion = VisibleContentRegion(); 560 if (fUpdateRequested && !fInUpdate) { 561 // we requested an update, but the client has not started it yet, 562 // so it is only allowed to draw outside the pending update sessions region 563 fEffectiveDrawingRegion.Exclude(&(fPendingUpdateSession->DirtyRegion())); 564 } else if (fInUpdate) { 565 // enforce the dirty region of the update session 566 fEffectiveDrawingRegion.IntersectWith(&fCurrentUpdateSession->DirtyRegion()); 567 } else { 568 // not in update, the view can draw everywhere 569 //printf("Window(%s)::GetEffectiveDrawingRegion(for %s) - outside update\n", Title(), view->Name()); 570 } 571 572 fEffectiveDrawingRegionValid = true; 573 } 574 575 // TODO: this is a region that needs to be cached later in the server 576 // when the current view in ServerWindow is set, and we are currently 577 // in an update (fInUpdate), than we can set this region and remember 578 // it for the comming drawing commands until the current view changes 579 // again or fEffectiveDrawingRegionValid is suddenly false. 580 region = fEffectiveDrawingRegion; 581 if (!fContentRegionValid) 582 _UpdateContentRegion(); 583 584 region.IntersectWith(&view->ScreenClipping(&fContentRegion)); 585 } 586 587 588 bool 589 Window::DrawingRegionChanged(View* view) const 590 { 591 return !fEffectiveDrawingRegionValid || !view->IsScreenClippingValid(); 592 } 593 594 595 void 596 Window::ProcessDirtyRegion(BRegion& region) 597 { 598 // if this is exectuted in the desktop thread, 599 // it means that the window thread currently 600 // blocks to get the read lock, if it is 601 // executed from the window thread, it should 602 // have the read lock and the desktop thread 603 // is blocking to get the write lock. IAW, this 604 // is only executed in one thread. 605 if (fDirtyRegion.CountRects() == 0) { 606 // the window needs to be informed 607 // when the dirty region was empty. 608 // NOTE: when the window thread has processed 609 // the dirty region in MessageReceived(), 610 // it will make the region empty again, 611 // when it is empty here, we need to send 612 // the message to initiate the next update round. 613 // Until the message is processed in the window 614 // thread, the desktop thread can add parts to 615 // the region as it likes. 616 ServerWindow()->RequestRedraw(); 617 } 618 619 fDirtyRegion.Include(®ion); 620 fDirtyCause |= UPDATE_EXPOSE; 621 } 622 623 624 void 625 Window::RedrawDirtyRegion() 626 { 627 // executed from ServerWindow with the read lock held 628 629 if (IsVisible()) { 630 _DrawBorder(); 631 632 BRegion* dirtyContentRegion = 633 fRegionPool.GetRegion(VisibleContentRegion()); 634 dirtyContentRegion->IntersectWith(&fDirtyRegion); 635 636 _TriggerContentRedraw(*dirtyContentRegion); 637 638 fRegionPool.Recycle(dirtyContentRegion); 639 } 640 641 // reset the dirty region, since 642 // we're fully clean. If the desktop 643 // thread wanted to mark something 644 // dirty in the mean time, it was 645 // blocking on the global region lock to 646 // get write access, since we're holding 647 // the read lock for the whole time. 648 fDirtyRegion.MakeEmpty(); 649 fDirtyCause = 0; 650 } 651 652 653 void 654 Window::MarkDirty(BRegion& regionOnScreen) 655 { 656 // for marking any part of the desktop dirty 657 // this will get write access to the global 658 // region lock, and result in ProcessDirtyRegion() 659 // to be called for any windows affected 660 if (fDesktop) 661 fDesktop->MarkDirty(regionOnScreen); 662 } 663 664 665 void 666 Window::MarkContentDirty(BRegion& regionOnScreen) 667 { 668 // for triggering AS_REDRAW 669 // since this won't affect other windows, read locking 670 // is sufficient. If there was no dirty region before, 671 // an update message is triggered 672 if (fHidden) 673 return; 674 675 regionOnScreen.IntersectWith(&VisibleContentRegion()); 676 fDirtyCause |= UPDATE_REQUEST; 677 _TriggerContentRedraw(regionOnScreen); 678 } 679 680 681 void 682 Window::MarkContentDirtyAsync(BRegion& regionOnScreen) 683 { 684 // NOTE: see comments in ProcessDirtyRegion() 685 if (fHidden) 686 return; 687 688 regionOnScreen.IntersectWith(&VisibleContentRegion()); 689 690 if (fDirtyRegion.CountRects() == 0) { 691 ServerWindow()->RequestRedraw(); 692 } 693 694 fDirtyRegion.Include(®ionOnScreen); 695 fDirtyCause |= UPDATE_REQUEST; 696 } 697 698 699 void 700 Window::InvalidateView(View* view, BRegion& viewRegion) 701 { 702 if (view && IsVisible() && view->IsVisible()) { 703 if (!fContentRegionValid) 704 _UpdateContentRegion(); 705 706 view->ConvertToScreen(&viewRegion); 707 viewRegion.IntersectWith(&VisibleContentRegion()); 708 if (viewRegion.CountRects() > 0) { 709 viewRegion.IntersectWith(&view->ScreenClipping(&fContentRegion)); 710 711 //fDrawingEngine->FillRegion(viewRegion, rgb_color{ 0, 255, 0, 255 }); 712 //snooze(10000); 713 fDirtyCause |= UPDATE_REQUEST; 714 _TriggerContentRedraw(viewRegion); 715 } 716 } 717 } 718 719 // DisableUpdateRequests 720 void 721 Window::DisableUpdateRequests() 722 { 723 fUpdatesEnabled = false; 724 } 725 726 727 // EnableUpdateRequests 728 void 729 Window::EnableUpdateRequests() 730 { 731 fUpdatesEnabled = true; 732 if (!fUpdateRequested && fPendingUpdateSession->IsUsed()) 733 _SendUpdateMessage(); 734 } 735 736 // #pragma mark - 737 738 739 void 740 Window::MouseDown(BMessage* message, BPoint where, int32* _viewToken) 741 { 742 // TODO: move into Decorator 743 if (!fBorderRegionValid) 744 GetBorderRegion(&fBorderRegion); 745 746 // default action is to drag the Window 747 if (fBorderRegion.Contains(where)) { 748 // clicking Window visible area 749 750 click_type action = DEC_DRAG; 751 752 if (fDecorator) 753 action = _ActionFor(message); 754 755 // ignore clicks on decorator buttons if the 756 // non-floating window doesn't have focus 757 if (!IsFocus() && !IsFloating() && action != DEC_MOVETOBACK 758 && action != DEC_RESIZE && action != DEC_SLIDETAB) 759 action = DEC_DRAG; 760 761 // set decorator internals 762 switch (action) { 763 case DEC_CLOSE: 764 fIsClosing = true; 765 STRACE_CLICK(("===> DEC_CLOSE\n")); 766 break; 767 768 case DEC_ZOOM: 769 fIsZooming = true; 770 STRACE_CLICK(("===> DEC_ZOOM\n")); 771 break; 772 773 case DEC_MINIMIZE: 774 fIsMinimizing = true; 775 STRACE_CLICK(("===> DEC_MINIMIZE\n")); 776 break; 777 778 case DEC_DRAG: 779 fIsDragging = true; 780 fLastMousePosition = where; 781 STRACE_CLICK(("===> DEC_DRAG\n")); 782 break; 783 784 case DEC_RESIZE: 785 fIsResizing = true; 786 fLastMousePosition = where; 787 STRACE_CLICK(("===> DEC_RESIZE\n")); 788 break; 789 790 case DEC_SLIDETAB: 791 fIsSlidingTab = true; 792 fLastMousePosition = where; 793 STRACE_CLICK(("===> DEC_SLIDETAB\n")); 794 break; 795 796 default: 797 break; 798 } 799 800 // redraw decorator 801 BRegion* visibleBorder = fRegionPool.GetRegion(); 802 GetBorderRegion(visibleBorder); 803 visibleBorder->IntersectWith(&VisibleRegion()); 804 805 DrawingEngine* engine = fDecorator->GetDrawingEngine(); 806 engine->LockParallelAccess(); 807 engine->ConstrainClippingRegion(visibleBorder); 808 809 if (fIsZooming) { 810 fDecorator->SetZoom(true); 811 } else if (fIsClosing) { 812 fDecorator->SetClose(true); 813 } else if (fIsMinimizing) { 814 fDecorator->SetMinimize(true); 815 } 816 817 engine->UnlockParallelAccess(); 818 819 fRegionPool.Recycle(visibleBorder); 820 821 // based on what the Decorator returned, properly place this window. 822 if (action == DEC_MOVETOBACK) { 823 fDesktop->SendWindowBehind(this); 824 } else { 825 fDesktop->SetMouseEventWindow(this); 826 827 // activate window if not in FFM mode 828 DesktopSettings desktopSettings(fDesktop); 829 if (!desktopSettings.FocusFollowsMouse()) { 830 fDesktop->ActivateWindow(this); 831 } else { 832 // actually, the window should already be 833 // focused since the mouse would have to 834 // be over it, but just for completeness... 835 fDesktop->SetFocusWindow(this); 836 if (action == DEC_DRAG) { 837 fActivateOnMouseUp = true; 838 fLastMoveTime = system_time(); 839 } 840 } 841 } 842 } else { 843 // click was inside the window contents 844 if (View* view = ViewAt(where)) { 845 if (HasModal()) 846 return; 847 848 // clicking a simple View 849 if (!IsFocus()) { 850 DesktopSettings desktopSettings(fDesktop); 851 852 // Activate window in case it doesn't accept first click, and 853 // we're not in FFM mode 854 if ((Flags() & B_WILL_ACCEPT_FIRST_CLICK) == 0 855 && !desktopSettings.FocusFollowsMouse()) 856 fDesktop->ActivateWindow(this); 857 858 // Eat the click if we don't accept first click 859 // (B_AVOID_FOCUS never gets the focus, so they always accept 860 // the first click) 861 // TODO: the latter is unlike BeOS - if we really wanted to 862 // imitate this behaviour, we would need to check if we're 863 // the front window instead of the focus window 864 if ((Flags() & (B_WILL_ACCEPT_FIRST_CLICK 865 | B_AVOID_FOCUS)) == 0) 866 return; 867 } 868 869 // fill out view token for the view under the mouse 870 *_viewToken = view->Token(); 871 view->MouseDown(message, where); 872 } 873 } 874 } 875 876 877 void 878 Window::MouseUp(BMessage* message, BPoint where, int32* _viewToken) 879 { 880 bool invalidate = false; 881 if (fDecorator) { 882 click_type action = _ActionFor(message); 883 884 // redraw decorator 885 BRegion* visibleBorder = fRegionPool.GetRegion(); 886 GetBorderRegion(visibleBorder); 887 visibleBorder->IntersectWith(&VisibleRegion()); 888 889 DrawingEngine* engine = fDecorator->GetDrawingEngine(); 890 engine->LockParallelAccess(); 891 engine->ConstrainClippingRegion(visibleBorder); 892 893 if (fIsZooming) { 894 fIsZooming = false; 895 fDecorator->SetZoom(false); 896 if (action == DEC_ZOOM) { 897 invalidate = true; 898 fWindow->NotifyZoom(); 899 } 900 } 901 if (fIsClosing) { 902 fIsClosing = false; 903 fDecorator->SetClose(false); 904 if (action == DEC_CLOSE) { 905 invalidate = true; 906 fWindow->NotifyQuitRequested(); 907 } 908 } 909 if (fIsMinimizing) { 910 fIsMinimizing = false; 911 fDecorator->SetMinimize(false); 912 if (action == DEC_MINIMIZE) { 913 invalidate = true; 914 fWindow->NotifyMinimize(true); 915 } 916 } 917 918 engine->UnlockParallelAccess(); 919 920 fRegionPool.Recycle(visibleBorder); 921 } 922 923 // in FFM mode, activate the window and bring it 924 // to front in case this was a drag click but the 925 // mouse was not moved 926 if (fActivateOnMouseUp) { 927 fActivateOnMouseUp = false; 928 // on R5, there is a time window for this feature 929 // ie, click and press too long, nothing will happen 930 if (system_time() - fLastMoveTime < 500000) 931 fDesktop->ActivateWindow(this); 932 } 933 934 fIsDragging = false; 935 fIsResizing = false; 936 fIsSlidingTab = false; 937 938 if (View* view = ViewAt(where)) { 939 if (HasModal()) 940 return; 941 942 *_viewToken = view->Token(); 943 view->MouseUp(message, where); 944 } 945 } 946 947 948 void 949 Window::MouseMoved(BMessage *message, BPoint where, int32* _viewToken, 950 bool isLatestMouseMoved) 951 { 952 #if 0 953 if (fDecorator != NULL && fTopView != NULL) { 954 DrawingEngine* engine = fDecorator->GetDrawingEngine(); 955 engine->LockParallelAccess(); 956 engine->ConstrainClippingRegion(&VisibleRegion()); 957 958 fTopView->MarkAt(engine, where); 959 engine->UnlockParallelAccess(); 960 } 961 #endif 962 963 View* view = ViewAt(where); 964 if (view != NULL) 965 *_viewToken = view->Token(); 966 967 // ignore pointer history 968 if (!isLatestMouseMoved) 969 return; 970 971 // limit the rate at which "mouse moved" events 972 // are handled that move or resize the window 973 if (fIsDragging || fIsResizing) { 974 bigtime_t now = system_time(); 975 if (now - fLastMoveTime < 13333) { 976 // TODO: add a "timed event" to query for 977 // the then current mouse position 978 return; 979 } 980 fLastMoveTime = now; 981 } 982 983 if (fDecorator) { 984 BRegion* visibleBorder = fRegionPool.GetRegion(); 985 GetBorderRegion(visibleBorder); 986 visibleBorder->IntersectWith(&VisibleRegion()); 987 988 DrawingEngine* engine = fDecorator->GetDrawingEngine(); 989 engine->LockParallelAccess(); 990 engine->ConstrainClippingRegion(visibleBorder); 991 992 if (fIsZooming) { 993 fDecorator->SetZoom(_ActionFor(message) == DEC_ZOOM); 994 } else if (fIsClosing) { 995 fDecorator->SetClose(_ActionFor(message) == DEC_CLOSE); 996 } else if (fIsMinimizing) { 997 fDecorator->SetMinimize(_ActionFor(message) == DEC_MINIMIZE); 998 } 999 1000 engine->UnlockParallelAccess(); 1001 fRegionPool.Recycle(visibleBorder); 1002 } 1003 1004 BPoint delta = where - fLastMousePosition; 1005 // NOTE: "delta" is later used to change fLastMousePosition. 1006 // If for some reason no change should take effect, delta 1007 // is to be set to (0, 0) so that fLastMousePosition is not 1008 // adjusted. This way the relative mouse position to the 1009 // item being changed (border during resizing, tab during 1010 // sliding...) stays fixed when the mouse is moved so that 1011 // changes are taking effect again. 1012 1013 // moving 1014 if (fIsDragging) { 1015 if (!(Flags() & B_NOT_MOVABLE)) { 1016 BPoint oldLeftTop = fFrame.LeftTop(); 1017 1018 fDesktop->MoveWindowBy(this, delta.x, delta.y); 1019 1020 // constrain delta to true change in size 1021 delta = fFrame.LeftTop() - oldLeftTop; 1022 } else 1023 delta = BPoint(0, 0); 1024 } 1025 // resizing 1026 if (fIsResizing) { 1027 if (!(Flags() & B_NOT_RESIZABLE)) { 1028 if (Flags() & B_NOT_V_RESIZABLE) 1029 delta.y = 0; 1030 if (Flags() & B_NOT_H_RESIZABLE) 1031 delta.x = 0; 1032 1033 BPoint oldRightBottom = fFrame.RightBottom(); 1034 1035 fDesktop->ResizeWindowBy(this, delta.x, delta.y); 1036 1037 // constrain delta to true change in size 1038 delta = fFrame.RightBottom() - oldRightBottom; 1039 } else 1040 delta = BPoint(0, 0); 1041 } 1042 // sliding tab 1043 if (fIsSlidingTab) { 1044 float loc = TabLocation(); 1045 // TODO: change to [0:1] 1046 loc += delta.x; 1047 if (fDesktop->SetWindowTabLocation(this, loc)) 1048 delta.y = 0; 1049 else 1050 delta = BPoint(0, 0); 1051 } 1052 1053 // NOTE: fLastMousePosition is currently only 1054 // used for window moving/resizing/sliding the tab 1055 fLastMousePosition += delta; 1056 1057 // the window was moved, it doesn't come to 1058 // the front in FFM mode when the mouse is released 1059 fActivateOnMouseUp = false; 1060 1061 // change focus in FFM mode 1062 DesktopSettings desktopSettings(fDesktop); 1063 if (desktopSettings.FocusFollowsMouse() 1064 && !IsFocus() && !(Flags() & B_AVOID_FOCUS)) { 1065 fDesktop->SetFocusWindow(this); 1066 } 1067 1068 // mouse cursor 1069 1070 if (view != NULL) { 1071 view->MouseMoved(message, where); 1072 1073 // TODO: there is more for real cursor support, ie. if a window is closed, 1074 // new app cursor shouldn't override view cursor, ... 1075 ServerWindow()->App()->SetCurrentCursor(view->Cursor()); 1076 } 1077 } 1078 1079 1080 // #pragma mark - 1081 1082 1083 void 1084 Window::WorkspaceActivated(int32 index, bool active) 1085 { 1086 if (!active) 1087 fWindow->HandleDirectConnection(B_DIRECT_STOP); 1088 1089 BMessage activatedMsg(B_WORKSPACE_ACTIVATED); 1090 activatedMsg.AddInt64("when", system_time()); 1091 activatedMsg.AddInt32("workspace", index); 1092 activatedMsg.AddBool("active", active); 1093 1094 ServerWindow()->SendMessageToClient(&activatedMsg); 1095 1096 if (active) 1097 fWindow->HandleDirectConnection(B_DIRECT_START | B_BUFFER_RESET); 1098 } 1099 1100 1101 void 1102 Window::WorkspacesChanged(uint32 oldWorkspaces, uint32 newWorkspaces) 1103 { 1104 fWorkspaces = newWorkspaces; 1105 1106 BMessage changedMsg(B_WORKSPACES_CHANGED); 1107 changedMsg.AddInt64("when", system_time()); 1108 changedMsg.AddInt32("old", oldWorkspaces); 1109 changedMsg.AddInt32("new", newWorkspaces); 1110 1111 ServerWindow()->SendMessageToClient(&changedMsg); 1112 } 1113 1114 1115 void 1116 Window::Activated(bool active) 1117 { 1118 BMessage msg(B_WINDOW_ACTIVATED); 1119 msg.AddBool("active", active); 1120 ServerWindow()->SendMessageToClient(&msg); 1121 } 1122 1123 1124 //# pragma mark - 1125 1126 1127 void 1128 Window::SetTitle(const char* name, BRegion& dirty) 1129 { 1130 // rebuild the clipping for the title area 1131 // and redraw it. 1132 1133 fTitle = name; 1134 1135 if (fDecorator) { 1136 fDecorator->SetTitle(name, &dirty); 1137 1138 fBorderRegionValid = false; 1139 // the border very likely changed 1140 } 1141 } 1142 1143 1144 void 1145 Window::SetFocus(bool focus) 1146 { 1147 // executed from Desktop thread 1148 // it holds the clipping write lock, 1149 // so the window thread cannot be 1150 // accessing fIsFocus 1151 1152 BRegion* dirty = fRegionPool.GetRegion(fBorderRegion); 1153 if (dirty) { 1154 dirty->IntersectWith(&fVisibleRegion); 1155 fDesktop->MarkDirty(*dirty); 1156 fRegionPool.Recycle(dirty); 1157 } 1158 1159 fIsFocus = focus; 1160 if (fDecorator) 1161 fDecorator->SetFocus(focus); 1162 1163 Activated(focus); 1164 } 1165 1166 1167 void 1168 Window::SetHidden(bool hidden) 1169 { 1170 // the desktop takes care of dirty regions 1171 if (fHidden != hidden) { 1172 fHidden = hidden; 1173 1174 fTopView->SetHidden(hidden); 1175 1176 // TODO: anything else? 1177 } 1178 } 1179 1180 1181 void 1182 Window::SetMinimized(bool minimized) 1183 { 1184 if (minimized == fMinimized) 1185 return; 1186 1187 fMinimized = minimized; 1188 } 1189 1190 1191 bool 1192 Window::IsVisible() const 1193 { 1194 if (IsOffscreenWindow()) 1195 return true; 1196 1197 if (IsHidden()) 1198 return false; 1199 1200 /* 1201 if (fVisibleRegion.CountRects() == 0) 1202 return false; 1203 */ 1204 return fCurrentWorkspace >= 0 && fCurrentWorkspace < kWorkingList; 1205 } 1206 1207 1208 void 1209 Window::SetSizeLimits(int32 minWidth, int32 maxWidth, 1210 int32 minHeight, int32 maxHeight) 1211 { 1212 if (minWidth < 0) 1213 minWidth = 0; 1214 1215 if (minHeight < 0) 1216 minHeight = 0; 1217 1218 fMinWidth = minWidth; 1219 fMaxWidth = maxWidth; 1220 fMinHeight = minHeight; 1221 fMaxHeight = maxHeight; 1222 1223 // give the Decorator a say in this too 1224 if (fDecorator) { 1225 fDecorator->GetSizeLimits(&fMinWidth, &fMinHeight, 1226 &fMaxWidth, &fMaxHeight); 1227 } 1228 1229 _ObeySizeLimits(); 1230 } 1231 1232 1233 void 1234 Window::GetSizeLimits(int32* minWidth, int32* maxWidth, 1235 int32* minHeight, int32* maxHeight) const 1236 { 1237 *minWidth = fMinWidth; 1238 *maxWidth = fMaxWidth; 1239 *minHeight = fMinHeight; 1240 *maxHeight = fMaxHeight; 1241 } 1242 1243 1244 bool 1245 Window::SetTabLocation(float location, BRegion& dirty) 1246 { 1247 bool ret = false; 1248 if (fDecorator) { 1249 ret = fDecorator->SetTabLocation(location, &dirty); 1250 // the border region changed if ret is true 1251 fBorderRegionValid = fBorderRegionValid && !ret; 1252 } 1253 return ret; 1254 } 1255 1256 1257 float 1258 Window::TabLocation() const 1259 { 1260 if (fDecorator) 1261 return fDecorator->TabLocation(); 1262 return 0.0; 1263 } 1264 1265 1266 bool 1267 Window::SetDecoratorSettings(const BMessage& settings, BRegion& dirty) 1268 { 1269 bool ret = false; 1270 if (fDecorator) { 1271 ret = fDecorator->SetSettings(settings, &dirty); 1272 // the border region changed if ret is true 1273 fBorderRegionValid = fBorderRegionValid && !ret; 1274 } 1275 return ret; 1276 } 1277 1278 1279 bool 1280 Window::GetDecoratorSettings(BMessage* settings) 1281 { 1282 if (fDecorator) 1283 return fDecorator->GetSettings(settings); 1284 1285 return false; 1286 } 1287 1288 1289 void 1290 Window::SetLook(window_look look, BRegion* updateRegion) 1291 { 1292 if (fDecorator == NULL && look != B_NO_BORDER_WINDOW_LOOK) { 1293 // we need a new decorator 1294 fDecorator = gDecorManager.AllocateDecorator(fDesktop, fDrawingEngine, 1295 Frame(), Title(), fLook, fFlags); 1296 if (IsFocus()) 1297 fDecorator->SetFocus(true); 1298 } 1299 1300 fLook = look; 1301 1302 fBorderRegionValid = false; 1303 // the border very likely changed 1304 fContentRegionValid = false; 1305 // mabye a resize handle was added... 1306 fEffectiveDrawingRegionValid = false; 1307 // ...and therefor the drawing region is 1308 // likely not valid anymore either 1309 1310 if (fDecorator != NULL) { 1311 DesktopSettings settings(fDesktop); 1312 fDecorator->SetLook(settings, look, updateRegion); 1313 1314 // we might need to resize the window! 1315 fDecorator->GetSizeLimits(&fMinWidth, &fMinHeight, &fMaxWidth, &fMaxHeight); 1316 _ObeySizeLimits(); 1317 } 1318 1319 if (look == B_NO_BORDER_WINDOW_LOOK) { 1320 // we don't need a decorator for this window 1321 delete fDecorator; 1322 fDecorator = NULL; 1323 } 1324 } 1325 1326 1327 void 1328 Window::SetFeel(window_feel feel) 1329 { 1330 // if the subset list is no longer needed, clear it 1331 if ((fFeel == B_MODAL_SUBSET_WINDOW_FEEL || fFeel == B_FLOATING_SUBSET_WINDOW_FEEL) 1332 && (feel != B_MODAL_SUBSET_WINDOW_FEEL && feel != B_FLOATING_SUBSET_WINDOW_FEEL)) 1333 fSubsets.MakeEmpty(); 1334 1335 fFeel = feel; 1336 1337 // having modal windows with B_AVOID_FRONT or B_AVOID_FOCUS doesn't 1338 // make that much sense, so we filter those flags out on demand 1339 fFlags = fOriginalFlags; 1340 fFlags &= ValidWindowFlags(fFeel); 1341 1342 if (!IsNormal()) { 1343 fFlags |= B_SAME_POSITION_IN_ALL_WORKSPACES; 1344 _PropagatePosition(); 1345 } 1346 } 1347 1348 1349 void 1350 Window::SetFlags(uint32 flags, BRegion* updateRegion) 1351 { 1352 fOriginalFlags = flags; 1353 fFlags = flags & ValidWindowFlags(fFeel); 1354 if (!IsNormal()) 1355 fFlags |= B_SAME_POSITION_IN_ALL_WORKSPACES; 1356 1357 if ((fFlags & B_SAME_POSITION_IN_ALL_WORKSPACES) != 0) 1358 _PropagatePosition(); 1359 1360 if (fDecorator == NULL) 1361 return; 1362 1363 fDecorator->SetFlags(flags, updateRegion); 1364 1365 fBorderRegionValid = false; 1366 // the border might have changed (smaller/larger tab) 1367 1368 // we might need to resize the window! 1369 if (fDecorator) { 1370 fDecorator->GetSizeLimits(&fMinWidth, &fMinHeight, &fMaxWidth, &fMaxHeight); 1371 _ObeySizeLimits(); 1372 } 1373 } 1374 1375 1376 /*! Returns wether or not a window is in the workspace list with the 1377 specified \a index. 1378 */ 1379 bool 1380 Window::InWorkspace(int32 index) const 1381 { 1382 return (fWorkspaces & (1UL << index)) != 0; 1383 } 1384 1385 1386 bool 1387 Window::SupportsFront() 1388 { 1389 if (fFeel == kDesktopWindowFeel 1390 || fFeel == kMenuWindowFeel 1391 || (fFlags & B_AVOID_FRONT) != 0) 1392 return false; 1393 1394 return true; 1395 } 1396 1397 1398 bool 1399 Window::IsModal() const 1400 { 1401 return IsModalFeel(fFeel); 1402 } 1403 1404 1405 bool 1406 Window::IsFloating() const 1407 { 1408 return IsFloatingFeel(fFeel); 1409 } 1410 1411 1412 bool 1413 Window::IsNormal() const 1414 { 1415 return !IsFloatingFeel(fFeel) && !IsModalFeel(fFeel); 1416 } 1417 1418 1419 bool 1420 Window::HasModal() const 1421 { 1422 for (Window* window = NextWindow(fCurrentWorkspace); window != NULL; 1423 window = window->NextWindow(fCurrentWorkspace)) { 1424 if (window->IsHidden() || !window->IsModal()) 1425 continue; 1426 1427 if (window->HasInSubset(this)) 1428 return true; 1429 } 1430 1431 return false; 1432 } 1433 1434 1435 /*! \brief Returns the windows that's in behind of the backmost position 1436 this window can get. 1437 Returns NULL is this window can be the backmost window. 1438 */ 1439 Window* 1440 Window::Backmost(Window* window, int32 workspace) 1441 { 1442 if (workspace == -1) 1443 workspace = fCurrentWorkspace; 1444 1445 // Desktop windows are always backmost 1446 if (fFeel == kDesktopWindowFeel) 1447 return NULL; 1448 1449 if (window == NULL) 1450 window = PreviousWindow(workspace); 1451 1452 for (; window != NULL; window = window->PreviousWindow(workspace)) { 1453 if (window->IsHidden() || window == this) 1454 continue; 1455 1456 if (HasInSubset(window)) 1457 return window; 1458 } 1459 1460 return NULL; 1461 } 1462 1463 1464 /*! \brief Returns the windows that's in front of the frontmost position 1465 this window can get. 1466 Returns NULL if this window can be the frontmost window. 1467 */ 1468 Window* 1469 Window::Frontmost(Window* first, int32 workspace) 1470 { 1471 if (workspace == -1) 1472 workspace = fCurrentWorkspace; 1473 1474 if (fFeel == kDesktopWindowFeel) 1475 return first ? first : NextWindow(workspace); 1476 1477 if (first == NULL) 1478 first = NextWindow(workspace); 1479 1480 for (Window* window = first; window != NULL; 1481 window = window->NextWindow(workspace)) { 1482 if (window->IsHidden() || window == this) 1483 continue; 1484 1485 if (window->HasInSubset(this)) 1486 return window; 1487 } 1488 1489 return NULL; 1490 } 1491 1492 1493 bool 1494 Window::AddToSubset(Window* window) 1495 { 1496 return fSubsets.AddItem(window); 1497 } 1498 1499 1500 void 1501 Window::RemoveFromSubset(Window* window) 1502 { 1503 fSubsets.RemoveItem(window); 1504 } 1505 1506 1507 bool 1508 Window::HasInSubset(const Window* window) const 1509 { 1510 if (window == NULL || fFeel == window->Feel() 1511 || fFeel == B_NORMAL_WINDOW_FEEL) 1512 return false; 1513 1514 // Menus are a special case: they will always be on-top of every window 1515 // of their application 1516 if (fFeel == kMenuWindowFeel) 1517 return window->ServerWindow()->App() == ServerWindow()->App(); 1518 else if (window->Feel() == kMenuWindowFeel) 1519 return false; 1520 1521 // we have a few special feels that have a fixed order 1522 1523 const int32 feel[] = {kWindowScreenFeel, B_MODAL_ALL_WINDOW_FEEL, 1524 B_FLOATING_ALL_WINDOW_FEEL, 0}; 1525 1526 for (int32 order = 0; feel[order]; order++) { 1527 if (fFeel == feel[order]) 1528 return true; 1529 if (window->Feel() == feel[order]) 1530 return false; 1531 } 1532 1533 if (fFeel == B_FLOATING_APP_WINDOW_FEEL 1534 && window->Feel() != B_MODAL_APP_WINDOW_FEEL 1535 || fFeel == B_MODAL_APP_WINDOW_FEEL) 1536 return window->ServerWindow()->App() == ServerWindow()->App(); 1537 1538 return fSubsets.HasItem(window); 1539 } 1540 1541 1542 /*! \brief Collects all workspaces views in this window and puts it into \a list 1543 */ 1544 void 1545 Window::FindWorkspacesViews(BObjectList<WorkspacesView>& list) const 1546 { 1547 int32 count = fWorkspacesViewCount; 1548 fTopView->FindViews(kWorkspacesViewFlag, (BObjectList<View>&)list, count); 1549 } 1550 1551 1552 /*! \brief Returns on which workspaces the window should be visible. 1553 1554 A modal or floating window may be visible on a workscreen if one 1555 of its subset windows is visible there. Floating windows also need 1556 to have a subset as front window to be visible. 1557 */ 1558 uint32 1559 Window::SubsetWorkspaces() const 1560 { 1561 if (fFeel == B_MODAL_ALL_WINDOW_FEEL 1562 || fFeel == B_FLOATING_ALL_WINDOW_FEEL) 1563 return B_ALL_WORKSPACES; 1564 1565 if (fFeel == B_FLOATING_APP_WINDOW_FEEL) { 1566 Window* front = fDesktop->FrontWindow(); 1567 if (front != NULL && front->IsNormal() 1568 && front->ServerWindow()->App() == ServerWindow()->App()) 1569 return ServerWindow()->App()->Workspaces(); 1570 1571 return 0; 1572 } 1573 1574 if (fFeel == B_MODAL_APP_WINDOW_FEEL) { 1575 uint32 workspaces = ServerWindow()->App()->Workspaces(); 1576 if (workspaces == 0) { 1577 // The application doesn't seem to have any other windows 1578 // open or visible - but we'd like to see modal windows 1579 // anyway, at least when they are first opened. 1580 return 1UL << fDesktop->CurrentWorkspace(); 1581 } 1582 return workspaces; 1583 } 1584 1585 if (fFeel == B_MODAL_SUBSET_WINDOW_FEEL 1586 || fFeel == B_FLOATING_SUBSET_WINDOW_FEEL) { 1587 uint32 workspaces = 0; 1588 bool hasNormalFront = false; 1589 for (int32 i = 0; i < fSubsets.CountItems(); i++) { 1590 Window* window = fSubsets.ItemAt(i); 1591 1592 if (!window->IsHidden()) 1593 workspaces |= window->Workspaces(); 1594 if (window == fDesktop->FrontWindow() && window->IsNormal()) 1595 hasNormalFront = true; 1596 } 1597 1598 if (fFeel == B_FLOATING_SUBSET_WINDOW_FEEL && !hasNormalFront) 1599 return 0; 1600 1601 return workspaces; 1602 } 1603 1604 return 0; 1605 } 1606 1607 1608 // #pragma mark - static 1609 1610 1611 /*static*/ 1612 bool 1613 Window::IsValidLook(window_look look) 1614 { 1615 return look == B_TITLED_WINDOW_LOOK 1616 || look == B_DOCUMENT_WINDOW_LOOK 1617 || look == B_MODAL_WINDOW_LOOK 1618 || look == B_FLOATING_WINDOW_LOOK 1619 || look == B_BORDERED_WINDOW_LOOK 1620 || look == B_NO_BORDER_WINDOW_LOOK 1621 || look == kDesktopWindowLook 1622 || look == kLeftTitledWindowLook; 1623 } 1624 1625 1626 /*static*/ 1627 bool 1628 Window::IsValidFeel(window_feel feel) 1629 { 1630 return feel == B_NORMAL_WINDOW_FEEL 1631 || feel == B_MODAL_SUBSET_WINDOW_FEEL 1632 || feel == B_MODAL_APP_WINDOW_FEEL 1633 || feel == B_MODAL_ALL_WINDOW_FEEL 1634 || feel == B_FLOATING_SUBSET_WINDOW_FEEL 1635 || feel == B_FLOATING_APP_WINDOW_FEEL 1636 || feel == B_FLOATING_ALL_WINDOW_FEEL 1637 || feel == kDesktopWindowFeel 1638 || feel == kMenuWindowFeel 1639 || feel == kWindowScreenFeel; 1640 } 1641 1642 1643 /*static*/ 1644 bool 1645 Window::IsModalFeel(window_feel feel) 1646 { 1647 return feel == B_MODAL_SUBSET_WINDOW_FEEL 1648 || feel == B_MODAL_APP_WINDOW_FEEL 1649 || feel == B_MODAL_ALL_WINDOW_FEEL; 1650 } 1651 1652 1653 /*static*/ 1654 bool 1655 Window::IsFloatingFeel(window_feel feel) 1656 { 1657 return feel == B_FLOATING_SUBSET_WINDOW_FEEL 1658 || feel == B_FLOATING_APP_WINDOW_FEEL 1659 || feel == B_FLOATING_ALL_WINDOW_FEEL; 1660 } 1661 1662 1663 /*static*/ 1664 uint32 1665 Window::ValidWindowFlags() 1666 { 1667 return B_NOT_MOVABLE | B_NOT_CLOSABLE | B_NOT_ZOOMABLE 1668 | B_NOT_MINIMIZABLE | B_NOT_RESIZABLE 1669 | B_NOT_H_RESIZABLE | B_NOT_V_RESIZABLE 1670 | B_AVOID_FRONT | B_AVOID_FOCUS 1671 | B_WILL_ACCEPT_FIRST_CLICK | B_OUTLINE_RESIZE 1672 | B_NO_WORKSPACE_ACTIVATION 1673 | B_NOT_ANCHORED_ON_ACTIVATE 1674 | B_ASYNCHRONOUS_CONTROLS 1675 | B_QUIT_ON_WINDOW_CLOSE 1676 | B_SAME_POSITION_IN_ALL_WORKSPACES 1677 | kWindowScreenFlag; 1678 } 1679 1680 1681 /*static*/ 1682 uint32 1683 Window::ValidWindowFlags(window_feel feel) 1684 { 1685 uint32 flags = ValidWindowFlags(); 1686 if (IsModalFeel(feel)) 1687 return flags & ~(B_AVOID_FOCUS | B_AVOID_FRONT); 1688 1689 return flags; 1690 } 1691 1692 1693 // #pragma mark - private 1694 1695 1696 // _ShiftPartOfRegion 1697 void 1698 Window::_ShiftPartOfRegion(BRegion* region, BRegion* regionToShift, 1699 int32 xOffset, int32 yOffset) 1700 { 1701 BRegion* common = fRegionPool.GetRegion(*regionToShift); 1702 if (!common) 1703 return; 1704 // see if there is a common part at all 1705 common->IntersectWith(region); 1706 if (common->CountRects() > 0) { 1707 // cut the common part from the region, 1708 // offset that to destination and include again 1709 region->Exclude(common); 1710 common->OffsetBy(xOffset, yOffset); 1711 region->Include(common); 1712 } 1713 fRegionPool.Recycle(common); 1714 } 1715 1716 1717 void 1718 Window::_TriggerContentRedraw(BRegion& dirtyContentRegion) 1719 { 1720 if (IsVisible() && dirtyContentRegion.CountRects() > 0) { 1721 // put this into the pending dirty region 1722 // to eventually trigger a client redraw 1723 bool wasExpose = fPendingUpdateSession->IsExpose(); 1724 BRegion* backgroundClearingRegion = &dirtyContentRegion; 1725 1726 _TransferToUpdateSession(&dirtyContentRegion); 1727 1728 if (fPendingUpdateSession->IsExpose()) { 1729 if (!fContentRegionValid) 1730 _UpdateContentRegion(); 1731 1732 if (!wasExpose) { 1733 // there was suddenly added a dirty region 1734 // caused by exposing content, we need to clear 1735 // the entire background 1736 backgroundClearingRegion 1737 = &(fPendingUpdateSession->DirtyRegion()); 1738 } 1739 1740 if (fDrawingEngine->LockParallelAccess()) { 1741 bool copyToFrontEnabled = fDrawingEngine->CopyToFrontEnabled(); 1742 fDrawingEngine->SetCopyToFrontEnabled(true); 1743 fDrawingEngine->SuspendAutoSync(); 1744 1745 fTopView->Draw(fDrawingEngine, backgroundClearingRegion, 1746 &fContentRegion, true); 1747 1748 fDrawingEngine->Sync(); 1749 fDrawingEngine->SetCopyToFrontEnabled(copyToFrontEnabled); 1750 fDrawingEngine->UnlockParallelAccess(); 1751 } 1752 } 1753 } 1754 } 1755 1756 1757 void 1758 Window::_DrawBorder() 1759 { 1760 // this is executed in the window thread, but only 1761 // in respond to a REDRAW message having been received, the 1762 // clipping lock is held for reading 1763 1764 if (!fDecorator) 1765 return; 1766 1767 // construct the region of the border that needs redrawing 1768 BRegion* dirtyBorderRegion = fRegionPool.GetRegion(); 1769 if (!dirtyBorderRegion) 1770 return; 1771 GetBorderRegion(dirtyBorderRegion); 1772 // intersect with our visible region 1773 dirtyBorderRegion->IntersectWith(&fVisibleRegion); 1774 // intersect with the dirty region 1775 dirtyBorderRegion->IntersectWith(&fDirtyRegion); 1776 1777 DrawingEngine* engine = fDecorator->GetDrawingEngine(); 1778 if (dirtyBorderRegion->CountRects() > 0 && engine->LockParallelAccess()) { 1779 engine->ConstrainClippingRegion(dirtyBorderRegion); 1780 bool copyToFrontEnabled = engine->CopyToFrontEnabled(); 1781 engine->SetCopyToFrontEnabled(true); 1782 1783 fDecorator->Draw(dirtyBorderRegion->Frame()); 1784 1785 engine->SetCopyToFrontEnabled(copyToFrontEnabled); 1786 1787 // TODO: remove this once the DrawState stuff is handled 1788 // more cleanly. The reason why this is needed is that 1789 // when the decorator draws strings, a draw state is set 1790 // on the Painter object, and this is were it might get 1791 // out of sync with what the ServerWindow things is the 1792 // current DrawState set on the Painter 1793 fWindow->ResyncDrawState(); 1794 1795 engine->UnlockParallelAccess(); 1796 } 1797 fRegionPool.Recycle(dirtyBorderRegion); 1798 } 1799 1800 1801 //static rgb_color sPendingColor; 1802 //static rgb_color sCurrentColor; 1803 1804 /*! 1805 pre: the clipping is readlocked (this function is 1806 only called from _TriggerContentRedraw()), which 1807 in turn is only called from MessageReceived() with 1808 the clipping lock held 1809 */ 1810 void 1811 Window::_TransferToUpdateSession(BRegion* contentDirtyRegion) 1812 { 1813 if (contentDirtyRegion->CountRects() <= 0) 1814 return; 1815 1816 //fDrawingEngine->FillRegion(*contentDirtyRegion, sPendingColor); 1817 //snooze(10000); 1818 1819 // add to pending 1820 fPendingUpdateSession->SetUsed(true); 1821 // if (!fPendingUpdateSession->IsExpose()) 1822 fPendingUpdateSession->AddCause(fDirtyCause); 1823 fPendingUpdateSession->Include(contentDirtyRegion); 1824 1825 if (!fUpdateRequested) { 1826 // send this to client 1827 _SendUpdateMessage(); 1828 // the pending region is now the current, 1829 // though the update does not start until 1830 // we received BEGIN_UPDATE from the client 1831 } 1832 } 1833 1834 // _SendUpdateMessage 1835 void 1836 Window::_SendUpdateMessage() 1837 { 1838 if (!fUpdatesEnabled) 1839 return; 1840 1841 BMessage message(_UPDATE_); 1842 ServerWindow()->SendMessageToClient(&message); 1843 1844 fUpdateRequested = true; 1845 fEffectiveDrawingRegionValid = false; 1846 } 1847 1848 1849 void 1850 Window::BeginUpdate(BPrivate::PortLink& link) 1851 { 1852 // NOTE: since we might "shift" parts of the 1853 // internal dirty regions from the desktop thread 1854 // in response to Window::ResizeBy(), which 1855 // might move arround views, the user of this function 1856 // needs to hold the global clipping lock so that the internal 1857 // dirty regions are not messed with from the Desktop thread 1858 // and ServerWindow thread at the same time. 1859 1860 if (!fUpdateRequested) { 1861 link.StartMessage(B_ERROR); 1862 link.Flush(); 1863 fprintf(stderr, "Window::BeginUpdate() - no update requested!\n"); 1864 return; 1865 } 1866 1867 // make the pending update session the current update session 1868 // (toggle the pointers) 1869 UpdateSession* temp = fCurrentUpdateSession; 1870 fCurrentUpdateSession = fPendingUpdateSession; 1871 fPendingUpdateSession = temp; 1872 fPendingUpdateSession->SetUsed(false); 1873 // all drawing command from the client 1874 // will have the dirty region from the update 1875 // session enforced 1876 fInUpdate = true; 1877 fEffectiveDrawingRegionValid = false; 1878 1879 // TODO: each view could be drawn individually 1880 // right before carrying out the first drawing 1881 // command from the client during an update 1882 // (View::IsBackgroundDirty() can be used 1883 // for this) 1884 if (!fContentRegionValid) 1885 _UpdateContentRegion(); 1886 1887 BRegion* dirty = fRegionPool.GetRegion( 1888 fCurrentUpdateSession->DirtyRegion()); 1889 if (!dirty) { 1890 link.StartMessage(B_ERROR); 1891 link.Flush(); 1892 return; 1893 } 1894 1895 dirty->IntersectWith(&VisibleContentRegion()); 1896 1897 //sCurrentColor.red = rand() % 255; 1898 //sCurrentColor.green = rand() % 255; 1899 //sCurrentColor.blue = rand() % 255; 1900 //sPendingColor.red = rand() % 255; 1901 //sPendingColor.green = rand() % 255; 1902 //sPendingColor.blue = rand() % 255; 1903 //fDrawingEngine->FillRegion(*dirty, sCurrentColor); 1904 //snooze(10000); 1905 1906 link.StartMessage(B_OK); 1907 // append the current window geometry to the 1908 // message, the client will need it 1909 link.Attach<BPoint>(fFrame.LeftTop()); 1910 link.Attach<float>(fFrame.Width()); 1911 link.Attach<float>(fFrame.Height()); 1912 // append he update rect in screen coords 1913 link.Attach<BRect>(dirty->Frame()); 1914 // find and attach all views that intersect with 1915 // the dirty region 1916 fTopView->AddTokensForViewsInRegion(link, *dirty, &fContentRegion); 1917 // mark the end of the token "list" 1918 link.Attach<int32>(B_NULL_TOKEN); 1919 link.Flush(); 1920 1921 // supress back to front buffer copies in the drawing engine 1922 fDrawingEngine->SetCopyToFrontEnabled(false); 1923 1924 if (!fCurrentUpdateSession->IsExpose() && fDrawingEngine->LockParallelAccess()) { 1925 //fDrawingEngine->FillRegion(dirty, (rgb_color){ 255, 0, 0, 255 }); 1926 fDrawingEngine->SuspendAutoSync(); 1927 1928 fTopView->Draw(fDrawingEngine, dirty, &fContentRegion, true); 1929 1930 fDrawingEngine->Sync(); 1931 fDrawingEngine->UnlockParallelAccess(); 1932 } // else the background was cleared already 1933 1934 fRegionPool.Recycle(dirty); 1935 } 1936 1937 1938 void 1939 Window::EndUpdate() 1940 { 1941 // NOTE: see comment in _BeginUpdate() 1942 1943 if (fInUpdate) { 1944 // reenable copy to front 1945 fDrawingEngine->SetCopyToFrontEnabled(true); 1946 1947 BRegion* dirty = fRegionPool.GetRegion( 1948 fCurrentUpdateSession->DirtyRegion()); 1949 1950 if (dirty) { 1951 dirty->IntersectWith(&VisibleContentRegion()); 1952 1953 fDrawingEngine->CopyToFront(*dirty); 1954 fRegionPool.Recycle(dirty); 1955 } 1956 1957 fCurrentUpdateSession->SetUsed(false); 1958 1959 fInUpdate = false; 1960 fEffectiveDrawingRegionValid = false; 1961 } 1962 if (fPendingUpdateSession->IsUsed()) { 1963 // send this to client 1964 _SendUpdateMessage(); 1965 } else { 1966 fUpdateRequested = false; 1967 } 1968 } 1969 1970 1971 void 1972 Window::_UpdateContentRegion() 1973 { 1974 fContentRegion.Set(fFrame); 1975 1976 // resize handle 1977 if (fDecorator) { 1978 if (!fBorderRegionValid) 1979 GetBorderRegion(&fBorderRegion); 1980 1981 fContentRegion.Exclude(&fBorderRegion); 1982 } 1983 1984 fContentRegionValid = true; 1985 } 1986 1987 1988 click_type 1989 Window::_ActionFor(const BMessage* msg) const 1990 { 1991 if (fDecorator == NULL) 1992 return DEC_NONE; 1993 1994 BPoint where; 1995 if (msg->FindPoint("where", &where) != B_OK) 1996 return DEC_NONE; 1997 1998 int32 buttons; 1999 if (msg->FindInt32("buttons", &buttons) != B_OK) 2000 buttons = 0; 2001 2002 int32 modifiers; 2003 if (msg->FindInt32("modifiers", &modifiers) != B_OK) 2004 modifiers = 0; 2005 2006 return fDecorator->Clicked(where, buttons, modifiers); 2007 } 2008 2009 2010 void 2011 Window::_ObeySizeLimits() 2012 { 2013 // make sure we even have valid size limits 2014 if (fMaxWidth < fMinWidth) 2015 fMaxWidth = fMinWidth; 2016 2017 if (fMaxHeight < fMinHeight) 2018 fMaxHeight = fMinHeight; 2019 2020 // Automatically resize the window to fit these new limits 2021 // if it does not already. 2022 2023 // On R5, Windows don't automatically resize, but since 2024 // BWindow::ResizeTo() even honors the limits, I would guess 2025 // this is a bug that we don't have to adopt. 2026 // Note that most current apps will do unnecessary resizing 2027 // after having set the limits, but the overhead is neglible. 2028 2029 float minWidthDiff = fMinWidth - fFrame.Width(); 2030 float minHeightDiff = fMinHeight - fFrame.Height(); 2031 float maxWidthDiff = fMaxWidth - fFrame.Width(); 2032 float maxHeightDiff = fMaxHeight - fFrame.Height(); 2033 2034 float xDiff = 0.0; 2035 if (minWidthDiff > 0.0) // we're currently smaller than minWidth 2036 xDiff = minWidthDiff; 2037 else if (maxWidthDiff < 0.0) // we're currently larger than maxWidth 2038 xDiff = maxWidthDiff; 2039 2040 float yDiff = 0.0; 2041 if (minHeightDiff > 0.0) // we're currently smaller than minHeight 2042 yDiff = minHeightDiff; 2043 else if (maxHeightDiff < 0.0) // we're currently larger than maxHeight 2044 yDiff = maxHeightDiff; 2045 2046 if (fDesktop) 2047 fDesktop->ResizeWindowBy(this, xDiff, yDiff); 2048 else 2049 ResizeBy(xDiff, yDiff, NULL); 2050 } 2051 2052 2053 // #pragma mark - UpdateSession 2054 2055 2056 Window::UpdateSession::UpdateSession() 2057 : fDirtyRegion(), 2058 fInUse(false), 2059 fCause(0) 2060 { 2061 } 2062 2063 2064 Window::UpdateSession::~UpdateSession() 2065 { 2066 } 2067 2068 2069 void 2070 Window::UpdateSession::Include(BRegion* additionalDirty) 2071 { 2072 fDirtyRegion.Include(additionalDirty); 2073 } 2074 2075 2076 void 2077 Window::UpdateSession::Exclude(BRegion* dirtyInNextSession) 2078 { 2079 fDirtyRegion.Exclude(dirtyInNextSession); 2080 } 2081 2082 2083 void 2084 Window::UpdateSession::MoveBy(int32 x, int32 y) 2085 { 2086 fDirtyRegion.OffsetBy(x, y); 2087 } 2088 2089 2090 void 2091 Window::UpdateSession::SetUsed(bool used) 2092 { 2093 fInUse = used; 2094 if (!fInUse) { 2095 fDirtyRegion.MakeEmpty(); 2096 fCause = 0; 2097 } 2098 } 2099 2100 2101 void 2102 Window::UpdateSession::AddCause(uint8 cause) 2103 { 2104 fCause |= cause; 2105 } 2106 2107 2108