Files
haiku-beta6/src/servers/app/WindowLayer.cpp
T

2100 lines
50 KiB
C++
Raw Normal View History

2005-06-17 19:10:15 +00:00
/*
* Copyright (c) 2001-2006, Haiku, Inc.
2005-06-17 19:10:15 +00:00
* Distributed under the terms of the MIT license.
*
* Authors:
* DarkWyrm <[email protected]>
* Adi Oanca <[email protected]>
* Stephan Aßmus <[email protected]>
* Axel Dörfler, [email protected]
2005-06-17 19:10:15 +00:00
*/
2005-06-23 22:43:11 +00:00
#include "WindowLayer.h"
#include "DebugInfoManager.h"
2003-02-14 01:53:53 +00:00
#include "Decorator.h"
2005-06-17 19:10:15 +00:00
#include "DecorManager.h"
2003-02-14 01:53:53 +00:00
#include "Desktop.h"
#include "DrawingEngine.h"
#include "HWInterface.h"
#include "MessagePrivate.h"
#include "PortLink.h"
#include "ServerApp.h"
#include "ServerWindow.h"
2004-01-13 00:38:42 +00:00
#include "Workspace.h"
#include "WorkspacesLayer.h"
2003-02-14 01:53:53 +00:00
#include <WindowPrivate.h>
#include <Debug.h>
#include <DirectWindow.h>
#include <PortLink.h>
#include <View.h>
#include <new>
#include <stdio.h>
// Toggle debug output
//#define DEBUG_WINDOW_LAYER
//#define DEBUG_WINDOW_LAYER_CLICK
#ifdef DEBUG_WINDOW_LAYER
2003-09-08 22:09:10 +00:00
# define STRACE(x) printf x
#else
# define STRACE(x) ;
#endif
#ifdef DEBUG_WINDOW_LAYER_CLICK
2003-09-08 22:09:10 +00:00
# define STRACE_CLICK(x) printf x
#else
# define STRACE_CLICK(x) ;
2003-04-05 01:51:35 +00:00
#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
2006-04-13 15:18:16 +00:00
WindowLayer::WindowLayer(const BRect& frame, const char *name,
window_look look, window_feel feel,
uint32 flags, uint32 workspaces,
::ServerWindow* window,
DrawingEngine* drawingEngine)
:
fTitle(name),
fFrame(frame),
fVisibleRegion(),
fVisibleContentRegion(),
fVisibleContentRegionValid(false),
fDirtyRegion(),
fDirtyCause(0),
fBorderRegion(),
fBorderRegionValid(false),
fContentRegion(),
fContentRegionValid(false),
fEffectiveDrawingRegion(),
fEffectiveDrawingRegionValid(false),
fRegionPool(),
fIsClosing(false),
fIsMinimizing(false),
fIsZooming(false),
fIsResizing(false),
fIsSlidingTab(false),
fIsDragging(false),
fActivateOnMouseUp(false),
fDecorator(NULL),
fTopLayer(NULL),
fWindow(window),
fDrawingEngine(drawingEngine),
fDesktop(window->Desktop()),
fLastMousePosition(0.0, 0.0),
fLastMoveTime(0),
fCurrentUpdateSession(),
fPendingUpdateSession(),
fUpdateRequested(false),
fInUpdate(false),
// windows start hidden
fHidden(true),
fMinimized(false),
fIsFocus(false),
fLook(look),
fFeel(feel),
fWorkspaces(workspaces),
fCurrentWorkspace(-1),
fMinWidth(1),
fMaxWidth(32768),
fMinHeight(1),
fMaxHeight(32768)
2003-02-14 01:53:53 +00:00
{
// 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);
2005-12-01 12:07:28 +00:00
if (fLook != B_NO_BORDER_WINDOW_LOOK) {
fDecorator = gDecorManager.AllocateDecorator(fDesktop, fDrawingEngine,
frame, name, fLook, fFlags);
if (fDecorator) {
fDecorator->GetSizeLimits(&fMinWidth, &fMinHeight,
&fMaxWidth, &fMaxHeight);
}
}
2004-03-28 14:58:39 +00:00
// 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 (fDesktop->ScreenAt(0)) {
fDesktop->ScreenAt(0)->GetMode(width, height,
colorSpace, frequency);
// TODO: MOVE THIS AWAY!!! ResizeBy contains calls to virtual methods!
// Also, there is no TopLayer()!
2005-11-28 23:36:59 +00:00
fFrame.OffsetTo(B_ORIGIN);
// ResizeBy(width - frame.Width(), height - frame.Height(), NULL);
}
}
STRACE(("WindowLayer %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"));
2003-02-14 01:53:53 +00:00
}
2004-06-11 02:46:48 +00:00
WindowLayer::~WindowLayer()
2003-02-14 01:53:53 +00:00
{
if (fTopLayer)
fTopLayer->DetachedFromWindow();
delete fTopLayer;
delete fDecorator;
2003-02-14 01:53:53 +00:00
}
void
WindowLayer::SetClipping(BRegion* stillAvailableOnScreen)
2003-02-14 01:53:53 +00:00
{
// this function is only called from the Desktop thread
2005-11-09 20:14:52 +00:00
// 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;
2005-12-11 21:46:35 +00:00
// TODO: review this!
fWindow->HandleDirectConnection(B_DIRECT_MODIFY | B_CLIPPING_MODIFIED);
}
2005-12-11 21:46:35 +00:00
void
WindowLayer::GetFullRegion(BRegion* region)
{
// TODO: if someone needs to call this from
// the outside, the clipping needs to be readlocked!
GetBorderRegion(region);
// start from the frame, extend to include decorator border
region->Include(fFrame);
}
// GetBorderRegion
void
WindowLayer::GetBorderRegion(BRegion* region)
{
// TODO: if someone needs to call this from
// the outside, the clipping needs to be readlocked!
if (!fBorderRegionValid) {
// TODO: checkup Decorator::GetFootPrint() to see if it is as fast as this:
/* fBorderRegion.Set(BRect(fFrame.left - 4, fFrame.top - 20,
(fFrame.left + fFrame.right) / 2, fFrame.top - 5));
fBorderRegion.Include(BRect(fFrame.left - 4, fFrame.top - 4,
fFrame.right + 4, fFrame.top - 1));
fBorderRegion.Include(BRect(fFrame.left - 4, fFrame.top,
fFrame.left - 1, fFrame.bottom));
fBorderRegion.Include(BRect(fFrame.right + 1, fFrame.top,
fFrame.right + 4, fFrame.bottom - 11));
fBorderRegion.Include(BRect(fFrame.left - 4, fFrame.bottom + 1,
fFrame.right - 11, fFrame.bottom + 4));
fBorderRegion.Include(BRect(fFrame.right - 10, fFrame.bottom - 10,
fFrame.right + 4, fFrame.bottom + 4));*/
// TODO: remove and use Decorator::GetFootPrint()
// start from the frame, extend to include decorator border
if (fDecorator) {
fDecorator->GetFootprint(&fBorderRegion);
}
fBorderRegionValid = true;
}
*region = fBorderRegion;
}
void
WindowLayer::GetContentRegion(BRegion* region)
{
// TODO: if someone needs to call this from
// the outside, the clipping needs to be readlocked!
if (!fContentRegionValid) {
_UpdateContentRegion();
}
*region = fContentRegion;
}
2005-11-24 17:12:32 +00:00
2005-11-09 20:14:52 +00:00
BRegion&
WindowLayer::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);
2005-11-09 20:14:52 +00:00
}
return fVisibleContentRegion;
}
// #pragma mark -
void
WindowLayer::_PropagatePosition()
{
if ((fFlags & B_SAME_POSITION_IN_ALL_WORKSPACES) == 0)
return;
for (int32 i = 0; i < kListCount; i++) {
Anchor(i).position = fFrame.LeftTop();
}
}
void
WindowLayer::MoveBy(int32 x, int32 y)
{
// 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);
if (fBorderRegionValid)
fBorderRegion.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 (fDecorator)
fDecorator->MoveBy(x, y);
if (fTopLayer != NULL) {
fTopLayer->MoveBy(x, y, NULL);
fTopLayer->UpdateOverlay();
}
// the desktop will take care of dirty regions
2005-11-09 20:14:52 +00:00
// 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);
2003-02-14 01:53:53 +00:00
}
2004-06-11 02:46:48 +00:00
void
WindowLayer::ResizeBy(int32 x, int32 y, BRegion* dirtyRegion)
{
// this function is only called from the desktop thread
int32 wantWidth = fFrame.IntegerWidth() + x;
int32 wantHeight = fFrame.IntegerHeight() + y;
// enforce size limits
if (wantWidth < fMinWidth)
wantWidth = fMinWidth;
if (wantWidth > fMaxWidth)
wantWidth = fMaxWidth;
if (wantHeight < fMinHeight)
wantHeight = fMinHeight;
if (wantHeight > fMaxHeight)
wantHeight = fMaxHeight;
x = wantWidth - fFrame.IntegerWidth();
y = wantHeight - fFrame.IntegerHeight();
if (x == 0 && y == 0)
2005-11-09 20:14:52 +00:00
return;
fWindow->HandleDirectConnection(B_DIRECT_STOP);
2005-11-09 20:14:52 +00:00
fFrame.right += x;
fFrame.bottom += y;
2005-11-09 20:14:52 +00:00
fWindow->HandleDirectConnection(B_DIRECT_START | B_BUFFER_RESIZED);
2005-11-09 20:14:52 +00:00
fBorderRegionValid = false;
fContentRegionValid = false;
fEffectiveDrawingRegionValid = false;
if (fDecorator) {
fDecorator->ResizeBy(x, y, dirtyRegion);
//if (dirtyRegion) {
//fDrawingEngine->FillRegion(*dirtyRegion, RGBColor(255, 255, 0, 255));
//snooze(40000);
//}
2005-11-09 20:14:52 +00:00
}
if (fTopLayer != NULL) {
fTopLayer->ResizeBy(x, y, dirtyRegion);
fTopLayer->UpdateOverlay();
}
//if (dirtyRegion)
//fDrawingEngine->FillRegion(*dirtyRegion, RGBColor(0, 255, 255, 255));
2005-11-09 20:14:52 +00:00
// 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);
2003-02-14 01:53:53 +00:00
}
2004-06-11 02:46:48 +00:00
2005-11-14 17:39:33 +00:00
2005-07-05 16:17:16 +00:00
void
WindowLayer::ScrollViewBy(ViewLayer* view, int32 dx, int32 dy)
2005-07-05 16:17:16 +00:00
{
// this is executed in ServerWindow with the Readlock
// held
2005-07-05 16:17:16 +00:00
if (!view || view == fTopLayer || (dx == 0 && dy == 0))
return;
2005-07-05 16:17:16 +00:00
BRegion* dirty = fRegionPool.GetRegion();
if (!dirty)
return;
view->ScrollBy(dx, dy, dirty);
2005-07-05 16:17:16 +00:00
//fDrawingEngine->FillRegion(dirty, RGBColor(255, 0, 255, 255));
//snooze(2000);
if (IsVisible() && view->IsVisible()) {
dirty->IntersectWith(&VisibleContentRegion());
_TriggerContentRedraw(*dirty);
2005-07-05 16:17:16 +00:00
}
fRegionPool.Recycle(dirty);
2005-07-05 16:17:16 +00:00
}
2005-11-14 17:39:33 +00:00
//! Takes care of invalidating parts that could not be copied
void
WindowLayer::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) {
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);
// the part which we can copy is not dirty
newDirty->Exclude(region);
fDrawingEngine->CopyRegion(region, xOffset, yOffset);
// move along the already dirty regions that are common
// with the region that we could copy
_ShiftPartOfRegion(&fDirtyRegion, region, xOffset, yOffset);
if (fPendingUpdateSession.IsUsed())
_ShiftPartOfRegion(&fPendingUpdateSession.DirtyRegion(), region, xOffset, yOffset);
if (fCurrentUpdateSession.IsUsed()) {
// if there are parts in the current update session
// that intersect with the copied region, we cannot
// simply shift them as with the other dirty regions
// - we cannot change the update rect already told to the
// client, that's why we transfer those parts to the
// new dirty region instead
BRegion* common = fRegionPool.GetRegion(*region);
// see if there is a common part at all
common->IntersectWith(&fCurrentUpdateSession.DirtyRegion());
if (common->CountRects() > 0) {
// cut the common part from the region
fCurrentUpdateSession.DirtyRegion().Exclude(common);
newDirty->Include(common);
}
fRegionPool.Recycle(common);
}
}
}
// 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
WindowLayer::SetTopLayer(ViewLayer* topLayer)
{
fTopLayer = topLayer;
if (fTopLayer) {
// the top layer is special, it has a coordinate system
// as if it was attached directly to the desktop, therefor,
// the coordinate conversion through the layer tree works
// as expected, since the top layer has no "parent" but has
// fFrame as if it had
// make sure the location of the top layer on screen matches ours
fTopLayer->MoveBy(fFrame.left - fTopLayer->Frame().left,
fFrame.top - fTopLayer->Frame().top, NULL);
// make sure the size of the top layer matches ours
fTopLayer->ResizeBy(fFrame.Width() - fTopLayer->Frame().Width(),
fFrame.Height() - fTopLayer->Frame().Height(), NULL);
fTopLayer->AttachedToWindow(this);
}
}
ViewLayer*
WindowLayer::ViewAt(const BPoint& where)
{
ViewLayer* view = NULL;
if (!fContentRegionValid)
_UpdateContentRegion();
view = fTopLayer->ViewAt(where, &fContentRegion);
return view;
}
window_anchor&
WindowLayer::Anchor(int32 index)
{
return fAnchor[index];
}
WindowLayer*
WindowLayer::NextWindow(int32 index) const
{
return fAnchor[index].next;
}
WindowLayer*
WindowLayer::PreviousWindow(int32 index) const
{
return fAnchor[index].previous;
}
// #pragma mark -
void
WindowLayer::GetEffectiveDrawingRegion(ViewLayer* layer, BRegion& region)
{
if (!fEffectiveDrawingRegionValid) {
fEffectiveDrawingRegion = VisibleContentRegion();
if (fInUpdate) {
// enforce the dirty region of the update session
fEffectiveDrawingRegion.IntersectWith(&fCurrentUpdateSession.DirtyRegion());
} else {
// not in update, the view can draw everywhere
//printf("WindowLayer(%s)::GetEffectiveDrawingRegion(for %s) - outside update\n", Title(), layer->Name());
}
fEffectiveDrawingRegionValid = true;
}
// TODO: this is a region that needs to be cached later in the server
// when the current layer 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 layer changes
// again or fEffectiveDrawingRegionValid is suddenly false.
region = fEffectiveDrawingRegion;
if (!fContentRegionValid)
_UpdateContentRegion();
region.IntersectWith(&layer->ScreenClipping(&fContentRegion));
}
bool
WindowLayer::DrawingRegionChanged(ViewLayer* layer) const
{
return !fEffectiveDrawingRegionValid || !layer->IsScreenClippingValid();
}
void
WindowLayer::ProcessDirtyRegion(BRegion& region)
2004-08-07 20:30:58 +00:00
{
// if this is exectuted 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(&region);
fDirtyCause |= UPDATE_EXPOSE;
}
void
WindowLayer::RedrawDirtyRegion()
{
// executed from ServerWindow with the read lock held
if (IsVisible()) {
_DrawBorder();
BRegion* dirtyContentRegion =
fRegionPool.GetRegion(VisibleContentRegion());
dirtyContentRegion->IntersectWith(&fDirtyRegion);
_TriggerContentRedraw(*dirtyContentRegion);
fRegionPool.Recycle(dirtyContentRegion);
}
// 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();
fDirtyCause = 0;
}
void
WindowLayer::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
WindowLayer::MarkContentDirty(BRegion& regionOnScreen)
{
// 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)
return;
regionOnScreen.IntersectWith(&VisibleContentRegion());
fDirtyCause |= UPDATE_REQUEST;
_TriggerContentRedraw(regionOnScreen);
}
void
WindowLayer::MarkContentDirtyAsync(BRegion& regionOnScreen)
{
// NOTE: see comments in ProcessDirtyRegion()
if (fHidden)
return;
regionOnScreen.IntersectWith(&VisibleContentRegion());
if (fDirtyRegion.CountRects() == 0) {
ServerWindow()->RequestRedraw();
}
fDirtyRegion.Include(&regionOnScreen);
fDirtyCause |= UPDATE_REQUEST;
}
void
WindowLayer::InvalidateView(ViewLayer* layer, BRegion& layerRegion)
{
if (layer && IsVisible() && layer->IsVisible()) {
if (!fContentRegionValid)
_UpdateContentRegion();
layer->ConvertToScreen(&layerRegion);
layerRegion.IntersectWith(&VisibleContentRegion());
if (layerRegion.CountRects() > 0) {
layerRegion.IntersectWith(&layer->ScreenClipping(&fContentRegion));
//fDrawingEngine->FillRegion(layerRegion, RGBColor(0, 255, 0, 255));
//snooze(10000);
fDirtyCause |= UPDATE_REQUEST;
_TriggerContentRedraw(layerRegion);
}
}
}
// EnableUpdateRequests
void
WindowLayer::EnableUpdateRequests()
{
// fUpdateRequestsEnabled = true;
/* if (fCumulativeRegion.CountRects() > 0) {
GetRootLayer()->MarkForRedraw(fCumulativeRegion);
GetRootLayer()->TriggerRedraw();
}*/
2004-08-07 20:30:58 +00:00
}
// #pragma mark -
void
WindowLayer::MouseDown(BMessage* message, BPoint where, int32* _viewToken)
{
// TODO: move into Decorator
if (!fBorderRegionValid)
GetBorderRegion(&fBorderRegion);
2005-11-28 23:36:59 +00:00
// default action is to drag the WindowLayer
if (fBorderRegion.Contains(where)) {
// clicking WindowLayer visible area
click_type action = DEC_DRAG;
if (fDecorator)
action = _ActionFor(message);
// ignore clicks on decorator buttons if the
// non-floating window doesn't have focus
if (!IsFocus() && !IsFloating() && action != DEC_MOVETOBACK
&& action != DEC_RESIZE && action != DEC_SLIDETAB)
action = DEC_DRAG;
// set decorator internals
switch (action) {
case DEC_CLOSE:
fIsClosing = true;
STRACE_CLICK(("===> DEC_CLOSE\n"));
break;
case DEC_ZOOM:
fIsZooming = true;
STRACE_CLICK(("===> DEC_ZOOM\n"));
break;
case DEC_MINIMIZE:
fIsMinimizing = true;
STRACE_CLICK(("===> DEC_MINIMIZE\n"));
break;
case DEC_DRAG:
fIsDragging = true;
fLastMousePosition = where;
STRACE_CLICK(("===> DEC_DRAG\n"));
break;
case DEC_RESIZE:
fIsResizing = true;
fLastMousePosition = where;
STRACE_CLICK(("===> DEC_RESIZE\n"));
break;
case DEC_SLIDETAB:
fIsSlidingTab = true;
fLastMousePosition = where;
STRACE_CLICK(("===> DEC_SLIDETAB\n"));
break;
default:
break;
}
// redraw decorator
BRegion* visibleBorder = fRegionPool.GetRegion();
GetBorderRegion(visibleBorder);
visibleBorder->IntersectWith(&VisibleRegion());
DrawingEngine* engine = fDecorator->GetDrawingEngine();
engine->LockParallelAccess();
engine->ConstrainClippingRegion(visibleBorder);
if (fIsZooming) {
fDecorator->SetZoom(true);
} else if (fIsClosing) {
fDecorator->SetClose(true);
} else if (fIsMinimizing) {
fDecorator->SetMinimize(true);
}
engine->UnlockParallelAccess();
fRegionPool.Recycle(visibleBorder);
// based on what the Decorator returned, properly place this window.
if (action == DEC_MOVETOBACK) {
fDesktop->SendWindowBehind(this);
} else {
fDesktop->SetMouseEventWindow(this);
// activate window if not in FFM mode
DesktopSettings desktopSettings(fDesktop);
if (desktopSettings.MouseMode() == B_NORMAL_MOUSE) {
fDesktop->ActivateWindow(this);
} else {
// actually, the window should already be
// focused since the mouse would have to
// be over it, but just for completeness...
fDesktop->SetFocusWindow(this);
if (action == DEC_DRAG) {
fActivateOnMouseUp = true;
fLastMoveTime = system_time();
}
}
}
} else {
if (ViewLayer* view = ViewAt(where)) {
if (HasModal())
return;
// clicking a simple ViewLayer
if (!IsFocus()) {
DesktopSettings desktopSettings(fDesktop);
// Activate window in case it doesn't accept first click, and
2006-01-16 14:43:50 +00:00
// we're not in FFM mode
if ((Flags() & B_WILL_ACCEPT_FIRST_CLICK) == 0
&& desktopSettings.MouseMode() == B_NORMAL_MOUSE)
fDesktop->ActivateWindow(this);
// eat the click if we don't accept first click
if ((Flags() & (B_WILL_ACCEPT_FIRST_CLICK | B_AVOID_FOCUS)) == 0)
return;
}
// fill out view token for the view under the mouse
*_viewToken = view->Token();
view->MouseDown(message, where);
}
}
}
void
WindowLayer::MouseUp(BMessage* message, BPoint where, int32* _viewToken)
{
bool invalidate = false;
if (fDecorator) {
click_type action = _ActionFor(message);
// redraw decorator
BRegion* visibleBorder = fRegionPool.GetRegion();
GetBorderRegion(visibleBorder);
visibleBorder->IntersectWith(&VisibleRegion());
DrawingEngine* engine = fDecorator->GetDrawingEngine();
engine->LockParallelAccess();
engine->ConstrainClippingRegion(visibleBorder);
if (fIsZooming) {
fIsZooming = false;
fDecorator->SetZoom(false);
if (action == DEC_ZOOM) {
invalidate = true;
fWindow->NotifyZoom();
}
}
if (fIsClosing) {
fIsClosing = false;
fDecorator->SetClose(false);
if (action == DEC_CLOSE) {
invalidate = true;
fWindow->NotifyQuitRequested();
}
}
if (fIsMinimizing) {
fIsMinimizing = false;
fDecorator->SetMinimize(false);
if (action == DEC_MINIMIZE) {
invalidate = true;
fWindow->NotifyMinimize(true);
}
}
engine->UnlockParallelAccess();
fRegionPool.Recycle(visibleBorder);
}
// in FFM mode, activate the window and bring it
// to front in case this was a drag click but the
// mouse was not moved
if (fActivateOnMouseUp) {
fActivateOnMouseUp = false;
// on R5, there is a time window for this feature
// ie, click and press too long, nothing will happen
if (system_time() - fLastMoveTime < 500000)
fDesktop->ActivateWindow(this);
}
fIsDragging = false;
fIsResizing = false;
fIsSlidingTab = false;
if (ViewLayer* view = ViewAt(where)) {
if (HasModal())
return;
*_viewToken = view->Token();
view->MouseUp(message, where);
}
}
void
WindowLayer::MouseMoved(BMessage *message, BPoint where, int32* _viewToken,
bool isLatestMouseMoved)
{
ViewLayer* view = ViewAt(where);
if (view != NULL)
*_viewToken = view->Token();
// ignore pointer history
if (!isLatestMouseMoved)
return;
// limit the rate at which "mouse moved" events
// are handled that move or resize the window
if (fIsDragging || fIsResizing) {
bigtime_t now = system_time();
if (now - fLastMoveTime < 13333) {
// TODO: add a "timed event" to query for
// the then current mouse position
return;
}
fLastMoveTime = now;
}
if (fDecorator) {
BRegion* visibleBorder = fRegionPool.GetRegion();
GetBorderRegion(visibleBorder);
visibleBorder->IntersectWith(&VisibleRegion());
DrawingEngine* engine = fDecorator->GetDrawingEngine();
engine->LockParallelAccess();
engine->ConstrainClippingRegion(visibleBorder);
if (fIsZooming) {
fDecorator->SetZoom(_ActionFor(message) == DEC_ZOOM);
} else if (fIsClosing) {
fDecorator->SetClose(_ActionFor(message) == DEC_CLOSE);
} else if (fIsMinimizing) {
fDecorator->SetMinimize(_ActionFor(message) == DEC_MINIMIZE);
}
engine->UnlockParallelAccess();
fRegionPool.Recycle(visibleBorder);
}
BPoint delta = where - fLastMousePosition;
2006-05-24 11:05:25 +00:00
// NOTE: "delta" is later used to change fLastMousePosition.
// If for some reason no change should take effect, delta
// is to be set to (0, 0) so that fLastMousePosition is not
// adjusted. This way the relative mouse position to the
// item being changed (border during resizing, tab during
// sliding...) stays fixed when the mouse is moved so that
// changes are taking effect again.
// moving
if (fIsDragging) {
if (!(Flags() & B_NOT_MOVABLE)) {
BPoint oldLeftTop = fFrame.LeftTop();
fDesktop->MoveWindowBy(this, delta.x, delta.y);
// constrain delta to true change in size
delta = fFrame.LeftTop() - oldLeftTop;
} else
delta = BPoint(0, 0);
}
// resizing
if (fIsResizing) {
if (!(Flags() & B_NOT_RESIZABLE)) {
if (Flags() & B_NOT_V_RESIZABLE)
delta.y = 0;
if (Flags() & B_NOT_H_RESIZABLE)
delta.x = 0;
BPoint oldRightBottom = fFrame.RightBottom();
fDesktop->ResizeWindowBy(this, delta.x, delta.y);
// constrain delta to true change in size
delta = fFrame.RightBottom() - oldRightBottom;
} else
delta = BPoint(0, 0);
}
// sliding tab
if (fIsSlidingTab) {
float loc = TabLocation();
// TODO: change to [0:1]
loc += delta.x;
2006-05-24 11:05:25 +00:00
if (fDesktop->SetWindowTabLocation(this, loc))
delta.y = 0;
else
delta = BPoint(0, 0);
}
// NOTE: fLastMousePosition is currently only
// used for window moving/resizing/sliding the tab
fLastMousePosition += delta;
// the window was moved, it doesn't come to
// the front in FFM mode when the mouse is released
fActivateOnMouseUp = false;
// change focus in FFM mode
DesktopSettings desktopSettings(fDesktop);
if (desktopSettings.MouseMode() != B_NORMAL_MOUSE
&& !IsFocus() && !(Flags() & B_AVOID_FOCUS)) {
fDesktop->SetFocusWindow(this);
}
// 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());
}
}
// #pragma mark -
void
WindowLayer::WorkspaceActivated(int32 index, bool active)
{
if (!active)
fWindow->HandleDirectConnection(B_DIRECT_STOP);
BMessage activatedMsg(B_WORKSPACE_ACTIVATED);
activatedMsg.AddInt64("when", system_time());
activatedMsg.AddInt32("workspace", index);
activatedMsg.AddBool("active", active);
ServerWindow()->SendMessageToClient(&activatedMsg);
if (active)
fWindow->HandleDirectConnection(B_DIRECT_START | B_BUFFER_RESET);
}
void
WindowLayer::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
WindowLayer::Activated(bool active)
{
BMessage msg(B_WINDOW_ACTIVATED);
2005-11-15 01:42:58 +00:00
msg.AddBool("active", active);
ServerWindow()->SendMessageToClient(&msg);
}
//# pragma mark -
void
WindowLayer::SetTitle(const char* name, BRegion& dirty)
{
// rebuild the clipping for the title area
// and redraw it.
fTitle = name;
if (fDecorator) {
fDecorator->SetTitle(name, &dirty);
fBorderRegionValid = false;
// the border very likely changed
}
}
void
WindowLayer::SetFocus(bool focus)
2003-02-24 15:47:06 +00:00
{
// executed from Desktop thread
// it holds the clipping write lock,
// so the window thread cannot be
// accessing fIsFocus
BRegion* dirty = fRegionPool.GetRegion(fBorderRegion);
if (dirty) {
dirty->IntersectWith(&fVisibleRegion);
fDesktop->MarkDirty(*dirty);
fRegionPool.Recycle(dirty);
}
fIsFocus = focus;
if (fDecorator)
fDecorator->SetFocus(focus);
Activated(focus);
2003-02-24 15:47:06 +00:00
}
void
WindowLayer::SetHidden(bool hidden)
2003-02-24 15:47:06 +00:00
{
// the desktop takes care of dirty regions
if (fHidden != hidden) {
fHidden = hidden;
fTopLayer->SetHidden(hidden);
// TODO: anything else?
}
2003-02-24 15:47:06 +00:00
}
void
WindowLayer::SetMinimized(bool minimized)
{
if (minimized == fMinimized)
return;
fMinimized = minimized;
}
bool
WindowLayer::IsVisible() const
2003-02-24 15:47:06 +00:00
{
if (IsOffscreenWindow())
return true;
if (IsHidden())
return false;
/*
if (fVisibleRegion.CountRects() == 0)
return false;
*/
return fCurrentWorkspace >= 0 && fCurrentWorkspace < kWorkingList;
2003-02-24 15:47:06 +00:00
}
void
WindowLayer::SetSizeLimits(int32 minWidth, int32 maxWidth,
int32 minHeight, int32 maxHeight)
2003-02-24 15:47:06 +00:00
{
if (minWidth < 0)
minWidth = 0;
2005-02-28 20:23:51 +00:00
if (minHeight < 0)
minHeight = 0;
fMinWidth = minWidth;
fMaxWidth = maxWidth;
fMinHeight = minHeight;
fMaxHeight = maxHeight;
// give the Decorator a say in this too
if (fDecorator) {
fDecorator->GetSizeLimits(&fMinWidth, &fMinHeight,
&fMaxWidth, &fMaxHeight);
}
_ObeySizeLimits();
}
void
WindowLayer::GetSizeLimits(int32* minWidth, int32* maxWidth,
int32* minHeight, int32* maxHeight) const
{
*minWidth = fMinWidth;
*maxWidth = fMaxWidth;
*minHeight = fMinHeight;
*maxHeight = fMaxHeight;
}
bool
WindowLayer::SetTabLocation(float location, BRegion& dirty)
2005-11-28 23:36:59 +00:00
{
bool ret = false;
if (fDecorator) {
ret = fDecorator->SetTabLocation(location, &dirty);
// the border region changed if ret is true
2006-05-24 11:05:25 +00:00
fBorderRegionValid = fBorderRegionValid && !ret;
}
return ret;
}
2005-11-28 23:36:59 +00:00
float
WindowLayer::TabLocation() const
{
if (fDecorator)
return fDecorator->TabLocation();
return 0.0;
2005-11-28 23:36:59 +00:00
}
bool
WindowLayer::SetDecoratorSettings(const BMessage& settings, BRegion& dirty)
{
bool ret = false;
if (fDecorator) {
ret = fDecorator->SetSettings(settings, &dirty);
// the border region changed if ret is true
fBorderRegionValid = fBorderRegionValid && !ret;
}
return ret;
}
bool
WindowLayer::GetDecoratorSettings(BMessage* settings)
{
if (fDecorator)
return fDecorator->GetSettings(settings);
return false;
}
void
WindowLayer::SetLook(window_look look, BRegion* updateRegion)
{
if (fDecorator == NULL && look != B_NO_BORDER_WINDOW_LOOK) {
// we need a new decorator
fDecorator = gDecorManager.AllocateDecorator(fDesktop, fDrawingEngine,
Frame(), Title(), fLook, fFlags);
if (IsFocus())
fDecorator->SetFocus(true);
}
fLook = look;
fBorderRegionValid = false;
// the border very likely changed
fContentRegionValid = false;
// mabye a resize handle was added...
fEffectiveDrawingRegionValid = false;
// ...and therefor the drawing region is
// likely not valid anymore either
if (fDecorator != NULL) {
DesktopSettings settings(fDesktop);
fDecorator->SetLook(settings, look, updateRegion);
// we might need to resize the window!
fDecorator->GetSizeLimits(&fMinWidth, &fMinHeight, &fMaxWidth, &fMaxHeight);
_ObeySizeLimits();
}
if (look == B_NO_BORDER_WINDOW_LOOK) {
// we don't need a decorator for this window
delete fDecorator;
fDecorator = NULL;
}
}
void
2005-12-01 12:07:28 +00:00
WindowLayer::SetFeel(window_feel feel)
2005-02-28 20:23:51 +00:00
{
// 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();
2005-04-21 18:57:34 +00:00
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();
}
}
2005-11-14 17:39:33 +00:00
void
WindowLayer::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();
if (fDecorator == NULL)
return;
fDecorator->SetFlags(flags, updateRegion);
fBorderRegionValid = false;
// the border might have changed (smaller/larger tab)
// we might need to resize the window!
if (fDecorator) {
fDecorator->GetSizeLimits(&fMinWidth, &fMinHeight, &fMaxWidth, &fMaxHeight);
_ObeySizeLimits();
}
}
/*!
\brief Returns wether or not the window is visible on the specified
workspace.
A modal or floating window may be visible on a workscreen if one
of its subset windows is visible there.
*/
bool
WindowLayer::InWorkspace(int32 index) const
{
if (IsNormal())
return (fWorkspaces & (1UL << index)) != 0;
if (fFeel == B_MODAL_ALL_WINDOW_FEEL
|| fFeel == B_FLOATING_ALL_WINDOW_FEEL)
return true;
if (fFeel == B_FLOATING_APP_WINDOW_FEEL)
return ServerWindow()->App()->InWorkspace(index);
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 index == fDesktop->CurrentWorkspace();
}
return (workspaces & (1UL << index)) != 0;
}
if (fFeel == B_MODAL_SUBSET_WINDOW_FEEL
|| fFeel == B_FLOATING_SUBSET_WINDOW_FEEL) {
for (int32 i = 0; i < fSubsets.CountItems(); i++) {
WindowLayer* window = fSubsets.ItemAt(i);
if (!window->IsHidden() && window->InWorkspace(index))
return true;
}
}
return false;
}
bool
WindowLayer::SupportsFront()
{
if (fFeel == kDesktopWindowFeel
|| fFeel == kMenuWindowFeel
|| (fFlags & B_AVOID_FRONT) != 0)
return false;
return true;
}
bool
WindowLayer::IsModal() const
{
return IsModalFeel(fFeel);
}
bool
WindowLayer::IsFloating() const
{
return IsFloatingFeel(fFeel);
}
bool
WindowLayer::IsNormal() const
{
return !IsFloatingFeel(fFeel) && !IsModalFeel(fFeel);
}
bool
WindowLayer::HasModal() const
{
for (WindowLayer* 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.
*/
WindowLayer*
WindowLayer::Backmost(WindowLayer* window, int32 workspace)
{
if (workspace == -1)
workspace = fCurrentWorkspace;
// 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 windows that's in front of the frontmost position
this window can get.
Returns NULL if this window can be the frontmost window.
*/
WindowLayer*
WindowLayer::Frontmost(WindowLayer* first, int32 workspace)
{
if (workspace == -1)
workspace = fCurrentWorkspace;
if (fFeel == kDesktopWindowFeel)
return first ? first : NextWindow(workspace);
if (first == NULL)
first = NextWindow(workspace);
for (WindowLayer* window = first; window != NULL;
window = window->NextWindow(workspace)) {
if (window->IsHidden() || window == this)
continue;
if (window->HasInSubset(this))
return window;
}
return NULL;
}
bool
WindowLayer::AddToSubset(WindowLayer* window)
{
return fSubsets.AddItem(window);
}
void
WindowLayer::RemoveFromSubset(WindowLayer* window)
{
fSubsets.RemoveItem(window);
}
bool
WindowLayer::HasInSubset(const WindowLayer* window) const
{
if (window == NULL || fFeel == window->Feel() || fFeel == B_NORMAL_WINDOW_FEEL)
return false;
2006-04-13 15:18:16 +00:00
// we have a few special feels that have a fixed order
2006-04-13 15:18:16 +00:00
const int32 feel[] = {kWindowScreenFeel, kMenuWindowFeel,
B_MODAL_ALL_WINDOW_FEEL, B_FLOATING_ALL_WINDOW_FEEL, 0};
2006-04-13 15:18:16 +00:00
for (int32 order = 0; feel[order]; order++) {
if (fFeel == feel[order])
return true;
if (window->Feel() == feel[order])
return false;
}
if (fFeel == B_FLOATING_APP_WINDOW_FEEL
|| fFeel == B_MODAL_APP_WINDOW_FEEL)
return window->ServerWindow()->App() == ServerWindow()->App();
return fSubsets.HasItem(window);
}
bool
WindowLayer::SameSubset(WindowLayer* window)
{
// TODO: this is probably not needed at all, but it doesn't hurt to have it in svn
if (fFeel == B_MODAL_ALL_WINDOW_FEEL || window->Feel() == B_MODAL_ALL_WINDOW_FEEL)
return fFeel == window->Feel();
if (fFeel == B_MODAL_APP_WINDOW_FEEL || window->Feel() == B_MODAL_APP_WINDOW_FEEL)
return ServerWindow()->App() == window->ServerWindow()->App();
if (fFeel == B_MODAL_SUBSET_WINDOW_FEEL) {
// we basically need to check if the subsets have anything in common
for (int32 i = fSubsets.CountItems(); i-- > 0;) {
if (window->HasInSubset(fSubsets.ItemAt(i)))
return true;
}
}
if (window->Feel() == B_MODAL_SUBSET_WINDOW_FEEL) {
for (int32 i = window->fSubsets.CountItems(); i-- > 0;) {
if (HasInSubset(window->fSubsets.ItemAt(i)))
return true;
}
}
2005-07-05 16:17:16 +00:00
return false;
}
uint32
WindowLayer::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)
return ServerWindow()->App()->Workspaces();
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;
for (int32 i = 0; i < fSubsets.CountItems(); i++) {
WindowLayer* window = fSubsets.ItemAt(i);
if (!window->IsHidden())
workspaces |= window->Workspaces();
}
return workspaces;
}
return 0;
}
// #pragma mark - static
2005-07-05 16:17:16 +00:00
/*static*/
bool
WindowLayer::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
WindowLayer::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;
}
/*static*/
bool
WindowLayer::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
WindowLayer::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
WindowLayer::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
| kWorkspacesWindowFlag
| kWindowScreenFlag;
}
/*static*/
uint32
WindowLayer::ValidWindowFlags(window_feel feel)
{
uint32 flags = ValidWindowFlags();
if (IsModalFeel(feel))
return flags & ~(B_AVOID_FOCUS | B_AVOID_FRONT);
return flags;
}
// #pragma mark - private
// _ShiftPartOfRegion
void
WindowLayer::_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
WindowLayer::_TriggerContentRedraw(BRegion& dirtyContentRegion)
{
if (IsVisible() && dirtyContentRegion.CountRects() > 0) {
// put this into the pending dirty region
// to eventually trigger a client redraw
bool wasExpose = fPendingUpdateSession.IsExpose();
BRegion* backgroundClearingRegion = &dirtyContentRegion;
_TransferToUpdateSession(&dirtyContentRegion);
if (fPendingUpdateSession.IsExpose()) {
if (!fContentRegionValid)
_UpdateContentRegion();
if (!wasExpose) {
// there was suddenly added a dirty region
// caused by exposing content, we need to clear
// the entire background
backgroundClearingRegion = &fPendingUpdateSession.DirtyRegion();
}
if (fDrawingEngine->LockParallelAccess()) {
fDrawingEngine->SuspendAutoSync();
fTopLayer->Draw(fDrawingEngine, backgroundClearingRegion,
&fContentRegion, true);
fDrawingEngine->Sync();
fDrawingEngine->UnlockParallelAccess();
}
}
}
}
void
WindowLayer::_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
if (!fDecorator)
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 = fDecorator->GetDrawingEngine();
if (dirtyBorderRegion->CountRects() > 0 && engine->LockParallelAccess()) {
engine->ConstrainClippingRegion(dirtyBorderRegion);
fDecorator->Draw(dirtyBorderRegion->Frame());
engine->UnlockParallelAccess();
}
fRegionPool.Recycle(dirtyBorderRegion);
}
//static rgb_color sPendingColor;
//static rgb_color sCurrentColor;
/*!
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
WindowLayer::_TransferToUpdateSession(BRegion* contentDirtyRegion)
{
if (contentDirtyRegion->CountRects() <= 0)
return;
//fDrawingEngine->FillRegion(*contentDirtyRegion, RGBColor(sPendingColor));
//snooze(10000);
// add to pending
fPendingUpdateSession.SetUsed(true);
// if (!fPendingUpdateSession.IsExpose())
fPendingUpdateSession.AddCause(fDirtyCause);
fPendingUpdateSession.Include(contentDirtyRegion);
// clip pending update session from current
// update session, it makes no sense to draw stuff
// already needing a redraw anyways. Theoretically,
// this could be done smarter (clip layers from pending
// that have not yet been redrawn in the current update
// session)
if (fCurrentUpdateSession.IsUsed() && fCurrentUpdateSession.IsExpose()) {
fCurrentUpdateSession.Exclude(contentDirtyRegion);
fEffectiveDrawingRegionValid = false;
}
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
}
}
// _SendUpdateMessage
void
WindowLayer::_SendUpdateMessage()
{
BMessage message(_UPDATE_);
ServerWindow()->SendMessageToClient(&message);
fUpdateRequested = true;
}
void
WindowLayer::BeginUpdate(BPrivate::PortLink& link)
{
// NOTE: since we might "shift" parts of the
// internal dirty regions from the desktop thread
// in response to WindowLayer::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) {
// make the pending update session the current update session
// TODO: the toggling between the update sessions is too
// expensive, optimize with some pointer tricks
fCurrentUpdateSession = fPendingUpdateSession;
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
// (ViewLayer::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());
//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, RGBColor(sCurrentColor));
//snooze(10000);
link.StartMessage(B_OK);
// append the current window geometry to the
// message, the client will need it
link.Attach<BPoint>(fFrame.LeftTop());
link.Attach<float>(fFrame.Width());
link.Attach<float>(fFrame.Height());
// append he update rect in screen coords
link.Attach<BRect>(dirty->Frame());
// find and attach all views that intersect with
// the dirty region
fTopLayer->AddTokensForLayersInRegion(link, *dirty, &fContentRegion);
// mark the end of the token "list"
link.Attach<int32>(B_NULL_TOKEN);
link.Flush();
if (!fCurrentUpdateSession.IsExpose() && fDrawingEngine->LockParallelAccess()) {
//fDrawingEngine->FillRegion(dirty, RGBColor(255, 0, 0, 255));
fDrawingEngine->SuspendAutoSync();
fTopLayer->Draw(fDrawingEngine, dirty,
&fContentRegion, true);
fDrawingEngine->Sync();
fDrawingEngine->UnlockParallelAccess();
} // else the background was cleared already
fRegionPool.Recycle(dirty);
} else {
printf("BeginUpdate() but no update requested!!\n");
link.StartMessage(B_ERROR);
link.Flush();
fprintf(stderr, "WindowLayer::BeginUpdate() - no update requested!\n");
}
}
void
WindowLayer::EndUpdate()
{
// NOTE: see comment in _BeginUpdate()
if (fInUpdate) {
fCurrentUpdateSession.SetUsed(false);
fInUpdate = false;
fEffectiveDrawingRegionValid = false;
}
if (fPendingUpdateSession.IsUsed()) {
// send this to client
_SendUpdateMessage();
} else {
fUpdateRequested = false;
}
}
void
WindowLayer::_UpdateContentRegion()
{
fContentRegion.Set(fFrame);
// resize handle
if (fDecorator) {
if (!fBorderRegionValid)
GetBorderRegion(&fBorderRegion);
fContentRegion.Exclude(&fBorderRegion);
}
fContentRegionValid = true;
}
click_type
WindowLayer::_ActionFor(const BMessage* msg) const
{
if (fDecorator == NULL)
return DEC_NONE;
BPoint where;
if (msg->FindPoint("where", &where) != B_OK)
return DEC_NONE;
int32 buttons;
if (msg->FindInt32("buttons", &buttons) != B_OK)
buttons = 0;
int32 modifiers;
if (msg->FindInt32("modifiers", &modifiers) != B_OK)
modifiers = 0;
return fDecorator->Clicked(where, buttons, modifiers);
}
void
WindowLayer::_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(xDiff, yDiff, NULL);
}
// #pragma mark - UpdateSession
// constructor
WindowLayer::UpdateSession::UpdateSession()
: fDirtyRegion(),
fInUse(false),
fCause(0)
{
}
// destructor
WindowLayer::UpdateSession::~UpdateSession()
{
}
// Include
void
WindowLayer::UpdateSession::Include(BRegion* additionalDirty)
{
fDirtyRegion.Include(additionalDirty);
}
void
WindowLayer::UpdateSession::Exclude(BRegion* dirtyInNextSession)
{
fDirtyRegion.Exclude(dirtyInNextSession);
}
void
WindowLayer::UpdateSession::MoveBy(int32 x, int32 y)
{
fDirtyRegion.OffsetBy(x, y);
}
void
WindowLayer::UpdateSession::SetUsed(bool used)
{
fInUse = used;
if (!fInUse) {
fDirtyRegion.MakeEmpty();
fCause = 0;
}
}
void
WindowLayer::UpdateSession::AddCause(uint8 cause)
{
fCause |= cause;
}
WindowLayer::UpdateSession&
WindowLayer::UpdateSession::operator=(const WindowLayer::UpdateSession& other)
{
fDirtyRegion = other.fDirtyRegion;
fInUse = other.fInUse;
fCause = other.fCause;
return *this;
}