#include "ScrollPane.h"

#define ViewHeight() (mFrame.bottom-mFrame.top - (mHBar ? 16 : 0))
#define ViewWidth() (mFrame.right-mFrame.left - (mVBar ? 16 : 0))

ScrollPane::ScrollPane( WindowPtr pWindow, const Boolean hasHBar, const Boolean hasVBar )
: mHBar(0L), mVBar(0L), mContent(0L), mOwnsContent(true), mBorder(true)
{
	mReserve.h = mReserve.v = 0;
	mSize.h = mSize.v = 200;
	if (hasHBar) {
		mHBar = new ScrollbarPane( pWindow );
	}
	if (hasVBar) {
		mVBar = new ScrollbarPane( pWindow );
	}
	Adjust();
}

ScrollPane::~ScrollPane()
{
	if (mHBar) delete mHBar;
	if (mVBar) delete mVBar;
	if (mContent and mOwnsContent) delete mContent;
}

Point ScrollPane::GetTopLeft() const
{
	Point p = { mVBar ? mVBar->GetValue() : 0,
				mHBar ? mHBar->GetValue() : 0 };
	return p;
}

void ScrollPane::SetTopLeft( const Point p )
{
	if (mHBar) {
		short val = p.h;
		if (val < 0) val = 0;
		if (val > mHBar->GetMaximum()) val = mHBar->GetMaximum();
		mHBar->SetValue( val );
	}
	if (mVBar) {
		short val = p.v;
		if (val < 0) val = 0;
		if (val > mVBar->GetMaximum()) val = mVBar->GetMaximum();
		mVBar->SetValue( val );
	}
}

void ScrollPane::Adjust()
{
	short max;
	if (mHBar) {
		mHBar->mFrame = CalcHBarRect();
		mHBar->mPageRate = ViewWidth();
		max = mSize.h - mHBar->mPageRate;
		if (max < 0) max = 0;
		mHBar->SetMaximum( max );
		if (mHBar->GetValue() > max)
			mHBar->SetValue( max );
		mHBar->FitFrame();
	}
	if (mVBar) {
		mVBar->mFrame = CalcVBarRect();
		mVBar->mPageRate = ViewHeight();
		max = mSize.v - mVBar->mPageRate;
		if (max < 0) max = 0;
		mVBar->SetMaximum( max );
		if (mVBar->GetValue() > max)
			mVBar->SetValue( max );
		mVBar->FitFrame();
	}
}

Rect ScrollPane::CalcHBarRect()
{
	Rect r = {	mFrame.bottom - 16,
				mFrame.left + mReserve.h,
				mFrame.bottom,
				mFrame.right - (mVBar ? 15 : 0) };
	return r;
}

Rect ScrollPane::CalcVBarRect()
{
	Rect r = {	mFrame.top + mReserve.v,
				mFrame.right - 16,
				mFrame.bottom - (mHBar ? 15 : 0),
				mFrame.right };
	return r;
}

Rect ScrollPane::CalcContentRect()
{
	Rect r = {	mFrame.top,
				mFrame.left,
				mFrame.bottom - (mHBar ? 15 : 0),
				mFrame.right - (mVBar ? 15 : 0) };
	return r;
}

void ScrollPane::SetContentPane( Pane *pane, const Boolean takeOwnership )
{
	// set internal variables
	if (mContent and mOwnsContent) delete mContent;
	mContent = pane;
	mOwnsContent = takeOwnership;

	// if a null pane, do nothing more
	if (!pane) return;
	
	// adjust size to size of the pane
	mSize.h = pane->mFrame.right - pane->mFrame.left;
	mSize.v = pane->mFrame.bottom - pane->mFrame.top;

	Adjust();
}

void ScrollPane::Draw()
{
	if (mHidden) return;
	// draw scroll bars
	if (mHBar) mHBar->Draw();
	if (mVBar) mVBar->Draw();
	// draw contents
	DrawContent();
	// if we have reserved space, erase that
	if (mReserve.h and mHBar) {
		Rect r = {	mFrame.bottom-15, mFrame.left,
					mFrame.bottom, mFrame.left + mReserve.h };
		EraseRect( &r );
	}
	if (mReserve.v and mVBar) {
		Rect r = {	mFrame.top, mFrame.right-15, 
					mFrame.top + mReserve.v, mFrame.right };
		EraseRect( &r );
	}
	
}

void ScrollPane::DrawContent()
{
	if (!mContent) return;

	// find document position
	GrafPtr port;
	GetPort( &port );
	Point original = { port->portRect.top, port->portRect.left };	
	Point scrolledTo = GetTopLeft();
	Point docTopLeft = { original.v - mFrame.top  + scrolledTo.v,
						 original.h - mFrame.left + scrolledTo.h };

	// change drawing origin
	SetOrigin( docTopLeft.h, docTopLeft.v );
	
	// set clipping rect (relative to newly set origin)
	RgnHandle saveClipRgn = NewRgn();	/* get an empty region */
	GetClip( saveClipRgn );				/* save current */
	Rect r = { scrolledTo.v, scrolledTo.h, 
				scrolledTo.v + ViewHeight(),
				scrolledTo.h + ViewWidth()};
	ClipRect( &r );

	// erase old stuff (especially important when content pane
	// is not centered, or does not fully cover the view pane)
	Rect r2 = { 0, 0, r.bottom-r.top, r.right-r.left };
	if (r2.left < mContent->mFrame.left or r2.right > mContent->mFrame.right
		or r2.top < mContent->mFrame.top or r2.bottom > mContent->mFrame.bottom) {
		// NOTE: for better performance, we could calculate only the
		// non-overlapping region, and erase that instead of the whole thing
		EraseRect( &r );
	}
	
	// then, draw the content pane
	mContent->Draw();
	
	// if desired, draw a border
	if (mBorder) {
		if (mHBar) r.bottom++;	// make border overlap scroll bar border
		if (mVBar) r.right++;
		ClipRect( &r );
		FrameRect( &r );
	}
	
	// restore the port origin and clipping region
	SetOrigin( original.h, original.v );
	SetClip( saveClipRgn );				/* restore previous value */
	DisposeRgn( saveClipRgn );			/* not needed any more */
}

Boolean ScrollPane::Click(Point p, short modifiers)
{
	// did we hit a control part?
	if (mHBar and mHBar->Contains(p)) {
		mHBar->Click(p, modifiers );
	} else if (mVBar and mVBar->Contains(p)) {
		mVBar->Click(p, modifiers );
	} 
	// or how about the content region?
	else if (PtInRect( p, &CalcContentRect() )){
		// we must adjust the click to reflect the scrolling!
		Point topleft = GetTopLeft();
		p.h += topleft.h;
		p.v += topleft.v;
		return mContent->Click(p,modifiers);
	}
	else {
		// none of the above?  (must be a reserved space...)
		// return false, indicating that we didn't deal with it
		return false;
	}
	// refresh the content region (probably scrolled)
	DrawContent();
	return true;
}