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