⛏️ index : haiku.git

/*
 * Copyright (c) 2001-2015, Haiku, Inc.
 * Distributed under the terms of the MIT license.
 *
 * Authors:
 *		DarkWyrm <bpmagic@columbus.rr.com>
 *		Adi Oanca <adioanca@gmail.com>
 *		Axel Dörfler, axeld@pinc-software.de
 *		Stephan Aßmus <superstippi@gmx.de>
 *		Marcus Overhagen <marcus@overhagen.de>
 *		Adrien Destugues <pulkomandy@pulkomandy.tk
 *		Julian Harnath <julian.harnath@rwth-aachen.de>
 *		Joseph Groover <looncraz@looncraz.net>
 */
#include "View.h"

#include <new>
#include <stdio.h>

#include "AlphaMask.h"
#include "Desktop.h"
#include "DrawingEngine.h"
#include "DrawState.h"
#include "Layer.h"
#include "Overlay.h"
#include "ServerApp.h"
#include "ServerBitmap.h"
#include "ServerCursor.h"
#include "ServerPicture.h"
#include "ServerWindow.h"
#include "Window.h"

#include "BitmapHWInterface.h"
#include "drawing_support.h"

#include <List.h>
#include <Message.h>
#include <PortLink.h>
#include <View.h> // for resize modes
#include <WindowPrivate.h>

#include <GradientLinear.h>
#include <GradientRadial.h>
#include <GradientRadialFocus.h>
#include <GradientDiamond.h>
#include <GradientConic.h>


using std::nothrow;


void
resize_frame(IntRect& frame, uint32 resizingMode, int32 x, int32 y)
{
	// follow with left side
	if ((resizingMode & 0x0F00U) == _VIEW_RIGHT_ << 8)
		frame.left += x;
	else if ((resizingMode & 0x0F00U) == _VIEW_CENTER_ << 8)
		frame.left += x / 2;

	// follow with right side
	if ((resizingMode & 0x000FU) == _VIEW_RIGHT_)
		frame.right += x;
	else if ((resizingMode & 0x000FU) == _VIEW_CENTER_)
		frame.right += x / 2;

	// follow with top side
	if ((resizingMode & 0xF000U) == _VIEW_BOTTOM_ << 12)
		frame.top += y;
	else if ((resizingMode & 0xF000U) == _VIEW_CENTER_ << 12)
		frame.top += y / 2;

	// follow with bottom side
	if ((resizingMode & 0x00F0U) == _VIEW_BOTTOM_ << 4)
		frame.bottom += y;
	else if ((resizingMode & 0x00F0U) == _VIEW_CENTER_ << 4)
		frame.bottom += y / 2;
}


//	#pragma mark -


View::View(IntRect frame, IntPoint scrollingOffset, const char* name,
		int32 token, uint32 resizeMode, uint32 flags)
	:
	fName(name),
	fToken(token),

	fFrame(frame),
	fScrollingOffset(scrollingOffset),

	fViewColor((rgb_color){ 255, 255, 255, 255 }),
	fWhichViewColor(B_NO_COLOR),
	fWhichViewColorTint(B_NO_TINT),
	fViewBitmap(NULL),
	fBitmapResizingMode(0),
	fBitmapOptions(0),

	fResizeMode(resizeMode),
	fFlags(flags),

	// Views start visible by default
	fHidden(false),
	fVisible(true),
	fBackgroundDirty(true),
	fIsDesktopBackground(false),

	fEventMask(0),
	fEventOptions(0),

	fWindow(NULL),
	fParent(NULL),

	fFirstChild(NULL),
	fPreviousSibling(NULL),
	fNextSibling(NULL),
	fLastChild(NULL),

	fCursor(NULL),
	fPicture(NULL),

	fLocalClipping((BRect)Bounds()),
	fScreenClipping(),
	fScreenClippingValid(false),
	fUserClipping(NULL),
	fScreenAndUserClipping(NULL)
{
	if (fDrawState.IsSet())
		fDrawState->SetSubPixelPrecise(fFlags & B_SUBPIXEL_PRECISE);
}


View::~View()
{
	// iterate over children and delete each one
	View* view = fFirstChild;
	while (view) {
		View* toast = view;
		view = view->fNextSibling;
		delete toast;
	}
}


IntRect
View::Bounds() const
{
	IntRect bounds(fScrollingOffset.x, fScrollingOffset.y,
		fScrollingOffset.x + fFrame.Width(),
		fScrollingOffset.y + fFrame.Height());
	return bounds;
}


void
View::ConvertToVisibleInTopView(IntRect* bounds) const
{
	*bounds = *bounds & Bounds();
	// NOTE: this step is necessary even if we don't have a parent!
	bounds->OffsetBy(fFrame.left - fScrollingOffset.x,
		fFrame.top - fScrollingOffset.y);

	if (fParent)
		fParent->ConvertToVisibleInTopView(bounds);
}


void
View::AttachedToWindow(::Window* window)
{
	fWindow = window;

	// an ugly hack to detect the desktop background
	if (window->Feel() == kDesktopWindowFeel && Parent() == TopView())
		fIsDesktopBackground = true;

	// insert view into local token space
	if (fWindow != NULL) {
		fWindow->ServerWindow()->App()->ViewTokens().SetToken(fToken,
			B_HANDLER_TOKEN, this);
	}

	// attach child views as well
	for (View* child = FirstChild(); child; child = child->NextSibling())
		child->AttachedToWindow(window);
}


void
View::DetachedFromWindow()
{
	// remove view from local token space
	if (fWindow != NULL && fWindow->ServerWindow()->App() != NULL)
		fWindow->ServerWindow()->App()->ViewTokens().RemoveToken(fToken);

	fWindow = NULL;
	// detach child views as well
	for (View* child = FirstChild(); child; child = child->NextSibling())
		child->DetachedFromWindow();
}


// #pragma mark -


DrawingEngine*
View::GetDrawingEngine() const
{
	return Window()->GetDrawingEngine();
}


ServerPicture*
View::GetPicture(int32 token) const
{
	return Window()->ServerWindow()->App()->GetPicture(token);
}


void
View::ResyncDrawState()
{
	return Window()->ServerWindow()->ResyncDrawState();
}


void
View::UpdateCurrentDrawingRegion()
{
	return Window()->ServerWindow()->UpdateCurrentDrawingRegion();
}


void
View::AddChild(View* view)
{
	if (view->fParent) {
		printf("View::AddChild() - View already has a parent\n");
		return;
	}

	view->fParent = this;

	if (!fLastChild) {
		// no children yet
		fFirstChild = view;
	} else {
		// append view to formerly last child
		fLastChild->fNextSibling = view;
		view->fPreviousSibling = fLastChild;
	}
	fLastChild = view;

	view->UpdateVisibleDeep(fVisible);

	if (view->IsVisible())
		RebuildClipping(false);

	if (fWindow) {
		view->AttachedToWindow(fWindow);

		if (view->IsVisible()) {
			// trigger redraw
			IntRect clippedFrame = view->Frame();
			ConvertToVisibleInTopView(&clippedFrame);

			BRegion dirty;
			dirty.Set((clipping_rect)clippedFrame);
			fWindow->MarkContentDirtyAsync(dirty);
		}
	}
}


bool
View::RemoveChild(View* view)
{
	if (view == NULL || view->fParent != this) {
		printf("View::RemoveChild(%p - %s) - View is not child of "
			"this (%p) view!\n", view, view ? view->Name() : NULL, this);
		return false;
	}

	view->fParent = NULL;

	if (fLastChild == view)
		fLastChild = view->fPreviousSibling;
		// view->fNextSibling would be NULL

	if (fFirstChild == view )
		fFirstChild = view->fNextSibling;
		// view->fPreviousSibling would be NULL

	// connect child before and after view
	if (view->fPreviousSibling)
		view->fPreviousSibling->fNextSibling = view->fNextSibling;

	if (view->fNextSibling)
		view->fNextSibling->fPreviousSibling = view->fPreviousSibling;

	// view has no siblings anymore
	view->fPreviousSibling = NULL;
	view->fNextSibling = NULL;

	if (view->IsVisible()) {
		Overlay* overlay = view->_Overlay();
		if (overlay != NULL)
			overlay->Hide();

		RebuildClipping(false);
	}

	if (fWindow) {
		view->DetachedFromWindow();

		if (fVisible && view->IsVisible()) {
			// trigger redraw
			IntRect clippedFrame = view->Frame();
			ConvertToVisibleInTopView(&clippedFrame);

			BRegion dirty;
			dirty.Set((clipping_rect)clippedFrame);
			fWindow->MarkContentDirtyAsync(dirty);
		}
	}

	return true;
}


View*
View::TopView()
{
	// returns the top level view of the hirarchy,
	// it doesn't have to be the top level of a window

	if (fParent)
		return fParent->TopView();

	return this;
}


uint32
View::CountChildren(bool deep) const
{
	uint32 count = 0;
	for (View* child = FirstChild(); child; child = child->NextSibling()) {
		count++;
		if (deep) {
			count += child->CountChildren(deep);
		}
	}
	return count;
}


void
View::CollectTokensForChildren(BList* tokenMap) const
{
	for (View* child = FirstChild(); child; child = child->NextSibling()) {
		tokenMap->AddItem((void*)child);
		child->CollectTokensForChildren(tokenMap);
	}
}


#if 0
bool
View::MarkAt(DrawingEngine* engine, const BPoint& where, int32 level)
{
	BRect rect(fFrame.left, fFrame.top, fFrame.right, fFrame.bottom);

	if (Parent() != NULL) {
		Parent()->ConvertToScreen(&rect);
		if (!rect.Contains(where))
			return false;

		engine->StrokeRect(rect, (rgb_color){level * 30, level * 30, level * 30});
	}


	bool found = false;
	for (View* child = FirstChild(); child; child = child->NextSibling()) {
		found |= child->MarkAt(engine, where, level + 1);
	}

	if (!found) {
		rgb_color color = {0};
		switch (level % 2) {
			case 0: color.green = rand() % 256; break;
			case 1: color.blue = rand() % 256; break;
		}

		rect.InsetBy(1, 1);
		//engine->FillRegion(fLocalClipping, (rgb_color){255, 255, 0, 10});
		engine->StrokeRect(rect, color);
		rect.InsetBy(1, 1);
		engine->StrokeRect(rect, color);
	}

	return true;
}
#endif


void
View::FindViews(uint32 flags, BObjectList<View>& list, int32& left)
{
	if ((Flags() & flags) == flags) {
		list.AddItem(this);
		left--;
		return;
	}

	for (View* child = FirstChild(); child; child = child->NextSibling()) {
		child->FindViews(flags, list, left);
		if (left == 0)
			break;
	}
}


bool
View::HasView(View* view)
{
	if (view == this)
		return true;

	for (View* child = FirstChild(); child; child = child->NextSibling()) {
		if (child->HasView(view))
			return true;
	}

	return false;
}


View*
View::ViewAt(const BPoint& where)
{
	if (!fVisible)
		return NULL;

	IntRect frame = Frame();
	if (Parent() != NULL)
		Parent()->LocalToScreenTransform().Apply(&frame);

	if (!frame.Contains(where))
		return NULL;

	for (View* child = FirstChild(); child; child = child->NextSibling()) {
		View* view = child->ViewAt(where);
		if (view != NULL)
			return view;
	}

	return this;
}


// #pragma mark -


void
View::SetName(const char* string)
{
	fName.SetTo(string);
}


void
View::SetFlags(uint32 flags)
{
	uint32 oldFlags = fFlags;
	fFlags = flags;

	// Child view with B_TRANSPARENT_BACKGROUND flag change clipping of
	// parent view.
	if (fParent != NULL
		&& IsVisible()
		&& (((oldFlags & B_TRANSPARENT_BACKGROUND) != 0)
			!= ((fFlags & B_TRANSPARENT_BACKGROUND) != 0))) {
		fParent->RebuildClipping(false);
	}

	fDrawState->SetSubPixelPrecise(fFlags & B_SUBPIXEL_PRECISE);
}


void
View::SetViewBitmap(ServerBitmap* bitmap, IntRect sourceRect,
	IntRect destRect, int32 resizingMode, int32 options)
{
	if (fViewBitmap != NULL) {
		Overlay* overlay = _Overlay();

		if (bitmap != NULL) {
			// take over overlay token from current overlay (if it has any)
			Overlay* newOverlay = bitmap->Overlay();

			if (overlay != NULL && newOverlay != NULL)
				newOverlay->TakeOverToken(overlay);
		} else if (overlay != NULL)
			overlay->Hide();
	}

	fViewBitmap.SetTo(bitmap, false);
	fBitmapSource = sourceRect;
	fBitmapDestination = destRect;
	fBitmapResizingMode = resizingMode;
	fBitmapOptions = options;

	_UpdateOverlayView();
}


::Overlay*
View::_Overlay() const
{
	if (fViewBitmap == NULL)
		return NULL;

	return fViewBitmap->Overlay();
}


void
View::_UpdateOverlayView() const
{
	Overlay* overlay = _Overlay();
	if (overlay == NULL)
		return;

	IntRect destination = fBitmapDestination;
	LocalToScreenTransform().Apply(&destination);

	overlay->Configure(fBitmapSource, destination);
}


/*!
	This method is called whenever the window is resized or moved - would
	be nice to have a better solution for this, though.
*/
void
View::UpdateOverlay()
{
	if (!IsVisible())
		return;

	if (_Overlay() != NULL) {
		_UpdateOverlayView();
	} else {
		// recursively ask children of this view

		for (View* child = FirstChild(); child; child = child->NextSibling()) {
			child->UpdateOverlay();
		}
	}
}


// #pragma mark -


void
View::_LocalToScreenTransform(SimpleTransform& transform) const
{
	const View* view = this;
	int32 offsetX = 0;
	int32 offsetY = 0;
	do {
		offsetX += view->fFrame.left - view->fScrollingOffset.x;
		offsetY += view->fFrame.top  - view->fScrollingOffset.y;
		view = view->fParent;
	} while (view != NULL);

	transform.AddOffset(offsetX, offsetY);
}


void
View::_ScreenToLocalTransform(SimpleTransform& transform) const
{
	const View* view = this;
	int32 offsetX = 0;
	int32 offsetY = 0;
	do {
		offsetX += view->fScrollingOffset.x - view->fFrame.left;
		offsetY += view->fScrollingOffset.y - view->fFrame.top;
		view = view->fParent;
	} while (view != NULL);

	transform.AddOffset(offsetX, offsetY);
}


// #pragma mark -


void
View::MoveBy(int32 x, int32 y, BRegion* dirtyRegion)
{
	if (x == 0 && y == 0)
		return;

	fFrame.OffsetBy(x, y);

	// to move on screen, we must not be hidden and we must have a parent
	if (fVisible && fParent && dirtyRegion) {
#if 1
// based on redraw on new location
		// the place were we are now visible
		IntRect newVisibleBounds(Bounds());
		// we can use the frame of the old
		// local clipping to see which parts need invalidation
		IntRect oldVisibleBounds(newVisibleBounds);
		oldVisibleBounds.OffsetBy(-x, -y);
		LocalToScreenTransform().Apply(&oldVisibleBounds);

		ConvertToVisibleInTopView(&newVisibleBounds);

		dirtyRegion->Include((clipping_rect)oldVisibleBounds);
		// newVisibleBounds already is in screen coords
		dirtyRegion->Include((clipping_rect)newVisibleBounds);
#else
// blitting version, invalidates
// old contents
		IntRect oldVisibleBounds(Bounds());
		IntRect newVisibleBounds(oldVisibleBounds);
		oldVisibleBounds.OffsetBy(-x, -y);
		LocalToScreenTransform().Apply(&oldVisibleBounds);

		// NOTE: using ConvertToVisibleInTopView()
		// instead of ConvertToScreen()! see below
		ConvertToVisibleInTopView(&newVisibleBounds);

		newVisibleBounds.OffsetBy(-x, -y);

		// clipping oldVisibleBounds to newVisibleBounds
		// makes sure we don't copy parts hidden under
		// parent views
		BRegion* region = fWindow->GetRegion();
		if (region) {
			region->Set(oldVisibleBounds & newVisibleBounds);
			fWindow->CopyContents(region, x, y);

			region->Set(oldVisibleBounds);
			newVisibleBounds.OffsetBy(x, y);
			region->Exclude((clipping_rect)newVisibleBounds);
			dirtyRegion->Include(dirty);

			fWindow->RecycleRegion(region);
		}

#endif
	}

	if (!fParent) {
		// the top view's screen clipping does not change,
		// because no parts are clipped away from parent
		// views
		_MoveScreenClipping(x, y, true);
	} else {
		// parts might have been revealed from underneath
		// the parent, or might now be hidden underneath
		// the parent, this is taken care of when building
		// the screen clipping
		InvalidateScreenClipping();
	}
}


void
View::ResizeBy(int32 x, int32 y, BRegion* dirtyRegion)
{
	if (x == 0 && y == 0)
		return;

	fFrame.right += x;
	fFrame.bottom += y;

	if (fVisible && dirtyRegion) {
		IntRect oldBounds(Bounds());
		oldBounds.right -= x;
		oldBounds.bottom -= y;

		BRegion* dirty = fWindow->GetRegion();
		if (!dirty)
			return;

		dirty->Set((clipping_rect)Bounds());
		dirty->Include((clipping_rect)oldBounds);

		if (!(fFlags & B_FULL_UPDATE_ON_RESIZE)) {
			// the dirty region is just the difference of
			// old and new bounds
			dirty->Exclude((clipping_rect)(oldBounds & Bounds()));
		}

		InvalidateScreenClipping();

		if (dirty->CountRects() > 0) {
			if ((fFlags & B_DRAW_ON_CHILDREN) == 0) {
				// exclude children, they are expected to
				// include their own dirty regions in ParentResized()
				for (View* child = FirstChild(); child;
						child = child->NextSibling()) {
					if (!child->IsVisible()
						|| (child->fFlags & B_TRANSPARENT_BACKGROUND) != 0) {
						continue;
					}
					IntRect previousChildVisible(
						child->Frame() & oldBounds & Bounds());
					if (dirty->Frame().Intersects(previousChildVisible)) {
						dirty->Exclude((clipping_rect)previousChildVisible);
					}
				}
			}

			LocalToScreenTransform().Apply(dirty);
			dirtyRegion->Include(dirty);
		}
		fWindow->RecycleRegion(dirty);
	}

	// layout the children
	for (View* child = FirstChild(); child; child = child->NextSibling())
		child->ParentResized(x, y, dirtyRegion);

	// view bitmap
	if (fViewBitmap != NULL)
		resize_frame(fBitmapDestination, fBitmapResizingMode, x, y);

	// at this point, children are at their new locations,
	// so we can rebuild the clipping
	// TODO: when the implementation of Hide() and Show() is
	// complete, see if this should be avoided
	RebuildClipping(false);
}


void
View::ParentResized(int32 x, int32 y, BRegion* dirtyRegion)
{
	IntRect newFrame = fFrame;
	resize_frame(newFrame, fResizeMode & 0x0000ffff, x, y);

	if (newFrame != fFrame) {
		// careful, MoveBy will change fFrame
		int32 widthDiff = (int32)(newFrame.Width() - fFrame.Width());
		int32 heightDiff = (int32)(newFrame.Height() - fFrame.Height());

		MoveBy(newFrame.left - fFrame.left,
			newFrame.top - fFrame.top, dirtyRegion);

		ResizeBy(widthDiff, heightDiff, dirtyRegion);
	} else {
		// TODO: this covers the fact that our screen clipping might change
		// when the parent changes its size, even though our frame stays
		// the same - there might be a way to test for this, but axeld doesn't
		// know, stippi should look into this when he's back :)
		InvalidateScreenClipping();
	}
}


void
View::ScrollBy(int32 x, int32 y, BRegion* dirtyRegion)
{
	if (!fVisible || !fWindow) {
		fScrollingOffset.x += x;
		fScrollingOffset.y += y;
		return;
	}

	// blitting version, invalidates
	// old contents

	// remember old bounds for tracking dirty region
	IntRect oldBounds(Bounds());

	// NOTE: using ConvertToVisibleInTopView()
	// instead of ConvertToScreen(), this makes
	// sure we don't try to move or invalidate an
	// area hidden underneath the parent view
	ConvertToVisibleInTopView(&oldBounds);

	// find the area of the view that can be scrolled,
	// contents are shifted in the opposite direction from scrolling
	IntRect stillVisibleBounds(oldBounds);
	stillVisibleBounds.OffsetBy(x, y);
	stillVisibleBounds = stillVisibleBounds & oldBounds;

	fScrollingOffset.x += x;
	fScrollingOffset.y += y;

	// do the blit, this will make sure
	// that other more complex dirty regions
	// are taken care of
	BRegion* copyRegion = fWindow->GetRegion();
	if (!copyRegion)
		return;
	copyRegion->Set((clipping_rect)stillVisibleBounds);
	fWindow->CopyContents(copyRegion, -x, -y);

	// find the dirty region as far as we are
	// concerned
	BRegion* dirty = copyRegion;
		// reuse copyRegion and call it dirty

	dirty->Set((clipping_rect)oldBounds);
	stillVisibleBounds.OffsetBy(-x, -y);
	dirty->Exclude((clipping_rect)stillVisibleBounds);
	dirtyRegion->Include(dirty);

	fWindow->RecycleRegion(dirty);

	// the screen clipping of this view and it's
	// childs is no longer valid
	InvalidateScreenClipping();
	RebuildClipping(false);
}


void
View::CopyBits(IntRect src, IntRect dst, BRegion& windowContentClipping)
{
	if (!fVisible || !fWindow)
		return;

	// TODO: figure out what to do when we have a transform which is not
	// a dilation
	BAffineTransform transform = CurrentState()->CombinedTransform();
	if (!transform.IsIdentity() && transform.IsDilation()) {
		BPoint points[4] = { src.LeftTop(), src.RightBottom(),
							 dst.LeftTop(), dst.RightBottom() };
		transform.Apply(&points[0], 4);
		src.Set(points[0].x, points[0].y, points[1].x, points[1].y);
		dst.Set(points[2].x, points[2].y, points[3].x, points[3].y);
	}

	// TODO: confirm that in R5 this call is affected by origin and scale

	// blitting version

	int32 xOffset = dst.left - src.left;
	int32 yOffset = dst.top - src.top;

	// figure out which part can be blittet
	IntRect visibleSrc(src);
	ConvertToVisibleInTopView(&visibleSrc);

	IntRect visibleSrcAtDest(src);
	visibleSrcAtDest.OffsetBy(xOffset, yOffset);
	ConvertToVisibleInTopView(&visibleSrcAtDest);

	// clip src to visible at dest
	visibleSrcAtDest.OffsetBy(-xOffset, -yOffset);
	visibleSrc = visibleSrc & visibleSrcAtDest;

	// do the blit, this will make sure
	// that other more complex dirty regions
	// are taken care of
	BRegion* copyRegion = fWindow->GetRegion();
	if (!copyRegion)
		return;

	// move src rect to destination here for efficiency reasons
	visibleSrc.OffsetBy(xOffset, yOffset);

	// we need to interstect the copyRegion two times, onces
	// at the source and once at the destination (here done
	// the other way arround but it doesn't matter)
	// the reason for this is that we are not supposed to visually
	// copy children in the source rect and neither to copy onto
	// children in the destination rect...
	copyRegion->Set((clipping_rect)visibleSrc);
	BRegion *screenAndUserClipping
		= &ScreenAndUserClipping(&windowContentClipping);
	copyRegion->IntersectWith(screenAndUserClipping);
	copyRegion->OffsetBy(-xOffset, -yOffset);
	copyRegion->IntersectWith(screenAndUserClipping);

	// do the actual blit
	fWindow->CopyContents(copyRegion, xOffset, yOffset);

	// find the dirty region as far as we are concerned
	IntRect dirtyDst(dst);
	ConvertToVisibleInTopView(&dirtyDst);

	BRegion* dirty = fWindow->GetRegion();
	if (!dirty) {
		fWindow->RecycleRegion(copyRegion);
		return;
	}

	// offset copyRegion to destination again
	copyRegion->OffsetBy(xOffset, yOffset);
	// start with destination given by user
	dirty->Set((clipping_rect)dirtyDst);
	// exclude the part that we could copy
	dirty->Exclude(copyRegion);

	dirty->IntersectWith(screenAndUserClipping);
	fWindow->MarkContentDirty(*dirty, *dirty);

	fWindow->RecycleRegion(dirty);
	fWindow->RecycleRegion(copyRegion);
}


// #pragma mark -


void
View::ColorUpdated(color_which which, rgb_color color)
{
	float tint = B_NO_TINT;

	if (fWhichViewColor == which)
		SetViewColor(tint_color(color, fWhichViewColorTint));

	if (CurrentState()->HighUIColor(&tint) == which)
		CurrentState()->SetHighColor(tint_color(color, tint));

	if (CurrentState()->LowUIColor(&tint) == which)
		CurrentState()->SetLowColor(tint_color(color, tint));

	for (View* child = FirstChild(); child != NULL;
			child = child->NextSibling()) {

		child->ColorUpdated(which, color);
	}
}


void
View::SetViewUIColor(color_which which, float tint)
{
	if (which != B_NO_COLOR) {
		DesktopSettings settings(fWindow->Desktop());
		SetViewColor(tint_color(settings.UIColor(which), tint));
	}

	fWhichViewColor = which;
	fWhichViewColorTint = tint;
}


color_which
View::ViewUIColor(float* tint)
{
	if (tint != NULL)
		*tint = fWhichViewColorTint;

	return fWhichViewColor;
}


// #pragma mark -


void
View::PushState()
{
	DrawState* previousState = fDrawState.Detach();
	DrawState* newState = previousState->PushState();
	if (newState == NULL)
		newState = previousState;

	fDrawState.SetTo(newState);
	// In BeAPI, B_SUBPIXEL_PRECISE is a view flag, and not affected by the
	// view state. Our implementation moves it to the draw state, but let's
	// be compatible with the API here and make it survive accross state
	// changes.
	fDrawState->SetSubPixelPrecise(fFlags & B_SUBPIXEL_PRECISE);
}


void
View::PopState()
{
	if (fDrawState->PreviousState() == NULL) {
		fprintf(stderr, "WARNING: User called BView(%s)::PopState(), "
			"but there is NO state on stack!\n", Name());
		return;
	}

	bool rebuildClipping = fDrawState->HasAdditionalClipping();

	fDrawState.SetTo(fDrawState->PopState());
	fDrawState->SetSubPixelPrecise(fFlags & B_SUBPIXEL_PRECISE);

	// rebuild clipping
	// (the clipping from the popped state is not effective anymore)
	if (rebuildClipping)
		RebuildClipping(false);
}


// #pragma mark -


void
View::SetEventMask(uint32 eventMask, uint32 options)
{
	fEventMask = eventMask;
	fEventOptions = options;
}


void
View::SetCursor(ServerCursor* cursor)
{
	if (cursor == fCursor)
		return;

	fCursor.SetTo(cursor, false);
}


void
View::SetPicture(ServerPicture* picture)
{
	if (picture == fPicture)
		return;

	fPicture.SetTo(picture, false);
}


void
View::BlendAllLayers()
{
	if (fPicture == NULL)
		return;
	Layer* layer = dynamic_cast<Layer*>(fPicture.Get());
	if (layer == NULL)
		return;
	BlendLayer(layer);
}


void
View::Draw(DrawingEngine* drawingEngine, const BRegion* effectiveClipping,
	const BRegion* windowContentClipping, bool deep)
{
	if (!fVisible) {
		// child views cannot be visible either
		return;
	}

	if (fViewBitmap != NULL || fViewColor != B_TRANSPARENT_COLOR) {
		// we can only draw within our own area
		BRegion* redraw;
		if ((fFlags & B_DRAW_ON_CHILDREN) != 0) {
			// The client may actually want to prevent the background to
			// be painted outside the user clipping.
			redraw = fWindow->GetRegion(
				ScreenAndUserClipping(windowContentClipping));
		} else {
			// Ignore user clipping as in BeOS for painting the background.
			redraw = fWindow->GetRegion(
				_ScreenClipping(windowContentClipping));
		}
		if (!redraw)
			return;
		// add the current clipping
		redraw->IntersectWith(effectiveClipping);

		Overlay* overlayCookie = _Overlay();

		if (fViewBitmap != NULL && overlayCookie == NULL) {
			// draw view bitmap
			// TODO: support other options!
			BRect rect = fBitmapDestination;
			PenToScreenTransform().Apply(&rect);

			align_rect_to_pixels(&rect);

			if (fBitmapOptions & B_TILE_BITMAP_Y) {
				// move rect up as much as needed
				while (rect.top > redraw->Frame().top)
					rect.OffsetBy(0.0, -(rect.Height() + 1));
			}
			if (fBitmapOptions & B_TILE_BITMAP_X) {
				// move rect left as much as needed
				while (rect.left > redraw->Frame().left)
					rect.OffsetBy(-(rect.Width() + 1), 0.0);
			}

// XXX: locking removed because the Window keeps the engine locked
// because it keeps track of syncing right now

			// lock the drawing engine for as long as we need the clipping
			// to be valid
			if (rect.IsValid()/* && drawingEngine->Lock()*/) {
				drawingEngine->ConstrainClippingRegion(redraw);

				drawing_mode oldMode;
				drawingEngine->SetDrawingMode(B_OP_COPY, oldMode);

				if (fBitmapOptions & B_TILE_BITMAP) {
					// tile across entire view

					float start = rect.left;
					while (rect.top < redraw->Frame().bottom) {
						while (rect.left < redraw->Frame().right) {
							drawingEngine->DrawBitmap(fViewBitmap,
								fBitmapSource, rect, fBitmapOptions);
							rect.OffsetBy(rect.Width() + 1, 0.0);
						}
						rect.OffsetBy(start - rect.left, rect.Height() + 1);
					}
					// nothing left to be drawn
					redraw->MakeEmpty();
				} else if (fBitmapOptions & B_TILE_BITMAP_X) {
					// tile in x direction

					while (rect.left < redraw->Frame().right) {
						drawingEngine->DrawBitmap(fViewBitmap, fBitmapSource,
							rect, fBitmapOptions);
						rect.OffsetBy(rect.Width() + 1, 0.0);
					}
					// remove horizontal stripe from clipping
					rect.left = redraw->Frame().left;
					rect.right = redraw->Frame().right;
					redraw->Exclude(rect);
				} else if (fBitmapOptions & B_TILE_BITMAP_Y) {
					// tile in y direction

					while (rect.top < redraw->Frame().bottom) {
						drawingEngine->DrawBitmap(fViewBitmap, fBitmapSource,
							rect, fBitmapOptions);
						rect.OffsetBy(0.0, rect.Height() + 1);
					}
					// remove vertical stripe from clipping
					rect.top = redraw->Frame().top;
					rect.bottom = redraw->Frame().bottom;
					redraw->Exclude(rect);
				} else {
					// no tiling at all

					drawingEngine->DrawBitmap(fViewBitmap, fBitmapSource,
						rect, fBitmapOptions);
					redraw->Exclude(rect);
				}

				drawingEngine->SetDrawingMode(oldMode);

				// NOTE: It is ok not to reset the clipping, that
				// would only waste time
//				drawingEngine->Unlock();
			}

		}

		if (fViewColor != B_TRANSPARENT_COLOR) {
			// fill visible region with view color,
			// this version of FillRegion ignores any
			// clipping, that's why "redraw" needs to
			// be correct
// see #634
//			if (redraw->Frame().left < 0 || redraw->Frame().top < 0) {
//				char message[1024];
//				BRect c = effectiveClipping->Frame();
//				BRect w = windowContentClipping->Frame();
//				BRect r = redraw->Frame();
//				sprintf(message, "invalid background: current clipping: (%d, %d)->(%d, %d), "
//					"window content: (%d, %d)->(%d, %d), redraw: (%d, %d)->(%d, %d)",
//					(int)c.left, (int)c.top, (int)c.right, (int)c.bottom,
//					(int)w.left, (int)w.top, (int)w.right, (int)w.bottom,
//					(int)r.left, (int)r.top, (int)r.right, (int)r.bottom);
//				debugger(message);
//			}

			drawingEngine->FillRegion(*redraw, overlayCookie != NULL
				? overlayCookie->Color() : fViewColor);
		}

		fWindow->RecycleRegion(redraw);
	}

	fBackgroundDirty = false;

	// let children draw
	if (deep) {
		for (View* child = FirstChild(); child; child = child->NextSibling()) {
			child->Draw(drawingEngine, effectiveClipping,
				windowContentClipping, deep);
		}
	}
}


// #pragma mark -


void
View::MouseDown(BMessage* message, BPoint where)
{
	// empty hook method
}


void
View::MouseUp(BMessage* message, BPoint where)
{
	// empty hook method
}


void
View::MouseMoved(BMessage* message, BPoint where)
{
	// empty hook method
}


// #pragma mark -


void
View::SetHidden(bool hidden)
{
	if (fHidden != hidden) {
		fHidden = hidden;

		// recurse into children and update their visible flag
		bool oldVisible = fVisible;
		UpdateVisibleDeep(fParent ? fParent->IsVisible() : !fHidden);
		if (oldVisible != fVisible) {
			// Include or exclude us from the parent area, and update the
			// children's clipping as well when the view will be visible
			if (fParent)
				fParent->RebuildClipping(fVisible);
			else
				RebuildClipping(fVisible);

			if (fWindow) {
				// trigger a redraw
				IntRect clippedBounds = Bounds();
				ConvertToVisibleInTopView(&clippedBounds);

				BRegion dirty, expose;
				dirty.Set((clipping_rect)clippedBounds);
				fWindow->MarkContentDirty(dirty, expose);
			}
		}
	}
}


bool
View::IsHidden() const
{
	return fHidden;
}


void
View::UpdateVisibleDeep(bool parentVisible)
{
	bool wasVisible = fVisible;

	fVisible = parentVisible && !fHidden;
	for (View* child = FirstChild(); child; child = child->NextSibling())
		child->UpdateVisibleDeep(fVisible);

	// overlay handling

	Overlay* overlay = _Overlay();
	if (overlay == NULL)
		return;

	if (fVisible && !wasVisible)
		_UpdateOverlayView();
	else if (!fVisible && wasVisible)
		overlay->Hide();
}


// #pragma mark -


void
View::MarkBackgroundDirty()
{
	if (fBackgroundDirty)
		return;
	fBackgroundDirty = true;
	for (View* child = FirstChild(); child; child = child->NextSibling())
		child->MarkBackgroundDirty();
}


void
View::AddTokensForViewsInRegion(BPrivate::PortLink& link, BRegion& region,
	BRegion* windowContentClipping)
{
	if (!fVisible)
		return;

	{
		// NOTE: use scope in order to reduce stack space requirements

		// This check will prevent descending the view hierarchy
		// any further than necessary
		IntRect screenBounds(Bounds());
		LocalToScreenTransform().Apply(&screenBounds);
		if (!region.Intersects((clipping_rect)screenBounds))
			return;

		// Unfortunately, we intersecting another region, but otherwise
		// we couldn't provide the exact update rect to the client
		BRegion localDirty = _ScreenClipping(windowContentClipping);
		localDirty.IntersectWith(&region);
		if (localDirty.CountRects() > 0) {
			link.Attach<int32>(fToken);
			link.Attach<BRect>(localDirty.Frame());
		}
	}

	for (View* child = FirstChild(); child; child = child->NextSibling())
		child->AddTokensForViewsInRegion(link, region, windowContentClipping);
}


void
View::PrintToStream() const
{
	printf("View:          %s\n", Name());
	printf("  fToken:           %" B_PRId32 "\n", fToken);
	printf("  fFrame:           IntRect(%" B_PRId32 ", %" B_PRId32 ", %" B_PRId32 ", %" B_PRId32 ")\n",
		fFrame.left, fFrame.top, fFrame.right, fFrame.bottom);
	printf("  fScrollingOffset: IntPoint(%" B_PRId32 ", %" B_PRId32 ")\n",
		fScrollingOffset.x, fScrollingOffset.y);
	printf("  fHidden:          %d\n", fHidden);
	printf("  fVisible:         %d\n", fVisible);
	printf("  fWindow:          %p\n", fWindow);
	printf("  fParent:          %p\n", fParent);
	printf("  fLocalClipping:\n");
	fLocalClipping.PrintToStream();
	printf("  fScreenClipping:\n");
	fScreenClipping.PrintToStream();
	printf("  valid:            %d\n", fScreenClippingValid);

	printf("  fUserClipping:\n");
	if (fUserClipping.IsSet())
		fUserClipping->PrintToStream();
	else
		printf("  none\n");

	printf("  fScreenAndUserClipping:\n");
	if (fScreenAndUserClipping.IsSet())
		fScreenAndUserClipping->PrintToStream();
	else
		printf("  invalid\n");

	printf("  state:\n");
	printf("    user clipping:  %d\n", fDrawState->HasClipping());
	BPoint origin = fDrawState->CombinedOrigin();
	printf("    origin:         BPoint(%.1f, %.1f)\n", origin.x, origin.y);
	printf("    scale:          %.2f\n", fDrawState->CombinedScale());
	printf("\n");
}


void
View::RebuildClipping(bool deep)
{
	// the clipping spans over the bounds area
	fLocalClipping.Set((clipping_rect)Bounds());

	if (View* child = FirstChild()) {
		// if this view does not draw over children,
		// exclude all children from the clipping
		if ((fFlags & B_DRAW_ON_CHILDREN) == 0) {
			BRegion* childrenRegion = fWindow->GetRegion();
			if (!childrenRegion)
				return;

			for (; child; child = child->NextSibling()) {
				if (child->IsVisible()
					&& (child->fFlags & B_TRANSPARENT_BACKGROUND) == 0) {
					childrenRegion->Include((clipping_rect)child->Frame());
				}
			}

			fLocalClipping.Exclude(childrenRegion);
			fWindow->RecycleRegion(childrenRegion);
		}
		// if the operation is "deep", make children rebuild their
		// clipping too
		if (deep) {
			for (child = FirstChild(); child; child = child->NextSibling())
				child->RebuildClipping(true);
		}
	}

	// add the user clipping in case there is one
	if (fDrawState->HasClipping()) {
		// NOTE: in case the user sets a user defined clipping region,
		// rebuilding the clipping is a bit more expensive because there
		// is no separate "drawing region"... on the other
		// hand, views for which this feature is actually used will
		// probably not have any children, so it is not that expensive
		// after all
		if (!fUserClipping.IsSet()) {
			fUserClipping.SetTo(new (nothrow) BRegion);
			if (!fUserClipping.IsSet())
				return;
		}

		fDrawState->GetCombinedClippingRegion(fUserClipping.Get());
	} else {
		fUserClipping.SetTo(NULL);
	}

	fScreenAndUserClipping.SetTo(NULL);
	fScreenClippingValid = false;
}


BRegion&
View::ScreenAndUserClipping(const BRegion* windowContentClipping, bool force) const
{
	// no user clipping - return screen clipping directly
	if (!fUserClipping.IsSet())
		return _ScreenClipping(windowContentClipping, force);

	// combined screen and user clipping already valid
	if (fScreenAndUserClipping.IsSet())
		return *fScreenAndUserClipping.Get();

	// build a new combined user and screen clipping
	fScreenAndUserClipping.SetTo(new (nothrow) BRegion(*fUserClipping.Get()));
	if (!fScreenAndUserClipping.IsSet())
		return fScreenClipping;

	LocalToScreenTransform().Apply(fScreenAndUserClipping.Get());
	fScreenAndUserClipping->IntersectWith(
		&_ScreenClipping(windowContentClipping, force));
	return *fScreenAndUserClipping.Get();
}


void
View::InvalidateScreenClipping()
{
// TODO: appearantly, we are calling ScreenClipping() on
// views who's parents don't have a valid screen clipping yet,
// this messes up the logic that for any given view with
// fScreenClippingValid == false, all children have
// fScreenClippingValid == false too. If this could be made the
// case, we could save some performance here with the commented
// out check, since InvalidateScreenClipping() might be called
// frequently.
// TODO: investigate, if InvalidateScreenClipping() could be
// called in "deep" and "non-deep" mode, ie. see if there are
// any cases where the children would still have valid screen
// clipping, even though the parent's screen clipping becomes
// invalid.
//	if (!fScreenClippingValid)
//		return;

	fScreenAndUserClipping.SetTo(NULL);
	fScreenClippingValid = false;
	// invalidate the childrens screen clipping as well
	for (View* child = FirstChild(); child; child = child->NextSibling()) {
		child->InvalidateScreenClipping();
	}
}


BRegion&
View::_ScreenClipping(const BRegion* windowContentClipping, bool force) const
{
	if (!fScreenClippingValid || force) {
		fScreenClipping = fLocalClipping;
		LocalToScreenTransform().Apply(&fScreenClipping);

		// see if parts of our bounds are hidden underneath
		// the parent, the local clipping does not account for this
		IntRect clippedBounds = Bounds();
		ConvertToVisibleInTopView(&clippedBounds);
		if (clippedBounds.Width() < fScreenClipping.Frame().Width()
			|| clippedBounds.Height() < fScreenClipping.Frame().Height()) {
			BRegion temp;
			temp.Set((clipping_rect)clippedBounds);
			fScreenClipping.IntersectWith(&temp);
		}

		fScreenClipping.IntersectWith(windowContentClipping);
		fScreenClippingValid = true;
	}

	return fScreenClipping;
}


void
View::_MoveScreenClipping(int32 x, int32 y, bool deep)
{
	if (fScreenClippingValid) {
		fScreenClipping.OffsetBy(x, y);
		fScreenAndUserClipping.SetTo(NULL);
	}

	if (deep) {
		// move the childrens screen clipping as well
		for (View* child = FirstChild(); child; child = child->NextSibling()) {
			child->_MoveScreenClipping(x, y, deep);
		}
	}
}