/*	XGView.cpp
 *
 *		This is the implementation of the view workhorse class. This is
 *	a glorified hierarchy of rectangles, which are used to clip against
 *	when drawing is requested.
 */

/*  YAAF - Yet another application framework
 *  Copyright (C) 1997, 1998 William Edward Woody
 *  
 *  This library is free software; you can redistribute it
 *  and/or modify it under the terms of the GNU Library
 *  General Public License as published by the Free Software
 *  Foundation; either version 2 of the License, or any
 *  later version.
 *  
 *  This library is distributed in the hope that it will be
 *  useful, but WITHOUT ANY WARRANTY; without even the implied
 *  warranty of MERCHANTABIILITY or FITNESS FOR A PARTICULAR
 *  PURPOSE. See the GNU Library General Public License for
 *  more details.
 *  
 *  You should have received a copy of the GNU Library General
 *  Public License along with this library; if not, write to the
 *  Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 *  Boston, MA 02111-1307, USA.
 *  
 *  To contact the author, either e-mail me at woody@pandawave.com, 
 *	or write to
 *  
 *          William Edward Woody
 *          In Phase Consulting
 *          1545 Ard Eevin Avenue
 *          Glendale, CA 91202
 *
 *	Or visit us on the web at http://www.pandawave.com
 */

#include <XConfig.h>			// must be first to optimize compile on MSVC++
#include <XCursorUtil.h>
#include <XDataUtil.h>
#include <XError.h>
#include <XDraw.h>
#include <XView.h>
#include <ctype.h>
#include <XWindow.h>


#if OPT_WINOS
extern "C" {
	extern HWND _GMDIWindow;
	extern char _GWindowClass[];
}
#endif

/************************************************************************/
/*																		*/
/*	Internal Support													*/
/*																		*/
/************************************************************************/

#if 0
#pragma mark Internal Support Routines
#endif

/*	IsEmptyRect
 *
 *		Is this an empty rectangle?
 */

static bool IsEmptyRect(Rect &r)
{
	return ((r.right <= r.left) || (r.bottom <= r.top));
}

/*	IsPtInRect
 *
 *		Is the point in the rectangle? The portable version
 */

static bool IsPtInRect(Point pt, Rect &r)
{
	if (IsEmptyRect(r)) return false;
	if ((pt.h < r.left) || (pt.h > r.right)) return false;
	if ((pt.v < r.top) || (pt.v > r.bottom)) return false;
	return true;
}

/*	DoIntersect
 *
 *		This calculates the intersection of two rectangles
 */

static bool DoIntersect(Rect &a, Rect &b, Rect *out)
{
	if (a.top < b.top) out->top = b.top;
	else out->top = a.top;
	
	if (a.bottom < b.bottom) out->bottom = a.bottom;
	else out->bottom = b.bottom;
	
	if (a.left < b.left) out->left = b.left;
	else out->left = a.left;
	
	if (a.right < b.right) out->right = a.right;
	else out->right = b.right;
	
	if (IsEmptyRect(*out)) {
		out->left = 0;
		out->top = 0;
		out->right = 0;
		out->bottom = 0;
		return false;
	} else return true;
}

/*	LAbs
 *
 *		Absolute value
 */

inline long LAbs(long x)
{
	return (x > 0) ? x : -x;
}

/************************************************************************/
/*																		*/
/*	View Definition														*/
/*																		*/
/************************************************************************/

// @G"Construction/Destruction"

#if 0
#pragma mark -
#pragma mark Construction/Destruction
#endif

/*	@F"XGView::XGView" @I
 *
 *		Construct the XGView object.
 */

XGView::XGView(XGOwner *parent, XGArgStream &init) : XGOwner(parent)
{
	XGSViewInitRecord i;
	
	i.fViewType = init.ReadInteger();
	
	i.fViewID = init.ReadInteger();
	i.fRefNum = init.ReadInteger();
	
	i.fLockLeft = init.ReadBoolean();	
	i.fLockTop = init.ReadBoolean();
	i.fLockRight = init.ReadBoolean();	
	i.fLockBottom = init.ReadBoolean();
	
	i.fAutoLeft = init.ReadInteger();
	i.fAutoTop = init.ReadInteger();
	i.fAutoRight = init.ReadInteger();
	i.fAutoBottom = init.ReadInteger();

	i.fVisible = init.ReadBoolean();
	i.fEnabled = init.ReadBoolean();
	i.fTabStop = init.ReadBoolean();
	
	IInitView(parent,i);
}

/*	@F"XGView::XGView" @I
 *
 *		Construct the XGView object from the XGSViewInitRecord
 */

XGView::XGView(XGOwner *parent, XGSViewInitRecord &i) : XGOwner(parent)
{
	IInitView(parent,i);
}

/*	@F"XGView::~XGView" @I
 *
 *		Delete me. Deletes my children
 */

XGView::~XGView()
{
	XGView *child;
	
	/*
	 *	Invalidate the space where I'm being deleted from
	 */
	
	if (fRootWindow != 0) InvalView();

	/* 
	 *	Delete my children 
	 */

	while (NULL != (child = GetChild())) delete child;
	
	/* 
	 *	Now delete me from my parent 
	 */
	 
	if (fParent) {
		if (fParent->fChild == this) {
			fParent->fChild = fSibling;
		} else {
			for (child = fParent->fChild; child != NULL; child = child->fSibling) {
				if (child->fSibling == this) {
					child->fSibling = fSibling;
					break;
				}
			}
		}
	}
}

/************************************************************************/
/*																		*/
/*	Events																*/
/*																		*/
/************************************************************************/

// @G"Event Stubs"

#if 0
#pragma mark -
#pragma mark Event Stubs
#endif

/*	@F"XGView::DoDrawView" @I
 *
 *		This method is called by YAAF when this view needs to be redawn.
 *	By default this draws a debugging rectangle
 */

void XGView::DoDrawView(Rect)
{
	Rect r;
	char buffer[256];
	
	if (GetChild()) return;						// don't draw if have children
	
	XGDraw draw(this);
	r = GetContentRect();
	draw.Draw3DRect(r,KXGEBackground);			// draw as grey background
	yformat(buffer,"'#I'#L",fViewType,(long)fViewID);
	draw.MoveTo(5,15);
	draw.DrawString(buffer);					// draw the identifier
	draw.Draw3DRect(r,KXGEOutset);				// Frame the border
}

/*	@F"XGView::DoActivate" @I
 *
 *		Called by YAAF when this view needs to be activated or deactivated.
 *	By default does nothing.
 */

void XGView::DoActivate(bool)
{
}

/*	@F"XGView::DoMouseDown" @I
 *
 *		Called by YAAF when the mouse is clicked in this view. By default
 *	this returns 'false'. Return 'true' if your view needs to capture the
 *	mouse for a click/drag cycle.
 */

bool XGView::DoMouseDown(Point,short)
{
	return false;
}

/*	@F"XGView::DoMouseMove" @I
 *
 *		Process mouse move. Sets the cursor to the arrow cursor
 */

void XGView::DoMouseMove(Point,short)
{
	SetCursorID(KNormalCursor);
}

/*	@F"XGView::DoMouseUp" @I
 *
 *		Process mouse up. Does nothing
 */

void XGView::DoMouseUp(Point,short)
{
}

/*	@F"XGView::DoSizeView" @I
 *
 *		Event sent when the view is resized. Does nothing
 */

void XGView::DoSizeView()
{
	XGView *foo;
	
	for (foo = fChild; foo != NULL; foo = foo->fSibling) {
		foo->SetLocation(CalcChildPos(foo));
	}
}

/*	@F"XGView::DoMoveView" @I
 *
 *		Event sent when the view is moved. Does nothing
 */

void XGView::DoMoveView()
{
}

/*	@F"XGView::DoShowHide" @I
 *
 *		When the view is either shown or hidden, this is sent
 */

void XGView::DoShowHide()
{
	XGView *view;
	
	for (view = fChild; view != NULL; view = view->fSibling) {
		view->DoShowHide();
	}
}

/*	@F"XGView::DoEnable" @I
 *
 *		Sent when the view is enabled or disabled
 */

void XGView::DoEnable()
{
}

/************************************************************************/
/*																		*/
/*	Focus Processing													*/
/*																		*/
/************************************************************************/

// @G"Focus"
#if 0
#pragma mark -
#pragma mark Focus
#endif

/*	@F"XGView::SetFocus" @I
 *
 *		Set the focus. This overrides stuff on Windows by setting the
 *	MS Window focus as well.
 */

bool XGView::SetFocus(void)
{
	if (XGDispatch::SetFocus()) {
#if OPT_WINOS
		HWND w;
		
		w = GetRootWindow();
		if (XGWindow::GetFrontWindow() == XGWindow::GetXGWindow(w)) {
			::SetFocus(w);
		}
#endif
		return true;
	} else return false;
}

/************************************************************************/
/*																		*/
/*	View display processing												*/
/*																		*/
/************************************************************************/

// @G"View Attributes"

#if 0
#pragma mark -
#pragma mark View attributes
#endif

/*	@F"XGView::IsVisible" @I
 *
 *		Is this view visible? Only if the parent is also visible
 */
 
bool XGView::IsVisible() const
{
	if (fParent) {
		if (!(fParent->IsVisible())) return false;
	}
	return fVisible;
}

/*	@F"XGView::ShowView" @I
 *
 *		This shows the view. If the view is hidden, this sends the
 *	'showhide' message and then shows the view
 */

void XGView::ShowView()
{
	if (fVisible) return;
	
	fVisible = true;
	if (IsVisible()) {
		DoShowHide();
		InvalView();
	}
}

/* @F"XGView::HideView" @I
 *
 *		This hides the view. If the view is shown, this sends the
 *	'showide' message and hides the view
 */

void XGView::HideView()
{
	if (!fVisible) return;

	bool v = IsVisible();
	fVisible = false;
	if (v) {
		DoShowHide();
		InvalView();
	}
}

/* @F"XGView::IsEnabled" @I
 *
 *		Is this view enabled? Only if the parent is also enabled
 */
 
bool XGView::IsEnabled() const
{
	if (fParent) {
		if (!(fParent->IsEnabled())) return false;
	}
	return fEnabled;
}

/* @F"XGView::EnableView" @I
 *
 *		Enable this view. If the view is not enabled, this will enable
 *	the view
 */

void XGView::EnableView()
{
	if (fEnabled) return;
	
	fEnabled = true;
	if (IsEnabled()) {
		DoEnable();
		InvalView();
	}
}

/* @F"XGView::DisableView" @I
 *
 *		Disable this view.
 */

void XGView::DisableView()
{
	if (!fEnabled) return;

	bool e = IsEnabled();
	fEnabled = false;
	if (e) {
		DoEnable();
		InvalView();
	}
}

/* @F"XGView::IsActive" @I
 *
 *		Am I an active view?
 */

bool XGView::IsActive() const
{
#if OPT_MACOS
	if (fRootWindow == NULL) return false;
	return (((WindowPeek)fRootWindow)->hilited) ? true : false;
#endif

#if OPT_WINOS
	if (fRootWindow == NULL) return false;
	if (fRootWindow == ::GetActiveWindow()) return true;
	
	if (_GMDIWindow) {
		HWND w;
		
		/*
		 *	MDI application. Determine active frontmost
		 */
		
		w = ::GetWindow(_GMDIWindow,GW_CHILD);
		while (w != NULL) {
			if (::GetProp(w,_GWindowClass)) {
				if (::IsWindowVisible(w)) break;
			}
			w = ::GetWindow(w,GW_HWNDNEXT);
		}
		if (w == fRootWindow) return true;
	}
	return false;
#endif

#if OPT_XWIN
	if (fRootWindow) {
		XGWindow *w = XGWindow::GetXGWindow(fRootWindow);
		return w->fActive;
	} else return false;
#endif
}

/* @F"XGView::GetTitle" @I
 *
 *		Get the title of this view. By default returns an empty string.
 *	This is necessary for '&' processing for dialogs
 */

void XGView::GetTitle(char *c) const
{
	*c = 0;
}

/* @F"XGView::SetTitle" @I
 *
 *		Set the title. This should never be called
 */

void XGView::SetTitle(const char *)
{
	Assert(0);
}

/************************************************************************/
/*																		*/
/*	View Hierarchy search support										*/
/*																		*/
/************************************************************************/

// @G"View hierarchy"

#if 0
#pragma mark -
#pragma mark View Hierarchy
#endif

/*	@F"XGView::GetWindow" @I
 *
 *		Get the XGWindow this view is contained in
 */

XGWindow *XGView::GetWindow() const
{
	return XGWindow::GetXGWindow(GetRootWindow());
}

/*	@F"XGView::GetDocument" @I
 *
 *		Get the XGWindow this view is contained in
 */

XGDocument *XGView::GetDocument() const
{
	XGWindow *w = GetWindow();
	if (w) return w->GetDocument();
	else return NULL;
}

/*	@F"XGView::GetForm" @I
 *
 *		Get the XGWindow this view is contained in
 */

XGForm *XGView::GetForm() const
{
	XGWindow *w = GetWindow();
	if (w) return w->GetForm();
	else return NULL;
}

/* @F"XGView::FindViewByID" @I
 *
 *		Find the specified view by it's identifier
 */

XGView *XGView::FindViewByID(short index)
{
	XGView *ptr;
	XGView *ret;
	
	/*
	 *	Is it me?
	 */
	
	if (GetViewID() == index) return this;
	
	/*
	 *	Scan
	 */
	
	for (ptr = GetChild(); ptr != NULL; ptr = ptr->GetSibling()) {
		ret = ptr->FindViewByID(index);
		if (ret) return ret;
	}
	
	/*
	 *	None
	 */
	
	return NULL;
}

/* @F"XGView::FindViewByPoint" @I
 *
 *		Find the specified view by it's location. Find the view which
 *	contains this rectangle. Note: This stops at invisible views, but
 *	searches enabled views
 */

XGView *XGView::FindViewByPoint(Point where, bool recurse)
{
	XGView *ptr;
	XGView *ret;
	
	if (!fVisible) return NULL;
	if (!IsPtInRect(where,fGlobalRect)) return NULL;
	if (recurse) {
		for (ptr = GetChild(); ptr != NULL; ptr = ptr->GetSibling()) {
			ret = ptr->FindViewByPoint(where,true);
			if (ret) return ret;
		}
	} else {
		for (ptr = GetChild(); ptr != NULL; ptr = ptr->GetSibling()) {
			if (IsPtInRect(where,ptr->fGlobalRect)) return ptr;
		}
	}
	return this;
}

/* @F"XGView::FindFirstView" @I
 *
 *		Find the first view in the list of views
 */

XGView *XGView::FindFirstView()
{
	XGView *vc;
	short min;
	XGView *c;
	XGView *m;
	
	/*
	 *	See if I can add me to the list
	 */
	
	vc = NULL;
	min = GetViewID();
	if ((0 != min) && IsTabStop() && IsEnabled() && IsVisible()) {
		vc = this;
	}
	
	/*
	 *	Scan my children
	 */
	
	for (c = GetChild(); c != NULL; c = c->GetSibling()) {
		m = c->FindFirstView();
		if (m) {
			if (!vc) {
				min = m->GetViewID();
				vc = m;
			} else if (m->GetViewID() < min) {
				min = m->GetViewID();
				vc = m;
			}
		}
	}
	
	/*
	 *	Done. Return my results
	 */
	 
	return vc;
}

/* @F"XGView::FindLastView" @I
 *
 *		Find the first view in the list of views
 */

XGView *XGView::FindLastView()
{
	XGView *vc;
	short min;
	XGView *c;
	XGView *m;
	
	/*
	 *	See if I can add me to the list
	 */
	
	vc = NULL;
	min = GetViewID();
	if ((0 != min) && IsTabStop() && IsEnabled() && IsVisible()) {
		vc = this;
	}
	
	/*
	 *	Scan my children
	 */
	
	for (c = GetChild(); c != NULL; c = c->GetSibling()) {
		m = c->FindLastView();
		if (m) {
			if (!vc) {
				min = m->GetViewID();
				vc = m;
			} else if (m->GetViewID() > min) {
				min = m->GetViewID();
				vc = m;
			}
		}
	}
	
	/*
	 *	Done. Return my results
	 */
	 
	return vc;
}

/* @F"XGView::FindBeforeView" @I
 *
 *		Find the first view in the list of views
 */

XGView *XGView::FindBeforeView(short vid)
{
	XGView *vc;
	short min;
	XGView *c;
	XGView *m;
	short n;
	
	/*
	 *	See if I can add me to the list
	 */
	
	vc = NULL;
	min = GetViewID();
	if ((0 != min) && IsTabStop() && IsEnabled() && IsVisible()) {
		if (min < vid) vc = this;
	}
	
	/*
	 *	Scan my children
	 */
	
	for (c = GetChild(); c != NULL; c = c->GetSibling()) {
		m = c->FindBeforeView(vid);
		if (m) {
			n = m->GetViewID();
			if (!vc && (n < vid)) {
				min = m->GetViewID();
				vc = m;
			} else if ((n > min) && (n < vid)) {
				min = n;
				vc = m;
			}
		}
	}
	
	/*
	 *	Done. Return my results
	 */
	 
	return vc;
}

/* @F"XGView::FindAfterView" @I
 *
 *		Find the first view in the list of views after this ID
 */

XGView *XGView::FindAfterView(short vid)
{
	XGView *vc;
	short min;
	XGView *c;
	XGView *m;
	short n;
	
	/*
	 *	See if I can add me to the list
	 */
	
	vc = NULL;
	min = GetViewID();
	if ((0 != min) && IsTabStop() && IsEnabled() && IsVisible()) {
		if (min > vid) vc = this;
	}
	
	/*
	 *	Scan my children
	 */
	
	for (c = GetChild(); c != NULL; c = c->GetSibling()) {
		m = c->FindAfterView(vid);
		if (m) {
			n = m->GetViewID();
			if (!vc && (n > vid)) {
				min = m->GetViewID();
				vc = m;
			} else if ((n < min) && (n > vid)) {
				min = n;
				vc = m;
			}
		}
	}
	
	/*
	 *	Done. Return my results
	 */
	 
	return vc;
}

/* @F"XGView::FindViewByAccel" @I
 *
 *		Find the view with the specified accelerator
 */

XGView *XGView::FindViewByAccel(char accel)
{
	XGView *ptr;
	XGView *ret;
	char buffer[256];
	char *x;
	
	/*
	 *	Is it me?
	 */
	
	GetTitle(buffer);
	for (x = buffer; *x != 0; x++) {
		if (*x == '&') {
			if (toupper(x[1]) == toupper(accel)) return this;
		}
	}
	
	/*
	 *	Scan
	 */
	
	for (ptr = GetChild(); ptr != NULL; ptr = ptr->GetSibling()) {
		ret = ptr->FindViewByAccel(accel);
		if (ret) return ret;
	}
	
	/*
	 *	None
	 */
	
	return NULL;
}

/* @F"XGView::FindNextView" @I
 *
 *		Find the next view; this finds the view after this one in
 *	the overall hierarchy
 */

XGView *XGView::FindNextView()
{
	XGView *tmp;
	XGView *ptr;
	
	/*
	 *	Do I have children? If so, move down the tree
	 */
	
	if (NULL != (tmp = GetChild())) return tmp;
	
	/*
	 *	Move up the tree until I find a sibling of me, my parent,
	 *	or one above my parent...
	 */
	
	ptr = this;
	for (;;) {
		if (NULL != (tmp = ptr->GetSibling())) return tmp;
		ptr = ptr->GetParent();
		if (ptr == NULL) return NULL;
	}
}


/************************************************************************/
/*																		*/
/*	View Location and View Manipulation									*/
/*																		*/
/************************************************************************/

// @G"View location"

#if 0
#pragma mark -
#pragma mark View Location and View Manipulation
#endif

/* @F"XGView::GetContentRect" @I
 *
 *		Get the content rectangle for this view
 */

Rect XGView::GetContentRect() const
{
	Rect r;
	
	r.left = 0;
	r.top = 0;
	r.right = fSize.h;
	r.bottom = fSize.v;
	
	return r;
}

/* @F"XGView::SetLocation" @I
 *
 *		Set the location of this thing within my parent's content
 *	area. This will invalidate the rectangle areas where the location
 *	was.
 */

void XGView::SetLocation(Rect r)
{
	Point newSize;
	Point newPos;
	Point oldSize;
	Point oldPos;
	
	/*
	 *	Calculate the new position and size
	 */
	
	newPos.h  = r.left;
	newPos.v  = r.top;
	newSize.h = r.right - r.left;
	newSize.v = r.bottom - r.top;
	
	/*
	 *	Determine if this view even moves; if it doesn't, this
	 *	aborts this operation.
	 */
	
//	if ((newPos.h == fPos.h) && (newPos.v == fPos.v) &&
//			(newSize.h == fSize.h) && (newSize.v == fSize.v)) return;
			
	/*
	 *	We're about to move this thing. First, invalidate the old
	 *	area where this thing used to be.
	 */
	
	InvalView();
	
	/*
	 *	Now, calculate this view's positioning information
	 */
	
	fGlobalRect = r;
	if (fParent) {
		fParent->ViewToGlobal(&fGlobalRect);
		::DoIntersect(fGlobalRect,fParent->fGlobalRect,&fGlobalRect);
	}
	oldSize = fSize;
	oldPos = fPos;
	
	fPos = newPos;
	fSize = newSize;
	fGlobalPos = newPos;
	if (fParent) {
		fParent->ViewToGlobal(&fGlobalPos);
	}

	/*
	 *	Invalidate the new location
	 */
	
	InvalView();

	/*
	 *	And send the callbacks
	 */
	
	if ((oldPos.h != fPos.h) || (oldPos.v != fPos.v)) CalcPos();
	if ((oldSize.h != fSize.h) || (oldSize.v != fSize.v)) DoSizeView();
}

/* @F"XGView::CalcChildPos" @I
 *
 *		This automatically calculates the position of this particular
 *	child inside of it's parent.
 */

Rect XGView::CalcChildPos(XGView *child)
{
	return child->CalcAutoLocate(GetContentRect());
}

/* @F"XGView::CalcAutoLocate" @I
 *
 *		Given the parameters for how this view should be locked to my parent's
 *	view, this calculates the various location parameters
 */

Rect XGView::CalcAutoLocate(Rect r)
{
	Rect s;
	
	if (fLockLeft) s.left = r.left + fAutoLeft;
	else s.left = r.right + fAutoLeft;
	if (!fLockRight) s.right = r.left + fAutoRight;
	else s.right = r.right + fAutoRight;
	
	if (fLockTop) s.top = r.top + fAutoTop;
	else s.top = r.bottom + fAutoTop;
	if (!fLockBottom) s.bottom = r.top + fAutoBottom;
	else s.bottom = r.bottom + fAutoBottom;

	return s;
}


/************************************************************************/
/*																		*/
/*	Drawing Support Routines											*/
/*																		*/
/*		The following five routines are highly OS platform dependant.	*/
/*	The idea is that all of the drawing routines that are OS dependant	*/
/*	live in XGDraw. However, it is convenient to be able to scroll a	*/
/*	view or invalidate it's contents without having to set up a drawing	*/
/*	context. Thus, these routines live here.							*/
/*																		*/
/************************************************************************/

// @G"Drawing Support"

#if 0
#pragma mark -
#pragma mark Drawing Support
#endif

/* @F"XGView::InvalView" @I
 *
 *		This invalidates the drawing area where this view appears.
 */

void XGView::InvalView(Rect *r)
{
	Rect s;
	
	if (fRootWindow == 0) return;			// can't do this...
	XGDraw draw(this);

	/*
	 *	Find the area to invalidate
	 */
		
	s = GetContentRect();
	if (r) if (!::DoIntersect(*r,s,&s)) return;

	/*
	 *	Invalidate the region within me.
	 */
	
#if OPT_MACOS == 1
	Point off;
	
	off.h = fRootWindow->portRect.left;			/* Calculate the area to */
	off.v = fRootWindow->portRect.top;			/* Invalidate based on the */
	ViewToGlobal(&s);							/* Current origin */
	
	s.left += off.h;
	s.right += off.h;
	s.top += off.v;
	s.bottom += off.v;
	::InvalRect(&s);
#endif

#if OPT_WINOS == 1
	ViewToGlobal(&s);
	::InvalidateRect(fRootWindow,&s,false);
#endif

#if OPT_XWIN == 1
	//### Is this right?
	::XClearArea(_GDisplay,fRootWindow,s.left + fGlobalPos.h,
									   s.top + fGlobalPos.v,
									   s.right-s.left,
									   s.bottom-s.top,
									   1);
#endif
}
/* @F"XGView::ValidView" @I
 *
 *		This validates the drawing area.
 */

void XGView::ValidView(Rect *r)
{
	Rect s;

	if (fRootWindow == 0) return;			// can't do this...
	XGDraw draw(this);

	/*
	 *	Find the area to invalidate
	 */
		
	s = GetContentRect();
	if (r) if (!::DoIntersect(*r,s,&s)) return;

	/*
	 *	Invalidate the region within me.
	 */
	
#if OPT_MACOS == 1
	Point off;
	
	off.h = fRootWindow->portRect.left;			/* Calculate the area to */
	off.v = fRootWindow->portRect.top;			/* Invalidate based on the */
	ViewToGlobal(&s);							/* Current origin */
	
	s.left += off.h;
	s.right += off.h;
	s.top += off.v;
	s.bottom += off.v;
	::ValidRect(&s);
#endif

#if OPT_WINOS == 1
	ViewToGlobal(&s);
	::ValidateRect(fRootWindow,&s);
#endif

#if OPT_XWIN == 1
	/*
	 *	There is no way to validate a portion of a window in X, as
	 *	far as I'm aware. Thus, I simply skip this step, and we can
	 *	survive with the occassional extra unnecessary redraw.
	 *
	 *	It has been my experience that while ValidView() is an
	 *	important routine, it is in practice seldom used. And in
	 *	the few cases where it is, the extra update later won't hurt.
	 */
	 
#endif
}

/* @F"XGView::UpdateView" @I
 *
 *		This forces this view to be updated _right_ _now_
 */

void XGView::UpdateView()
{
	if (fRootWindow == 0) return;			// can't do this...

#if OPT_XWIN == 1
	// Scan for Expose events or GraphicsUpdate events on
	// my root window. overkill???
	for (;;) {
		XEvent e;
		
		if (!::XCheckWindowEvent(_GDisplay,fRootWindow,ExposureMask,&e)) break;
		
		/*
		 *	Process event mask
		 */
		
		if (e.xexpose.count == 0) {
			DoDrawView(GetContentRect());
		}
	}
#endif

#if OPT_WINOS == 1
	UpdateWindow(fRootWindow);			// overkill??? ###
#endif

#if OPT_MACOS == 1
	RgnHandle rgn;
	Point pt;
	RgnHandle tmp;
	Rect r;
	GrafPtr port;
	
	if (!fVisible) return;
	
	/*
	 *	Initialize stuff
	 */
	
	XGDraw draw(this,false);			// Set this as current port
	::GetPort(&port);
	pt.h = 0, pt.v = 0;
	::GlobalToLocal(&pt);				// Shift to window coordinates
	
	/*
	 *	Calculate the intersection of the update region with the
	 *	view rectangle
	 */
	
	rgn = ::NewRgn();					// Get the update region
	::CopyRgn(((WindowPeek)port)->updateRgn,rgn);
	::OffsetRgn(rgn,pt.h,pt.v);			// Offset the region to window coords
	tmp = ::NewRgn();
	r = GetContentRect();
	::OffsetRect(&r,fGlobalPos.h,fGlobalPos.v);
	::RectRgn(tmp,&r);
	::SectRgn(rgn,tmp,rgn);				// Intersect with view rectangle
	::DisposeRgn(tmp);
	
	if (!EmptyRgn(rgn)) {
		/*
		 *	This is a bit wierd, but what I do is to set the invalid
		 *	region to the region that I calculated, force a normal
		 *	validation (BeginUpdate/EndUpdate) cycle, and then
		 *	invalidate the area not in the region I calculated...
		 */
		
		tmp = ::NewRgn();
		::CopyRgn(((WindowPeek)port)->updateRgn,tmp);
		::OffsetRgn(tmp,pt.h,pt.v);		// Offset to window coordinates
		::DiffRgn(tmp,rgn,tmp);			// Area remaining (window coords)
		
		r = (**rgn).rgnBBox;			// Get bounding rectangle
		::OffsetRgn(rgn,-pt.h,-pt.v);	// Move my invalid area to global cds
		::CopyRgn(rgn,((WindowPeek)port)->updateRgn);	// Replace area
		
		::BeginUpdate(port);			// Update this window
		ProcessDraw(r);
		::EndUpdate(port);
		
		::SetPort(port);
		::SetOrigin(0,0);				// Move to window coordinates
		::InvalRgn(tmp);				// Invalidate the remaining area
		::DisposeRgn(tmp);
	}
		
	::DisposeRgn(rgn);					// Clean up and exit
#endif
}

/************************************************************************/
/*																		*/
/*	Scrolling Support													*/
/*																		*/
/************************************************************************/

// @G"Scroll Support"

#if 0
#pragma mark -
#pragma mark Scroll Support
#endif

/* @F"XGView::Scroll" @I
 *
 *		This is the method which is called when it's time to scroll this
 *	thing, such as processing scrollbar messages. By default, this scrolls
 *	the entire thing.
 */

void XGView::Scroll(long x, long y)
{
	ScrollView(x,y);
}

/* @F"XGView::ScrollView" @I
 *
 *		This handles the scrolling view itself. This is a bit tricky, given
 *	the various operating systems this thing can be hosted on. Basically,
 *	I can scroll the bits in this view without having a drawing context
 *	open to this view. This is accomplished with various black magic
 *	incantations.
 */

void XGView::ScrollView(long x, long y, Rect *r)
{
	Rect s;
	
	/*
	 *	First, in an OS independant way, find the area that actually
	 *	gets scrolled
	 */
	
	if (r == NULL) s = GetContentRect();
	else s = *r;
	ViewToGlobal(&s);
	if (!::DoIntersect(fGlobalRect,s,&s)) return;	/* Not visible */
	
	/*
	 *	Determine if the amount we get scrolled is too wide or tall.
	 *	If it is, simply invalidate the entire view
	 */
	
	if (((s.right - s.left) <= LAbs(x)) || ((s.bottom - s.top) <= LAbs(y))) {
		InvalView();
		return;
	}
	
	/*
	 *	We have to scroll. Oh, well. At least do it with a little class...
	 */
	
#if OPT_MACOS == 1
	/*
	 *	Macintosh version: Calculate the location inside the current
	 *	drawing port where I should start scrolling bits. This requires
	 *	finding the current coordinate system the window is in, finding
	 *	the bits where my view is, and modifying
	 */
	
	XGDraw draw(this,false);
	RgnHandle rgn;
	Point off;
	
	off.h = fRootWindow->portRect.left;			/* Calculate the area to */
	off.v = fRootWindow->portRect.top;			/* scroll based on the */
	
	s.left += off.h;
	s.right += off.h;
	s.top += off.v;
	s.bottom += off.v;
	
	rgn = ::NewRgn();
	::ScrollRect(&s,(short)x,(short)y,rgn);
	::InvalRgn(rgn);
	::DisposeRgn(rgn);
#endif

#if OPT_WINOS == 1
	::ScrollWindowEx(fRootWindow,(int)x,(int)y,&s,&s,NULL,NULL,SW_ERASE | SW_INVALIDATE);
#endif

#if OPT_XWIN == 1
	short xsrc,ysrc;
	short xdst,ydst;
	short xwid,ywid;
	GC gc;
	
	/*
	 *	X Windows doesn't support the scroll stuff like the Macintosh
	 *	or like Windows. I gotta do this the long way
	 *
	 *	First, find the bounding rectangle that is getting moved
	 */
	
	xwid = s.right - s.left;
	ywid = s.bottom - s.top;
	if (x < 0) {
		xsrc = s.left - x;
		xdst = s.left;
		xwid += x;
	} else {
		xsrc = s.left;
		xdst = s.left + x;
		xwid -= x;
	}
	if (y < 0) {
		ysrc = s.top - y;
		ydst = s.top;
		ywid += y;
	} else {
		ysrc = s.top;
		ydst = s.top + y;
		ywid -= y;
	}
	
	gc = ::XCreateGC(_GDisplay,fRootWindow,0,NULL);
	::XCopyArea(_GDisplay,fRootWindow,fRootWindow,gc,xsrc,ysrc,
						xwid,ywid,xdst,ydst);
	::XFreeGC(_GDisplay,gc);
	
	/*
	 *	Second, find the (up to) two rectangles which need to be
	 *	invalidated
	 */
	
	if (x < 0) {
		xsrc = s.right + x;
		xwid = -x;
		::XClearArea(_GDisplay,
					 fRootWindow,
					 xsrc,
					 s.top,
					 xwid,
					 s.bottom-s.top,
					 1);
	} else if (x > 0) {
		xsrc = s.left;
		xwid = x;
		::XClearArea(_GDisplay,
					 fRootWindow,
					 xsrc,
					 s.top,
					 xwid,
					 s.bottom-s.top,
					 1);
	}
	if (y < 0) {
		ysrc = s.bottom + y;
		ywid = -y;
		::XClearArea(_GDisplay,
					 fRootWindow,
					 s.left,
					 ysrc,
					 s.right-s.left,
					 ywid,
					 1);
	} else {
		ysrc = s.top;
		ywid = y;
		::XClearArea(_GDisplay,
					 fRootWindow,
					 s.left,
					 ysrc,
					 s.right-s.left,
					 ywid,
					 1);
	}
#endif
}

/************************************************************************/
/*																		*/
/*	View Management														*/
/*																		*/
/************************************************************************/

// @G"View Management"

#if 0
#pragma mark -
#pragma mark View Management
#endif

/* @F"XGView::ProcessDraw" @I
 *
 *		Process drawing command. This runs through all of the views
 *	that are inside of me and passes the appropriate command
 */

void XGView::ProcessDraw(Rect ginval)
{	Rect r;
	Rect s;
	XGView *v;
	
	/*
	 *	Am I visible?
	 */
	
	if (!fVisible) return;				// Nope.
	
	/*
	 *	Am I even in the invalid area?
	 */
	
	if (::DoIntersect(ginval,fGlobalRect,&r)) {
		/*
		 *	Do me first
		 */
		
		s = r;
		GlobalToView(&s);
		DoDrawView(s);
		
		/*
		 *	Now let my children draw inside of me
		 */
		
		for (v = fChild; v != NULL; v = v->fSibling) {
			v->ProcessDraw(r);
		}
	}
}

/* @F"XGView::ProcessActivate" @I
 *
 *		Run through and activate or deactivate all the views
 */

void XGView::ProcessActivate(bool active)
{
	XGView *v;
	XGView *ptr;
	
	ptr = fChild;
	while (ptr != NULL) {
		v = ptr;
		ptr = ptr->fSibling;
		v->ProcessActivate(active);
	}
	DoActivate(active);
}

/* @F"XGView::FindClick" @I
 *
 *		Find the view to send this click event. This skips disabled
 *	and invisible views
 */

XGView *XGView::FindClick(Point where)
{
	XGView *ptr;
	XGView *ret;
	
	if (!fVisible) return NULL;				// view not visible
	if (!fEnabled) return NULL;				// view not enabled
	if (!IsPtInRect(where,fGlobalRect)) return NULL;
	
	for (ptr = GetChild(); ptr != NULL; ptr = ptr->GetSibling()) {
		ret = ptr->FindClick(where);
		if (ret) return ret;
	}
	return this;
}

/************************************************************************/
/*																		*/
/*	OS Specific Support													*/
/*																		*/
/************************************************************************/

// @G"OS Support"

#if 0
#pragma mark -
#pragma mark OS Specific Support
#endif

/* @F"XGView::Is72DPI" @I
 *
 *		Return true if this is 72 DPI, and false if this is higher.
 *
 *	###FIX ME
 */

bool XGView::Is72DPI()
{
#if OPT_MACOS == 1
	return true;
#endif

#if OPT_WINOS == 1
	return false;
#endif

#if OPT_XWIN == 1
	return false;
#endif
}

/************************************************************************/
/*																		*/
/*	Resize Support														*/
/*																		*/
/************************************************************************/

// @G"View location"

#if 0
#pragma mark -
#pragma mark Resize Support
#endif

/* @F"XGView::CalcPos" @I
 *
 *		Calculate the position of the children views. This recurses down,
 *	moving the children around as well.
 */

void XGView::CalcPos(void)
{
	Point newPos;
	XGView *v;
	
	/*
	 *	Run through the children, and move them. If they do move, then
	 *	we move them, and recurse
	 */
	
	for (v = fChild; v != NULL; v = v->fSibling) {
		newPos.h = fGlobalPos.h + v->fPos.h;
		newPos.v = fGlobalPos.v + v->fPos.v;
		if ((newPos.h != v->fGlobalPos.h) || (newPos.v != v->fGlobalPos.v)) {
			/*
			 *	Update global pos
			 */
			
			v->fGlobalPos = newPos;
			
			/*
			 *	Update global rectangle
			 */
			
			v->fGlobalRect.left = v->fGlobalPos.h;
			v->fGlobalRect.top  = v->fGlobalPos.v;
			v->fGlobalRect.right = v->fGlobalRect.left + v->fSize.h;
			v->fGlobalRect.bottom = v->fGlobalRect.top + v->fSize.v;
			::DoIntersect(v->fGlobalRect,fGlobalRect,&(v->fGlobalRect));

			/*
			 *	And move the children
			 */
			
			v->CalcPos();
		}
	}
	
	/*
	 *	Let me know we moved
	 */
	
	DoMoveView();
}

/************************************************************************/
/*																		*/
/*	Resize Support														*/
/*																		*/
/************************************************************************/

#if 0
#pragma mark -
#pragma mark Initialization Support
#endif

/*	XGView::IInitView
 *
 *		Initialize the view. This contains the hard stuff
 */

void XGView::IInitView(XGOwner *parent, XGSViewInitRecord &init)
{
	Rect s;
	XGView *p;

	/*
	 *	Initialize the basic hierarchy
	 */
	
	if (parent == NULL) p = NULL;
	else {
		try {
			p = dynamic_cast<XGView *>(parent);
		}
		catch (...) {
			p = NULL;
		}
	}
	
	fChild = NULL;
	if (p) {
		XGView *end;
		
		// Hook me into the parent at the end of the linked list.
		// order preservation is important.
		fParent = p;
		fSibling = NULL;
		
		if (p->fChild == NULL) {
			p->fChild = this;
		} else {
			for (end = p->fChild; end->fSibling != NULL; end = end->fSibling) ;
			end->fSibling = this;
		}
	} else {
		// Don't hook me into the parent--I *am* the root
		fParent = NULL;
		fSibling = NULL;
	}
	
	/*
	 *	Get the view specification parameters
	 */
	
	fViewType   = init.fViewType;
	fViewID     = init.fViewID;
	fRefNum     = init.fRefNum;
	
	/*
	 *	Get the size of this thing
	 */
	
	fLockLeft   = init.fLockLeft;
	fLockTop    = init.fLockTop;
	fLockRight  = init.fLockRight;
	fLockBottom = init.fLockBottom;
	
	fAutoLeft   = init.fAutoLeft;
	fAutoTop    = init.fAutoTop;
	fAutoRight  = init.fAutoRight;
	fAutoBottom = init.fAutoBottom;
	
	/*
	 *	Calculate the size of this thing based on the auto parameters
	 */
	
	if (fParent) {
		s = fParent->CalcChildPos(this);
	} else {
		/*
		 *	For initialization purposes only. This will be refined
		 *	later. I set this to an arbitrarly large value so that
		 *	my autolocate calculations work during initialization.
		 */

		s.left = 0;
		s.top = 0;
		s.right = 1000;
		s.bottom = 1000;
	}
	
	/*
	 *	Initialize the view positioning information
	 */
	
	fSize.h = s.right - s.left;
	fSize.v = s.bottom - s.top;
	fPos.h = s.left;
	fPos.v = s.top;
	fGlobalPos = fPos;
	fGlobalRect = s;
	if (fParent) {
		fParent->ViewToGlobal(&fGlobalPos);
		fParent->ViewToGlobal(&fGlobalRect);
		
		::DoIntersect(fParent->fGlobalRect,fGlobalRect,&fGlobalRect);
	}
	
	/*
	 *	Initialization
	 */
	
	fVisible    = init.fVisible;
	fEnabled    = init.fEnabled;
	fTabStop    = init.fTabStop;
	
	/*
	 *	Now invalidate the area where this was created.
	 */
	
	if (fRootWindow != 0) InvalView();
}
