/* * Copyright 2001-2020, Haiku, Inc. * Distributed under the terms of the MIT license. * * Authors: * DarkWyrm, bpmagic@columbus.rr.com * Adi Oanca, adioanca@gmail.com * Stephan Aßmus, superstippi@gmx.de * Axel Dörfler, axeld@pinc-software.de * Brecht Machiels, brecht@mos6581.org * Clemens Zeidler, haiku@clemens-zeidler.de * Tri-Edge AI * Jacob Secunda, secundja@gmail.com */ #include "Window.h" #include #include #include #include #include #include #include #include #include "ClickTarget.h" #include "Decorator.h" #include "DecorManager.h" #include "Desktop.h" #include "DrawingEngine.h" #include "HWInterface.h" #include "MessagePrivate.h" #include "PortLink.h" #include "ServerApp.h" #include "ServerWindow.h" #include "WindowBehaviour.h" #include "Workspace.h" #include "WorkspacesView.h" // Toggle debug output //#define DEBUG_WINDOW #ifdef DEBUG_WINDOW # define STRACE(x) printf x #else # define STRACE(x) ; #endif // IMPORTANT: nested LockSingleWindow()s are not supported (by MultiLocker) using std::nothrow; // if the background clearing is delayed until // the client draws the view, we have less flickering // when contents have to be redrawn because of resizing // a window or because the client invalidates parts. // when redrawing something that has been exposed from underneath // other windows, the other window will be seen longer at // its previous position though if the exposed parts are not // cleared right away. maybe there ought to be a flag in // the update session, which tells us the cause of the update //static rgb_color sPendingColor = (rgb_color){ 255, 255, 0, 255 }; //static rgb_color sCurrentColor = (rgb_color){ 255, 0, 255, 255 }; Window::Window(const BRect& frame, const char *name, window_look look, window_feel feel, uint32 flags, uint32 workspaces, ::ServerWindow* window, DrawingEngine* drawingEngine) : fTitle(name), fFrame(frame), fScreen(NULL), fVisibleRegion(), fVisibleContentRegion(), fDirtyRegion(), fContentRegion(), fEffectiveDrawingRegion(), fVisibleContentRegionValid(false), fContentRegionValid(false), fEffectiveDrawingRegionValid(false), fRegionPool(), fWindow(window), fDrawingEngine(drawingEngine), fDesktop(window->Desktop()), fCurrentUpdateSession(&fUpdateSessions[0]), fPendingUpdateSession(&fUpdateSessions[1]), fUpdateRequested(false), fInUpdate(false), fUpdatesEnabled(false), // Windows start hidden fHidden(true), // Hidden is 1 or more fShowLevel(1), fMinimized(false), fIsFocus(false), fLook(look), fFeel(feel), fWorkspaces(workspaces), fCurrentWorkspace(-1), fMinWidth(1), fMaxWidth(32768), fMinHeight(1), fMaxHeight(32768), fWorkspacesViewCount(0) { _InitWindowStack(); // make sure our arguments are valid if (!IsValidLook(fLook)) fLook = B_TITLED_WINDOW_LOOK; if (!IsValidFeel(fFeel)) fFeel = B_NORMAL_WINDOW_FEEL; SetFlags(flags, NULL); if (fLook != B_NO_BORDER_WINDOW_LOOK && fCurrentStack.IsSet()) { // allocates a decorator ::Decorator* decorator = Decorator(); if (decorator != NULL) { decorator->GetSizeLimits(&fMinWidth, &fMinHeight, &fMaxWidth, &fMaxHeight); } } if (fFeel != kOffscreenWindowFeel) fWindowBehaviour.SetTo(gDecorManager.AllocateWindowBehaviour(this)); // do we need to change our size to let the decorator fit? // _ResizeBy() will adapt the frame for validity before resizing if (feel == kDesktopWindowFeel) { // the desktop window spans over the whole screen // TODO: this functionality should be moved somewhere else // (so that it is always used when the workspace is changed) uint16 width, height; uint32 colorSpace; float frequency; if (Screen() != NULL) { Screen()->GetMode(width, height, colorSpace, frequency); // TODO: MOVE THIS AWAY!!! ResizeBy contains calls to virtual methods! // Also, there is no TopView()! fFrame.OffsetTo(B_ORIGIN); // ResizeBy(width - frame.Width(), height - frame.Height(), NULL); } } STRACE(("Window %p, %s:\n", this, Name())); STRACE(("\tFrame: (%.1f, %.1f, %.1f, %.1f)\n", fFrame.left, fFrame.top, fFrame.right, fFrame.bottom)); STRACE(("\tWindow %s\n", window ? window->Title() : "NULL")); } Window::~Window() { if (fTopView.IsSet()) { fTopView->DetachedFromWindow(); } DetachFromWindowStack(false); gDecorManager.CleanupForWindow(this); } status_t Window::InitCheck() const { if (GetDrawingEngine() == NULL || (fFeel != kOffscreenWindowFeel && !fWindowBehaviour.IsSet())) return B_NO_MEMORY; // TODO: anything else? return B_OK; } void Window::SetClipping(BRegion* stillAvailableOnScreen) { // this function is only called from the Desktop thread // start from full region (as if the window was fully visible) GetFullRegion(&fVisibleRegion); // clip to region still available on screen fVisibleRegion.IntersectWith(stillAvailableOnScreen); fVisibleContentRegionValid = false; fEffectiveDrawingRegionValid = false; } void Window::GetFullRegion(BRegion* region) { // TODO: if someone needs to call this from // the outside, the clipping needs to be readlocked! // start from the decorator border, extend to use the frame GetBorderRegion(region); region->Include(fFrame); } void Window::GetBorderRegion(BRegion* region) { // TODO: if someone needs to call this from // the outside, the clipping needs to be readlocked! ::Decorator* decorator = Decorator(); if (decorator) *region = decorator->GetFootprint(); else region->MakeEmpty(); } void Window::GetContentRegion(BRegion* region) { // TODO: if someone needs to call this from // the outside, the clipping needs to be readlocked! if (!fContentRegionValid) { _UpdateContentRegion(); } *region = fContentRegion; } BRegion& Window::VisibleContentRegion() { // TODO: if someone needs to call this from // the outside, the clipping needs to be readlocked! // regions expected to be locked if (!fVisibleContentRegionValid) { GetContentRegion(&fVisibleContentRegion); fVisibleContentRegion.IntersectWith(&fVisibleRegion); } return fVisibleContentRegion; } // #pragma mark - void Window::_PropagatePosition() { if ((fFlags & B_SAME_POSITION_IN_ALL_WORKSPACES) == 0) return; for (int32 i = 0; i < kListCount; i++) { Anchor(i).position = fFrame.LeftTop(); } } void Window::MoveBy(int32 x, int32 y, bool moveStack) { // this function is only called from the desktop thread if (x == 0 && y == 0) return; fFrame.OffsetBy(x, y); _PropagatePosition(); // take along the dirty region which is not // processed yet fDirtyRegion.OffsetBy(x, y); fExposeRegion.OffsetBy(x, y); if (fContentRegionValid) fContentRegion.OffsetBy(x, y); if (fCurrentUpdateSession->IsUsed()) fCurrentUpdateSession->MoveBy(x, y); if (fPendingUpdateSession->IsUsed()) fPendingUpdateSession->MoveBy(x, y); fEffectiveDrawingRegionValid = false; if (fTopView.IsSet()) { fTopView->MoveBy(x, y, NULL); fTopView->UpdateOverlay(); } ::Decorator* decorator = Decorator(); if (moveStack && decorator) decorator->MoveBy(x, y); WindowStack* stack = GetWindowStack(); if (moveStack && stack) { for (int32 i = 0; i < stack->CountWindows(); i++) { Window* window = stack->WindowList().ItemAt(i); if (window == this) continue; window->MoveBy(x, y, false); } } // the desktop will take care of dirty regions // dispatch a message to the client informing about the changed size BMessage msg(B_WINDOW_MOVED); msg.AddInt64("when", system_time()); msg.AddPoint("where", fFrame.LeftTop()); fWindow->SendMessageToClient(&msg); } void Window::ResizeBy(int32 x, int32 y, BRegion* dirtyRegion, bool resizeStack) { // this function is only called from the desktop thread int32 wantWidth = fFrame.IntegerWidth() + x; int32 wantHeight = fFrame.IntegerHeight() + y; // enforce size limits WindowStack* stack = GetWindowStack(); if (resizeStack && stack) { for (int32 i = 0; i < stack->CountWindows(); i++) { Window* window = stack->WindowList().ItemAt(i); if (wantWidth < window->fMinWidth) wantWidth = window->fMinWidth; if (wantWidth > window->fMaxWidth) wantWidth = window->fMaxWidth; if (wantHeight < window->fMinHeight) wantHeight = window->fMinHeight; if (wantHeight > window->fMaxHeight) wantHeight = window->fMaxHeight; } } x = wantWidth - fFrame.IntegerWidth(); y = wantHeight - fFrame.IntegerHeight(); if (x == 0 && y == 0) return; fFrame.right += x; fFrame.bottom += y; fContentRegionValid = false; fEffectiveDrawingRegionValid = false; if (fTopView.IsSet()) { fTopView->ResizeBy(x, y, dirtyRegion); fTopView->UpdateOverlay(); } ::Decorator* decorator = Decorator(); if (decorator && resizeStack) decorator->ResizeBy(x, y, dirtyRegion); if (resizeStack && stack) { for (int32 i = 0; i < stack->CountWindows(); i++) { Window* window = stack->WindowList().ItemAt(i); if (window == this) continue; window->ResizeBy(x, y, dirtyRegion, false); } } // send a message to the client informing about the changed size BRect frame(Frame()); BMessage msg(B_WINDOW_RESIZED); msg.AddInt64("when", system_time()); msg.AddInt32("width", frame.IntegerWidth()); msg.AddInt32("height", frame.IntegerHeight()); fWindow->SendMessageToClient(&msg); } void Window::SetOutlinesDelta(BPoint delta, BRegion* dirtyRegion) { float wantWidth = fFrame.IntegerWidth() + delta.x; float wantHeight = fFrame.IntegerHeight() + delta.y; // enforce size limits WindowStack* stack = GetWindowStack(); if (stack != NULL) { for (int32 i = 0; i < stack->CountWindows(); i++) { Window* window = stack->WindowList().ItemAt(i); if (wantWidth < window->fMinWidth) wantWidth = window->fMinWidth; if (wantWidth > window->fMaxWidth) wantWidth = window->fMaxWidth; if (wantHeight < window->fMinHeight) wantHeight = window->fMinHeight; if (wantHeight > window->fMaxHeight) wantHeight = window->fMaxHeight; } delta.x = wantWidth - fFrame.IntegerWidth(); delta.y = wantHeight - fFrame.IntegerHeight(); } ::Decorator* decorator = Decorator(); if (decorator != NULL) decorator->SetOutlinesDelta(delta, dirtyRegion); _UpdateContentRegion(); } void Window::ScrollViewBy(View* view, int32 dx, int32 dy) { // this is executed in ServerWindow with the Readlock // held if (!view || view == fTopView.Get() || (dx == 0 && dy == 0)) return; BRegion* dirty = fRegionPool.GetRegion(); if (!dirty) return; view->ScrollBy(dx, dy, dirty); //fDrawingEngine->FillRegion(*dirty, (rgb_color){ 255, 0, 255, 255 }); //snooze(20000); if (!IsOffscreenWindow() && IsVisible() && view->IsVisible()) { dirty->IntersectWith(&VisibleContentRegion()); _TriggerContentRedraw(*dirty); } fRegionPool.Recycle(dirty); } //! Takes care of invalidating parts that could not be copied void Window::CopyContents(BRegion* region, int32 xOffset, int32 yOffset) { // executed in ServerWindow thread with the read lock held if (!IsVisible()) return; BRegion* newDirty = fRegionPool.GetRegion(*region); // clip the region to the visible contents at the // source and destination location (note that VisibleContentRegion() // is used once to make sure it is valid, then fVisibleContentRegion // is used directly) region->IntersectWith(&VisibleContentRegion()); if (region->CountRects() > 0) { // Constrain to content region at destination region->OffsetBy(xOffset, yOffset); region->IntersectWith(&fVisibleContentRegion); if (region->CountRects() > 0) { // if the region still contains any rects // offset to source location again region->OffsetBy(-xOffset, -yOffset); BRegion* allDirtyRegions = fRegionPool.GetRegion(fDirtyRegion); if (allDirtyRegions != NULL) { if (fPendingUpdateSession->IsUsed()) { allDirtyRegions->Include( &fPendingUpdateSession->DirtyRegion()); } if (fCurrentUpdateSession->IsUsed()) { allDirtyRegions->Include( &fCurrentUpdateSession->DirtyRegion()); } // Get just the part of the dirty regions which is semantically // copied along allDirtyRegions->IntersectWith(region); } BRegion* copyRegion = fRegionPool.GetRegion(*region); if (copyRegion != NULL) { // never copy what's already dirty if (allDirtyRegions != NULL) copyRegion->Exclude(allDirtyRegions); if (fDrawingEngine->LockParallelAccess()) { fDrawingEngine->CopyRegion(copyRegion, xOffset, yOffset); fDrawingEngine->UnlockParallelAccess(); // Prevent those parts from being added to the dirty region... newDirty->Exclude(copyRegion); // The parts that could be copied are not dirty (at the // target location!) copyRegion->OffsetBy(xOffset, yOffset); // ... and even exclude them from the pending dirty region! if (fPendingUpdateSession->IsUsed()) fPendingUpdateSession->DirtyRegion().Exclude(copyRegion); } fRegionPool.Recycle(copyRegion); } else { // Fallback, should never be here. if (fDrawingEngine->LockParallelAccess()) { fDrawingEngine->CopyRegion(region, xOffset, yOffset); fDrawingEngine->UnlockParallelAccess(); } } if (allDirtyRegions != NULL) fRegionPool.Recycle(allDirtyRegions); } } // what is left visible from the original region // at the destination after the region which could be // copied has been excluded, is considered dirty // NOTE: it may look like dirty regions are not moved // if no region could be copied, but that's alright, // since these parts will now be in newDirty anyways // (with the right offset) newDirty->OffsetBy(xOffset, yOffset); newDirty->IntersectWith(&fVisibleContentRegion); if (newDirty->CountRects() > 0) ProcessDirtyRegion(*newDirty); fRegionPool.Recycle(newDirty); } // #pragma mark - void Window::SetTopView(View* topView) { if (fTopView.IsSet()) { fTopView->DetachedFromWindow(); } fTopView.SetTo(topView); if (fTopView.IsSet()) { // the top view is special, it has a coordinate system // as if it was attached directly to the desktop, therefor, // the coordinate conversion through the view tree works // as expected, since the top view has no "parent" but has // fFrame as if it had // make sure the location of the top view on screen matches ours fTopView->MoveBy((int32)(fFrame.left - fTopView->Frame().left), (int32)(fFrame.top - fTopView->Frame().top), NULL); // make sure the size of the top view matches ours fTopView->ResizeBy((int32)(fFrame.Width() - fTopView->Frame().Width()), (int32)(fFrame.Height() - fTopView->Frame().Height()), NULL); fTopView->AttachedToWindow(this); } } View* Window::ViewAt(const BPoint& where) { return fTopView->ViewAt(where); } window_anchor& Window::Anchor(int32 index) { return fAnchor[index]; } Window* Window::NextWindow(int32 index) const { return fAnchor[index].next; } Window* Window::PreviousWindow(int32 index) const { return fAnchor[index].previous; } ::Decorator* Window::Decorator() const { if (!fCurrentStack.IsSet()) return NULL; return fCurrentStack->Decorator(); } bool Window::ReloadDecor() { ::Decorator* decorator = NULL; WindowBehaviour* windowBehaviour = NULL; WindowStack* stack = GetWindowStack(); if (stack == NULL) return false; // only reload the window at the first position if (stack->WindowAt(0) != this) return true; if (fLook != B_NO_BORDER_WINDOW_LOOK) { // we need a new decorator decorator = gDecorManager.AllocateDecorator(this); if (decorator == NULL) return false; // add all tabs to the decorator for (int32 i = 1; i < stack->CountWindows(); i++) { Window* window = stack->WindowAt(i); BRegion dirty; DesktopSettings settings(fDesktop); if (decorator->AddTab(settings, window->Title(), window->Look(), window->Flags(), -1, &dirty) == NULL) { delete decorator; return false; } } } else return true; windowBehaviour = gDecorManager.AllocateWindowBehaviour(this); if (windowBehaviour == NULL) { delete decorator; return false; } stack->SetDecorator(decorator); fWindowBehaviour.SetTo(windowBehaviour); // set the correct focus and top layer tab for (int32 i = 0; i < stack->CountWindows(); i++) { Window* window = stack->WindowAt(i); if (window->IsFocus()) decorator->SetFocus(i, true); if (window == stack->TopLayerWindow()) decorator->SetTopTab(i); } return true; } void Window::SetScreen(const ::Screen* screen) { // TODO this assert fails in Desktop::ShowWindow //ASSERT_MULTI_WRITE_LOCKED(fDesktop->ScreenLocker()); fScreen = screen; } const ::Screen* Window::Screen() const { // TODO this assert also fails //ASSERT_MULTI_READ_LOCKED(fDesktop->ScreenLocker()); return fScreen; } // #pragma mark - void Window::GetEffectiveDrawingRegion(View* view, BRegion& region) { if (!fEffectiveDrawingRegionValid) { fEffectiveDrawingRegion = VisibleContentRegion(); if (fUpdateRequested && !fInUpdate) { // We requested an update, but the client has not started it yet, // so it is only allowed to draw outside the pending update sessions // region fEffectiveDrawingRegion.Exclude( &fPendingUpdateSession->DirtyRegion()); } else if (fInUpdate) { // enforce the dirty region of the update session fEffectiveDrawingRegion.IntersectWith( &fCurrentUpdateSession->DirtyRegion()); } else { // not in update, the view can draw everywhere //printf("Window(%s)::GetEffectiveDrawingRegion(for %s) - outside update\n", Title(), view->Name()); } fEffectiveDrawingRegionValid = true; } // TODO: this is a region that needs to be cached later in the server // when the current view in ServerWindow is set, and we are currently // in an update (fInUpdate), than we can set this region and remember // it for the comming drawing commands until the current view changes // again or fEffectiveDrawingRegionValid is suddenly false. region = fEffectiveDrawingRegion; if (!fContentRegionValid) _UpdateContentRegion(); region.IntersectWith(&view->ScreenAndUserClipping(&fContentRegion)); } bool Window::DrawingRegionChanged(View* view) const { return !fEffectiveDrawingRegionValid || !view->IsScreenClippingValid(); } void Window::ProcessDirtyRegion(const BRegion& dirtyRegion, const BRegion& exposeRegion) { // if this is executed in the desktop thread, // it means that the window thread currently // blocks to get the read lock, if it is // executed from the window thread, it should // have the read lock and the desktop thread // is blocking to get the write lock. IAW, this // is only executed in one thread. if (fDirtyRegion.CountRects() == 0) { // the window needs to be informed // when the dirty region was empty. // NOTE: when the window thread has processed // the dirty region in MessageReceived(), // it will make the region empty again, // when it is empty here, we need to send // the message to initiate the next update round. // Until the message is processed in the window // thread, the desktop thread can add parts to // the region as it likes. ServerWindow()->RequestRedraw(); } fDirtyRegion.Include(&dirtyRegion); fExposeRegion.Include(&exposeRegion); } void Window::RedrawDirtyRegion() { if (TopLayerStackWindow() != this) { fDirtyRegion.MakeEmpty(); fExposeRegion.MakeEmpty(); return; } // executed from ServerWindow with the read lock held if (IsVisible()) { _DrawBorder(); BRegion* dirtyContentRegion = fRegionPool.GetRegion(VisibleContentRegion()); BRegion* exposeContentRegion = fRegionPool.GetRegion(VisibleContentRegion()); dirtyContentRegion->IntersectWith(&fDirtyRegion); exposeContentRegion->IntersectWith(&fExposeRegion); _TriggerContentRedraw(*dirtyContentRegion, *exposeContentRegion); fRegionPool.Recycle(dirtyContentRegion); fRegionPool.Recycle(exposeContentRegion); } // reset the dirty region, since // we're fully clean. If the desktop // thread wanted to mark something // dirty in the mean time, it was // blocking on the global region lock to // get write access, since we're holding // the read lock for the whole time. fDirtyRegion.MakeEmpty(); fExposeRegion.MakeEmpty(); } void Window::MarkDirty(BRegion& regionOnScreen) { // for marking any part of the desktop dirty // this will get write access to the global // region lock, and result in ProcessDirtyRegion() // to be called for any windows affected if (fDesktop) fDesktop->MarkDirty(regionOnScreen); } void Window::MarkContentDirty(BRegion& dirtyRegion, BRegion& exposeRegion) { // for triggering AS_REDRAW // since this won't affect other windows, read locking // is sufficient. If there was no dirty region before, // an update message is triggered if (fHidden || IsOffscreenWindow()) return; dirtyRegion.IntersectWith(&VisibleContentRegion()); exposeRegion.IntersectWith(&VisibleContentRegion()); _TriggerContentRedraw(dirtyRegion, exposeRegion); } void Window::MarkContentDirtyAsync(BRegion& dirtyRegion) { // NOTE: see comments in ProcessDirtyRegion() if (fHidden || IsOffscreenWindow()) return; dirtyRegion.IntersectWith(&VisibleContentRegion()); if (fDirtyRegion.CountRects() == 0) { ServerWindow()->RequestRedraw(); } fDirtyRegion.Include(&dirtyRegion); } void Window::InvalidateView(View* view, BRegion& viewRegion) { if (view && IsVisible() && view->IsVisible()) { if (!fContentRegionValid) _UpdateContentRegion(); view->LocalToScreenTransform().Apply(&viewRegion); viewRegion.IntersectWith(&VisibleContentRegion()); if (viewRegion.CountRects() > 0) { viewRegion.IntersectWith( &view->ScreenAndUserClipping(&fContentRegion)); //fDrawingEngine->FillRegion(viewRegion, rgb_color{ 0, 255, 0, 255 }); //snooze(10000); _TriggerContentRedraw(viewRegion); } } } // DisableUpdateRequests void Window::DisableUpdateRequests() { fUpdatesEnabled = false; } // EnableUpdateRequests void Window::EnableUpdateRequests() { fUpdatesEnabled = true; if (!fUpdateRequested && fPendingUpdateSession->IsUsed()) _SendUpdateMessage(); } // #pragma mark - /*! \brief Handles a mouse-down message for the window. \param message The message. \param where The point where the mouse click happened. \param lastClickTarget The target of the previous click. \param clickCount The number of subsequent, no longer than double-click interval separated clicks that have happened so far. This number doesn't necessarily match the value in the message. It has already been pre-processed in order to avoid erroneous multi-clicks (e.g. when a different button has been used or a different window was targeted). This is an in-out variable. The method can reset the value to 1, if it doesn't want this event handled as a multi-click. Returning a different click target will also make the caller reset the click count. \param _clickTarget Set by the method to a value identifying the clicked element. If not explicitly set, an invalid click target is assumed. */ void Window::MouseDown(BMessage* message, BPoint where, const ClickTarget& lastClickTarget, int32& clickCount, ClickTarget& _clickTarget) { // If the previous click hit our decorator, get the hit region. int32 windowToken = fWindow->ServerToken(); int32 lastHitRegion = 0; if (lastClickTarget.GetType() == ClickTarget::TYPE_WINDOW_DECORATOR && lastClickTarget.WindowToken() == windowToken) { lastHitRegion = lastClickTarget.WindowElement(); } // Let the window behavior process the mouse event. int32 hitRegion = 0; bool eventEaten = fWindowBehaviour->MouseDown(message, where, lastHitRegion, clickCount, hitRegion); if (eventEaten) { // click on the decorator (or equivalent) _clickTarget = ClickTarget(ClickTarget::TYPE_WINDOW_DECORATOR, windowToken, (int32)hitRegion); } else { // click was inside the window contents int32 viewToken = B_NULL_TOKEN; if (View* view = ViewAt(where)) { if (HasModal()) return; // clicking a simple View if (!IsFocus()) { bool acceptFirstClick = (Flags() & B_WILL_ACCEPT_FIRST_CLICK) != 0; // Activate or focus the window in case it doesn't accept first // click, depending on the mouse mode if (!acceptFirstClick) { bool avoidFocus = (Flags() & B_AVOID_FOCUS) != 0; DesktopSettings desktopSettings(fDesktop); if (desktopSettings.MouseMode() == B_NORMAL_MOUSE) fDesktop->ActivateWindow(this); else if (!avoidFocus) fDesktop->SetFocusWindow(this); // Eat the click if we don't accept first click // (B_AVOID_FOCUS never gets the focus, so they always accept // the first click) // TODO: the latter is unlike BeOS - if we really wanted to // imitate this behaviour, we would need to check if we're // the front window instead of the focus window if (!desktopSettings.AcceptFirstClick() && !avoidFocus) return; } } // fill out view token for the view under the mouse viewToken = view->Token(); view->MouseDown(message, where); } _clickTarget = ClickTarget(ClickTarget::TYPE_WINDOW_CONTENTS, windowToken, viewToken); } } void Window::MouseUp(BMessage* message, BPoint where, int32* _viewToken) { fWindowBehaviour->MouseUp(message, where); if (View* view = ViewAt(where)) { if (HasModal()) return; *_viewToken = view->Token(); view->MouseUp(message, where); } } void Window::MouseMoved(BMessage *message, BPoint where, int32* _viewToken, bool isLatestMouseMoved, bool isFake) { View* view = ViewAt(where); if (view != NULL) *_viewToken = view->Token(); // ignore pointer history if (!isLatestMouseMoved) return; fWindowBehaviour->MouseMoved(message, where, isFake); // mouse cursor if (view != NULL) { view->MouseMoved(message, where); // TODO: there is more for real cursor support, ie. if a window is closed, // new app cursor shouldn't override view cursor, ... ServerWindow()->App()->SetCurrentCursor(view->Cursor()); } } void Window::ModifiersChanged(int32 modifiers) { fWindowBehaviour->ModifiersChanged(modifiers); } // #pragma mark - void Window::WorkspaceActivated(int32 index, bool active) { BMessage activatedMsg(B_WORKSPACE_ACTIVATED); activatedMsg.AddInt64("when", system_time()); activatedMsg.AddInt32("workspace", index); activatedMsg.AddBool("active", active); ServerWindow()->SendMessageToClient(&activatedMsg); } void Window::WorkspacesChanged(uint32 oldWorkspaces, uint32 newWorkspaces) { fWorkspaces = newWorkspaces; BMessage changedMsg(B_WORKSPACES_CHANGED); changedMsg.AddInt64("when", system_time()); changedMsg.AddInt32("old", oldWorkspaces); changedMsg.AddInt32("new", newWorkspaces); ServerWindow()->SendMessageToClient(&changedMsg); } void Window::Activated(bool active) { BMessage msg(B_WINDOW_ACTIVATED); msg.AddBool("active", active); ServerWindow()->SendMessageToClient(&msg); } //# pragma mark - void Window::SetTitle(const char* name, BRegion& dirty) { // rebuild the clipping for the title area // and redraw it. fTitle = name; ::Decorator* decorator = Decorator(); if (decorator) { int32 index = PositionInStack(); decorator->SetTitle(index, name, &dirty); } } void Window::SetFocus(bool focus) { ::Decorator* decorator = Decorator(); // executed from Desktop thread // it holds the clipping write lock, // so the window thread cannot be // accessing fIsFocus BRegion* dirty = NULL; if (decorator) dirty = fRegionPool.GetRegion(decorator->GetFootprint()); if (dirty) { dirty->IntersectWith(&fVisibleRegion); fDesktop->MarkDirty(*dirty); fRegionPool.Recycle(dirty); } fIsFocus = focus; if (decorator) { int32 index = PositionInStack(); decorator->SetFocus(index, focus); } Activated(focus); } void Window::SetHidden(bool hidden) { // the desktop takes care of dirty regions if (fHidden != hidden) { fHidden = hidden; fTopView->SetHidden(hidden); // TODO: anything else? } } void Window::SetShowLevel(int32 showLevel) { if (showLevel == fShowLevel) return; fShowLevel = showLevel; } void Window::SetMinimized(bool minimized) { if (minimized == fMinimized) return; fMinimized = minimized; } bool Window::IsVisible() const { if (IsOffscreenWindow()) return true; if (IsHidden()) return false; /* if (fVisibleRegion.CountRects() == 0) return false; */ return fCurrentWorkspace >= 0 && fCurrentWorkspace < kWorkingList; } bool Window::IsDragging() const { if (!fWindowBehaviour.IsSet()) return false; return fWindowBehaviour->IsDragging(); } bool Window::IsResizing() const { if (!fWindowBehaviour.IsSet()) return false; return fWindowBehaviour->IsResizing(); } void Window::SetSizeLimits(int32 minWidth, int32 maxWidth, int32 minHeight, int32 maxHeight) { if (minWidth < 0) minWidth = 0; if (minHeight < 0) minHeight = 0; fMinWidth = minWidth; fMaxWidth = maxWidth; fMinHeight = minHeight; fMaxHeight = maxHeight; // give the Decorator a say in this too ::Decorator* decorator = Decorator(); if (decorator) { decorator->GetSizeLimits(&fMinWidth, &fMinHeight, &fMaxWidth, &fMaxHeight); } _ObeySizeLimits(); } void Window::GetSizeLimits(int32* minWidth, int32* maxWidth, int32* minHeight, int32* maxHeight) const { *minWidth = fMinWidth; *maxWidth = fMaxWidth; *minHeight = fMinHeight; *maxHeight = fMaxHeight; } bool Window::SetTabLocation(float location, bool isShifting, BRegion& dirty) { ::Decorator* decorator = Decorator(); if (decorator) { int32 index = PositionInStack(); return decorator->SetTabLocation(index, location, isShifting, &dirty); } return false; } float Window::TabLocation() const { ::Decorator* decorator = Decorator(); if (decorator) { int32 index = PositionInStack(); return decorator->TabLocation(index); } return 0.0; } bool Window::SetDecoratorSettings(const BMessage& settings, BRegion& dirty) { if (settings.what == 'prVu') { // 'prVu' == preview a decorator! BString path; if (settings.FindString("preview", &path) == B_OK) return gDecorManager.PreviewDecorator(path, this) == B_OK; return false; } ::Decorator* decorator = Decorator(); if (decorator) return decorator->SetSettings(settings, &dirty); return false; } bool Window::GetDecoratorSettings(BMessage* settings) { if (fDesktop) fDesktop->GetDecoratorSettings(this, *settings); ::Decorator* decorator = Decorator(); if (decorator) return decorator->GetSettings(settings); return false; } void Window::FontsChanged(BRegion* updateRegion) { ::Decorator* decorator = Decorator(); if (decorator != NULL) { DesktopSettings settings(fDesktop); decorator->FontsChanged(settings, updateRegion); } } void Window::ColorsChanged(BRegion* updateRegion) { ::Decorator* decorator = Decorator(); if (decorator != NULL) { DesktopSettings settings(fDesktop); decorator->ColorsChanged(settings, updateRegion); } } void Window::SetLook(window_look look, BRegion* updateRegion) { fLook = look; fContentRegionValid = false; // mabye a resize handle was added... fEffectiveDrawingRegionValid = false; // ...and therefor the drawing region is // likely not valid anymore either if (!fCurrentStack.IsSet()) return; int32 stackPosition = PositionInStack(); ::Decorator* decorator = Decorator(); if (decorator == NULL && look != B_NO_BORDER_WINDOW_LOOK) { // we need a new decorator decorator = gDecorManager.AllocateDecorator(this); fCurrentStack->SetDecorator(decorator); if (IsFocus()) decorator->SetFocus(stackPosition, true); } if (decorator != NULL) { DesktopSettings settings(fDesktop); decorator->SetLook(stackPosition, settings, look, updateRegion); // we might need to resize the window! decorator->GetSizeLimits(&fMinWidth, &fMinHeight, &fMaxWidth, &fMaxHeight); _ObeySizeLimits(); } if (look == B_NO_BORDER_WINDOW_LOOK) { // we don't need a decorator for this window fCurrentStack->SetDecorator(NULL); } } void Window::SetFeel(window_feel feel) { // if the subset list is no longer needed, clear it if ((fFeel == B_MODAL_SUBSET_WINDOW_FEEL || fFeel == B_FLOATING_SUBSET_WINDOW_FEEL) && feel != B_MODAL_SUBSET_WINDOW_FEEL && feel != B_FLOATING_SUBSET_WINDOW_FEEL) fSubsets.MakeEmpty(); fFeel = feel; // having modal windows with B_AVOID_FRONT or B_AVOID_FOCUS doesn't // make that much sense, so we filter those flags out on demand fFlags = fOriginalFlags; fFlags &= ValidWindowFlags(fFeel); if (!IsNormal()) { fFlags |= B_SAME_POSITION_IN_ALL_WORKSPACES; _PropagatePosition(); } } void Window::SetFlags(uint32 flags, BRegion* updateRegion) { fOriginalFlags = flags; fFlags = flags & ValidWindowFlags(fFeel); if (!IsNormal()) fFlags |= B_SAME_POSITION_IN_ALL_WORKSPACES; if ((fFlags & B_SAME_POSITION_IN_ALL_WORKSPACES) != 0) _PropagatePosition(); ::Decorator* decorator = Decorator(); if (decorator == NULL) return; int32 stackPosition = PositionInStack(); decorator->SetFlags(stackPosition, flags, updateRegion); // we might need to resize the window! decorator->GetSizeLimits(&fMinWidth, &fMinHeight, &fMaxWidth, &fMaxHeight); _ObeySizeLimits(); // TODO: not sure if we want to do this #if 0 if ((fOriginalFlags & kWindowScreenFlag) != (flags & kWindowScreenFlag)) { // TODO: disabling needs to be nestable (or we might lose the previous // update state) if ((flags & kWindowScreenFlag) != 0) DisableUpdateRequests(); else EnableUpdateRequests(); } #endif } /*! Returns whether or not a window is in the workspace list with the specified \a index. */ bool Window::InWorkspace(int32 index) const { return (fWorkspaces & (1UL << index)) != 0; } bool Window::SupportsFront() { if (fFeel == kDesktopWindowFeel || fFeel == kMenuWindowFeel || (fFlags & B_AVOID_FRONT) != 0) return false; return true; } bool Window::IsModal() const { return IsModalFeel(fFeel); } bool Window::IsFloating() const { return IsFloatingFeel(fFeel); } bool Window::IsNormal() const { return !IsFloatingFeel(fFeel) && !IsModalFeel(fFeel); } bool Window::HasModal() const { for (Window* window = NextWindow(fCurrentWorkspace); window != NULL; window = window->NextWindow(fCurrentWorkspace)) { if (window->IsHidden() || !window->IsModal()) continue; if (window->HasInSubset(this)) return true; } return false; } /*! \brief Returns the windows that's in behind of the backmost position this window can get. Returns NULL is this window can be the backmost window. \param workspace the workspace on which this check should be made. If the value is -1, the window's current workspace will be used. */ Window* Window::Backmost(Window* window, int32 workspace) { if (workspace == -1) workspace = fCurrentWorkspace; ASSERT(workspace != -1); if (workspace == -1) return NULL; // Desktop windows are always backmost if (fFeel == kDesktopWindowFeel) return NULL; if (window == NULL) window = PreviousWindow(workspace); for (; window != NULL; window = window->PreviousWindow(workspace)) { if (window->IsHidden() || window == this) continue; if (HasInSubset(window)) return window; } return NULL; } /*! \brief Returns the window that's in front of the frontmost position this window can get. Returns NULL if this window can be the frontmost window. \param workspace the workspace on which this check should be made. If the value is -1, the window's current workspace will be used. */ Window* Window::Frontmost(Window* first, int32 workspace) { if (workspace == -1) workspace = fCurrentWorkspace; ASSERT(workspace != -1); if (workspace == -1) return NULL; if (fFeel == kDesktopWindowFeel) return first ? first : NextWindow(workspace); if (first == NULL) first = NextWindow(workspace); for (Window* window = first; window != NULL; window = window->NextWindow(workspace)) { if (window->IsHidden() || window == this) continue; if (window->HasInSubset(this)) return window; } return NULL; } bool Window::AddToSubset(Window* window) { return fSubsets.AddItem(window); } void Window::RemoveFromSubset(Window* window) { fSubsets.RemoveItem(window); } /*! Returns whether or not a window is in the subset of this window. If a window is in the subset of this window, it means it should always appear behind this window. */ bool Window::HasInSubset(const Window* window) const { if (window == NULL || fFeel == window->Feel() || fFeel == B_NORMAL_WINDOW_FEEL) return false; // Menus are a special case: they will always be on-top of every window // of their application if (fFeel == kMenuWindowFeel) return window->ServerWindow()->App() == ServerWindow()->App(); if (window->Feel() == kMenuWindowFeel) return false; // we have a few special feels that have a fixed order const int32 kFeels[] = {kPasswordWindowFeel, kWindowScreenFeel, B_MODAL_ALL_WINDOW_FEEL, B_FLOATING_ALL_WINDOW_FEEL}; for (uint32 order = 0; order < sizeof(kFeels) / sizeof(kFeels[0]); order++) { if (fFeel == kFeels[order]) return true; if (window->Feel() == kFeels[order]) return false; } if ((fFeel == B_FLOATING_APP_WINDOW_FEEL && window->Feel() != B_MODAL_APP_WINDOW_FEEL) || fFeel == B_MODAL_APP_WINDOW_FEEL) return window->ServerWindow()->App() == ServerWindow()->App(); return fSubsets.HasItem(window); } /*! \brief Collects all workspaces views in this window and puts it into \a list */ void Window::FindWorkspacesViews(BObjectList& list) const { int32 count = fWorkspacesViewCount; fTopView->FindViews(kWorkspacesViewFlag, (BObjectList&)list, count); } /*! \brief Returns on which workspaces the window should be visible. A modal or floating window may be visible on a workspace if one of its subset windows is visible there. Floating windows also need to have a subset as front window to be visible. */ uint32 Window::SubsetWorkspaces() const { if (fFeel == B_MODAL_ALL_WINDOW_FEEL || fFeel == B_FLOATING_ALL_WINDOW_FEEL) return B_ALL_WORKSPACES; if (fFeel == B_FLOATING_APP_WINDOW_FEEL) { Window* front = fDesktop->FrontWindow(); if (front != NULL && front->IsNormal() && front->ServerWindow()->App() == ServerWindow()->App()) return ServerWindow()->App()->Workspaces(); return 0; } if (fFeel == B_MODAL_APP_WINDOW_FEEL) { uint32 workspaces = ServerWindow()->App()->Workspaces(); if (workspaces == 0) { // The application doesn't seem to have any other windows // open or visible - but we'd like to see modal windows // anyway, at least when they are first opened. return 1UL << fDesktop->CurrentWorkspace(); } return workspaces; } if (fFeel == B_MODAL_SUBSET_WINDOW_FEEL || fFeel == B_FLOATING_SUBSET_WINDOW_FEEL) { uint32 workspaces = 0; bool hasNormalFront = false; for (int32 i = 0; i < fSubsets.CountItems(); i++) { Window* window = fSubsets.ItemAt(i); if (!window->IsHidden()) workspaces |= window->Workspaces(); if (window == fDesktop->FrontWindow() && window->IsNormal()) hasNormalFront = true; } if (fFeel == B_FLOATING_SUBSET_WINDOW_FEEL && !hasNormalFront) return 0; return workspaces; } return 0; } /*! Returns whether or not a window is in the subset workspace list with the specified \a index. See SubsetWorkspaces(). */ bool Window::InSubsetWorkspace(int32 index) const { return (SubsetWorkspaces() & (1UL << index)) != 0; } // #pragma mark - static /*static*/ bool Window::IsValidLook(window_look look) { return look == B_TITLED_WINDOW_LOOK || look == B_DOCUMENT_WINDOW_LOOK || look == B_MODAL_WINDOW_LOOK || look == B_FLOATING_WINDOW_LOOK || look == B_BORDERED_WINDOW_LOOK || look == B_NO_BORDER_WINDOW_LOOK || look == kDesktopWindowLook || look == kLeftTitledWindowLook; } /*static*/ bool Window::IsValidFeel(window_feel feel) { return feel == B_NORMAL_WINDOW_FEEL || feel == B_MODAL_SUBSET_WINDOW_FEEL || feel == B_MODAL_APP_WINDOW_FEEL || feel == B_MODAL_ALL_WINDOW_FEEL || feel == B_FLOATING_SUBSET_WINDOW_FEEL || feel == B_FLOATING_APP_WINDOW_FEEL || feel == B_FLOATING_ALL_WINDOW_FEEL || feel == kDesktopWindowFeel || feel == kMenuWindowFeel || feel == kWindowScreenFeel || feel == kPasswordWindowFeel || feel == kOffscreenWindowFeel; } /*static*/ bool Window::IsModalFeel(window_feel feel) { return feel == B_MODAL_SUBSET_WINDOW_FEEL || feel == B_MODAL_APP_WINDOW_FEEL || feel == B_MODAL_ALL_WINDOW_FEEL; } /*static*/ bool Window::IsFloatingFeel(window_feel feel) { return feel == B_FLOATING_SUBSET_WINDOW_FEEL || feel == B_FLOATING_APP_WINDOW_FEEL || feel == B_FLOATING_ALL_WINDOW_FEEL; } /*static*/ uint32 Window::ValidWindowFlags() { return B_NOT_MOVABLE | B_NOT_CLOSABLE | B_NOT_ZOOMABLE | B_NOT_MINIMIZABLE | B_NOT_RESIZABLE | B_NOT_H_RESIZABLE | B_NOT_V_RESIZABLE | B_AVOID_FRONT | B_AVOID_FOCUS | B_WILL_ACCEPT_FIRST_CLICK | B_OUTLINE_RESIZE | B_NO_WORKSPACE_ACTIVATION | B_NOT_ANCHORED_ON_ACTIVATE | B_ASYNCHRONOUS_CONTROLS | B_QUIT_ON_WINDOW_CLOSE | B_SAME_POSITION_IN_ALL_WORKSPACES | B_AUTO_UPDATE_SIZE_LIMITS | B_CLOSE_ON_ESCAPE | B_NO_SERVER_SIDE_WINDOW_MODIFIERS | kWindowScreenFlag | kAcceptKeyboardFocusFlag; } /*static*/ uint32 Window::ValidWindowFlags(window_feel feel) { uint32 flags = ValidWindowFlags(); if (IsModalFeel(feel)) return flags & ~(B_AVOID_FOCUS | B_AVOID_FRONT); return flags; } // #pragma mark - private void Window::_ShiftPartOfRegion(BRegion* region, BRegion* regionToShift, int32 xOffset, int32 yOffset) { BRegion* common = fRegionPool.GetRegion(*regionToShift); if (!common) return; // see if there is a common part at all common->IntersectWith(region); if (common->CountRects() > 0) { // cut the common part from the region, // offset that to destination and include again region->Exclude(common); common->OffsetBy(xOffset, yOffset); region->Include(common); } fRegionPool.Recycle(common); } void Window::_TriggerContentRedraw(BRegion& dirty, const BRegion& expose) { if (!IsVisible() || dirty.CountRects() == 0 || (fFlags & kWindowScreenFlag) != 0) return; // put this into the pending dirty region // to eventually trigger a client redraw _TransferToUpdateSession(&dirty); if (expose.CountRects() > 0) { // draw exposed region background right now to avoid stamping artifacts if (fDrawingEngine->LockParallelAccess()) { bool copyToFrontEnabled = fDrawingEngine->CopyToFrontEnabled(); fDrawingEngine->SetCopyToFrontEnabled(true); fDrawingEngine->SuspendAutoSync(); fTopView->Draw(fDrawingEngine.Get(), &expose, &fContentRegion, true); fDrawingEngine->Sync(); fDrawingEngine->SetCopyToFrontEnabled(copyToFrontEnabled); fDrawingEngine->UnlockParallelAccess(); } } } void Window::_DrawBorder() { // this is executed in the window thread, but only // in respond to a REDRAW message having been received, the // clipping lock is held for reading ::Decorator* decorator = Decorator(); if (!decorator) return; // construct the region of the border that needs redrawing BRegion* dirtyBorderRegion = fRegionPool.GetRegion(); if (!dirtyBorderRegion) return; GetBorderRegion(dirtyBorderRegion); // intersect with our visible region dirtyBorderRegion->IntersectWith(&fVisibleRegion); // intersect with the dirty region dirtyBorderRegion->IntersectWith(&fDirtyRegion); DrawingEngine* engine = decorator->GetDrawingEngine(); if (dirtyBorderRegion->CountRects() > 0 && engine->LockParallelAccess()) { engine->ConstrainClippingRegion(dirtyBorderRegion); bool copyToFrontEnabled = engine->CopyToFrontEnabled(); engine->SetCopyToFrontEnabled(false); decorator->Draw(dirtyBorderRegion->Frame()); engine->SetCopyToFrontEnabled(copyToFrontEnabled); engine->CopyToFront(*dirtyBorderRegion); // TODO: remove this once the DrawState stuff is handled // more cleanly. The reason why this is needed is that // when the decorator draws strings, a draw state is set // on the Painter object, and this is were it might get // out of sync with what the ServerWindow things is the // current DrawState set on the Painter fWindow->ResyncDrawState(); engine->UnlockParallelAccess(); } fRegionPool.Recycle(dirtyBorderRegion); } /*! pre: the clipping is readlocked (this function is only called from _TriggerContentRedraw()), which in turn is only called from MessageReceived() with the clipping lock held */ void Window::_TransferToUpdateSession(BRegion* contentDirtyRegion) { if (contentDirtyRegion->CountRects() <= 0) return; //fDrawingEngine->FillRegion(*contentDirtyRegion, sPendingColor); //snooze(20000); // add to pending fPendingUpdateSession->SetUsed(true); fPendingUpdateSession->Include(contentDirtyRegion); if (!fUpdateRequested) { // send this to client _SendUpdateMessage(); // the pending region is now the current, // though the update does not start until // we received BEGIN_UPDATE from the client } } void Window::_SendUpdateMessage() { if (!fUpdatesEnabled) return; BMessage message(_UPDATE_); if (ServerWindow()->SendMessageToClient(&message) != B_OK) { // If sending the message failed, we'll just keep adding to the dirty // region until sending was successful. // TODO: we might want to automatically resend this message in this case return; } fUpdateRequested = true; fEffectiveDrawingRegionValid = false; } void Window::BeginUpdate(BPrivate::PortLink& link) { // NOTE: since we might "shift" parts of the // internal dirty regions from the desktop thread // in response to Window::ResizeBy(), which // might move arround views, the user of this function // needs to hold the global clipping lock so that the internal // dirty regions are not messed with from the Desktop thread // and ServerWindow thread at the same time. if (!fUpdateRequested) { link.StartMessage(B_ERROR); link.Flush(); fprintf(stderr, "Window::BeginUpdate() - no update requested!\n"); return; } // make the pending update session the current update session // (toggle the pointers) UpdateSession* temp = fCurrentUpdateSession; fCurrentUpdateSession = fPendingUpdateSession; fPendingUpdateSession = temp; fPendingUpdateSession->SetUsed(false); // all drawing command from the client // will have the dirty region from the update // session enforced fInUpdate = true; fEffectiveDrawingRegionValid = false; // TODO: each view could be drawn individually // right before carrying out the first drawing // command from the client during an update // (View::IsBackgroundDirty() can be used // for this) if (!fContentRegionValid) _UpdateContentRegion(); BRegion* dirty = fRegionPool.GetRegion( fCurrentUpdateSession->DirtyRegion()); if (!dirty) { link.StartMessage(B_ERROR); link.Flush(); return; } dirty->IntersectWith(&VisibleContentRegion()); //if (!fCurrentUpdateSession->IsExpose()) { ////sCurrentColor.red = rand() % 255; ////sCurrentColor.green = rand() % 255; ////sCurrentColor.blue = rand() % 255; ////sPendingColor.red = rand() % 255; ////sPendingColor.green = rand() % 255; ////sPendingColor.blue = rand() % 255; //fDrawingEngine->FillRegion(*dirty, sCurrentColor); //snooze(10000); //} link.StartMessage(B_OK); // append the current window geometry to the // message, the client will need it link.Attach(fFrame.LeftTop()); link.Attach(fFrame.Width()); link.Attach(fFrame.Height()); // find and attach all views that intersect with // the dirty region fTopView->AddTokensForViewsInRegion(link, *dirty, &fContentRegion); // mark the end of the token "list" link.Attach(B_NULL_TOKEN); link.Flush(); // supress back to front buffer copies in the drawing engine fDrawingEngine->SetCopyToFrontEnabled(false); if (fDrawingEngine->LockParallelAccess()) { fDrawingEngine->SuspendAutoSync(); fTopView->Draw(GetDrawingEngine(), dirty, &fContentRegion, true); fDrawingEngine->Sync(); fDrawingEngine->UnlockParallelAccess(); } // else the background was cleared already fRegionPool.Recycle(dirty); } void Window::EndUpdate() { // NOTE: see comment in _BeginUpdate() if (fInUpdate) { // reenable copy to front fDrawingEngine->SetCopyToFrontEnabled(true); BRegion* dirty = fRegionPool.GetRegion( fCurrentUpdateSession->DirtyRegion()); if (dirty) { dirty->IntersectWith(&VisibleContentRegion()); fDrawingEngine->CopyToFront(*dirty); fRegionPool.Recycle(dirty); } fCurrentUpdateSession->SetUsed(false); fInUpdate = false; fEffectiveDrawingRegionValid = false; } if (fPendingUpdateSession->IsUsed()) { // send this to client _SendUpdateMessage(); } else { fUpdateRequested = false; } } void Window::_UpdateContentRegion() { fContentRegion.Set(fFrame); // resize handle ::Decorator* decorator = Decorator(); if (decorator) fContentRegion.Exclude(&decorator->GetFootprint()); fContentRegionValid = true; } void Window::_ObeySizeLimits() { // make sure we even have valid size limits if (fMaxWidth < fMinWidth) fMaxWidth = fMinWidth; if (fMaxHeight < fMinHeight) fMaxHeight = fMinHeight; // Automatically resize the window to fit these new limits // if it does not already. // On R5, Windows don't automatically resize, but since // BWindow::ResizeTo() even honors the limits, I would guess // this is a bug that we don't have to adopt. // Note that most current apps will do unnecessary resizing // after having set the limits, but the overhead is neglible. float minWidthDiff = fMinWidth - fFrame.Width(); float minHeightDiff = fMinHeight - fFrame.Height(); float maxWidthDiff = fMaxWidth - fFrame.Width(); float maxHeightDiff = fMaxHeight - fFrame.Height(); float xDiff = 0.0; if (minWidthDiff > 0.0) // we're currently smaller than minWidth xDiff = minWidthDiff; else if (maxWidthDiff < 0.0) // we're currently larger than maxWidth xDiff = maxWidthDiff; float yDiff = 0.0; if (minHeightDiff > 0.0) // we're currently smaller than minHeight yDiff = minHeightDiff; else if (maxHeightDiff < 0.0) // we're currently larger than maxHeight yDiff = maxHeightDiff; if (fDesktop) fDesktop->ResizeWindowBy(this, xDiff, yDiff); else ResizeBy((int32)xDiff, (int32)yDiff, NULL); } // #pragma mark - UpdateSession Window::UpdateSession::UpdateSession() : fDirtyRegion(), fInUse(false) { } void Window::UpdateSession::Include(BRegion* additionalDirty) { fDirtyRegion.Include(additionalDirty); } void Window::UpdateSession::Exclude(BRegion* dirtyInNextSession) { fDirtyRegion.Exclude(dirtyInNextSession); } void Window::UpdateSession::MoveBy(int32 x, int32 y) { fDirtyRegion.OffsetBy(x, y); } void Window::UpdateSession::SetUsed(bool used) { fInUse = used; if (!fInUse) fDirtyRegion.MakeEmpty(); } int32 Window::PositionInStack() const { if (!fCurrentStack.IsSet()) return -1; return fCurrentStack->WindowList().IndexOf(this); } bool Window::DetachFromWindowStack(bool ownStackNeeded) { // The lock must normally be held but is not held when closing the window. //ASSERT_MULTI_WRITE_LOCKED(fDesktop->WindowLocker()); if (!fCurrentStack.IsSet()) return false; if (fCurrentStack->CountWindows() == 1) return true; int32 index = PositionInStack(); if (fCurrentStack->RemoveWindow(this) == false) return false; BRegion invalidatedRegion; ::Decorator* decorator = fCurrentStack->Decorator(); if (decorator != NULL) { decorator->RemoveTab(index, &invalidatedRegion); decorator->SetTopTab(fCurrentStack->LayerOrder().CountItems() - 1); } Window* remainingTop = fCurrentStack->TopLayerWindow(); if (remainingTop != NULL) { if (decorator != NULL) decorator->SetDrawingEngine(remainingTop->GetDrawingEngine()); // propagate focus to the decorator remainingTop->SetFocus(remainingTop->IsFocus()); remainingTop->SetLook(remainingTop->Look(), NULL); } fCurrentStack = NULL; if (ownStackNeeded == true) _InitWindowStack(); // propagate focus to the new decorator SetFocus(IsFocus()); if (remainingTop != NULL) { invalidatedRegion.Include(&remainingTop->VisibleRegion()); fDesktop->RebuildAndRedrawAfterWindowChange(remainingTop, invalidatedRegion); } return true; } bool Window::AddWindowToStack(Window* window) { ASSERT_MULTI_WRITE_LOCKED(fDesktop->WindowLocker()); WindowStack* stack = GetWindowStack(); if (stack == NULL) return false; BRegion dirty; // move window to the own position BRect ownFrame = Frame(); BRect frame = window->Frame(); float deltaToX = round(ownFrame.left - frame.left); float deltaToY = round(ownFrame.top - frame.top); frame.OffsetBy(deltaToX, deltaToY); float deltaByX = round(ownFrame.right - frame.right); float deltaByY = round(ownFrame.bottom - frame.bottom); dirty.Include(&window->VisibleRegion()); window->MoveBy(deltaToX, deltaToY, false); window->ResizeBy(deltaByX, deltaByY, &dirty, false); // first collect dirt from the window to add ::Decorator* otherDecorator = window->Decorator(); if (otherDecorator != NULL) dirty.Include(otherDecorator->TitleBarRect()); ::Decorator* decorator = stack->Decorator(); if (decorator != NULL) dirty.Include(decorator->TitleBarRect()); int32 position = PositionInStack() + 1; if (position >= stack->CountWindows()) position = -1; if (stack->AddWindow(window, position) == false) return false; window->DetachFromWindowStack(false); window->fCurrentStack.SetTo(stack); if (decorator != NULL) { DesktopSettings settings(fDesktop); decorator->AddTab(settings, window->Title(), window->Look(), window->Flags(), position, &dirty); } window->SetLook(window->Look(), &dirty); fDesktop->RebuildAndRedrawAfterWindowChange(TopLayerStackWindow(), dirty); window->SetFocus(window->IsFocus()); return true; } Window* Window::StackedWindowAt(const BPoint& where) { ::Decorator* decorator = Decorator(); if (decorator == NULL) return this; int tab = decorator->TabAt(where); // if we have a decorator we also have a stack Window* window = fCurrentStack->WindowAt(tab); if (window != NULL) return window; return this; } Window* Window::TopLayerStackWindow() { if (!fCurrentStack.IsSet()) return this; return fCurrentStack->TopLayerWindow(); } WindowStack* Window::GetWindowStack() { if (!fCurrentStack.IsSet()) return _InitWindowStack(); return fCurrentStack; } bool Window::MoveToTopStackLayer() { ::Decorator* decorator = Decorator(); if (decorator == NULL) return false; decorator->SetDrawingEngine(GetDrawingEngine()); SetLook(Look(), NULL); decorator->SetTopTab(PositionInStack()); return fCurrentStack->MoveToTopLayer(this); } bool Window::MoveToStackPosition(int32 to, bool isMoving) { if (!fCurrentStack.IsSet()) return false; int32 index = PositionInStack(); if (fCurrentStack->Move(index, to) == false) return false; BRegion dirty; ::Decorator* decorator = Decorator(); if (decorator && decorator->MoveTab(index, to, isMoving, &dirty) == false) return false; fDesktop->RebuildAndRedrawAfterWindowChange(this, dirty); return true; } WindowStack* Window::_InitWindowStack() { fCurrentStack = NULL; ::Decorator* decorator = NULL; if (fLook != B_NO_BORDER_WINDOW_LOOK) decorator = gDecorManager.AllocateDecorator(this); WindowStack* stack = new(std::nothrow) WindowStack(decorator); if (stack == NULL) return NULL; if (stack->AddWindow(this) != true) { delete stack; return NULL; } fCurrentStack.SetTo(stack, true); return stack; } WindowStack::WindowStack(::Decorator* decorator) : fDecorator(decorator) { } WindowStack::~WindowStack() { } void WindowStack::SetDecorator(::Decorator* decorator) { fDecorator.SetTo(decorator); } ::Decorator* WindowStack::Decorator() { return fDecorator.Get(); } Window* WindowStack::TopLayerWindow() const { return fWindowLayerOrder.ItemAt(fWindowLayerOrder.CountItems() - 1); } int32 WindowStack::CountWindows() { return fWindowList.CountItems(); } Window* WindowStack::WindowAt(int32 index) { return fWindowList.ItemAt(index); } bool WindowStack::AddWindow(Window* window, int32 position) { if (position >= 0) { if (fWindowList.AddItem(window, position) == false) return false; } else if (fWindowList.AddItem(window) == false) return false; if (fWindowLayerOrder.AddItem(window) == false) { fWindowList.RemoveItem(window); return false; } return true; } bool WindowStack::RemoveWindow(Window* window) { if (fWindowList.RemoveItem(window) == false) return false; fWindowLayerOrder.RemoveItem(window); return true; } bool WindowStack::MoveToTopLayer(Window* window) { int32 index = fWindowLayerOrder.IndexOf(window); return fWindowLayerOrder.MoveItem(index, fWindowLayerOrder.CountItems() - 1); } bool WindowStack::Move(int32 from, int32 to) { return fWindowList.MoveItem(from, to); }