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