/*
 * Constructs a ScrollArea object, which implements a scrolling content window with 
 * OS-style scrollbar functionality. This object relies on Div, and will not function 
 * without div.js being imported into the same document.
 *
 * Parameters:
 *	(All of the following are objects of type Div)
 *	viewDiv - The viewport div, inside of which the div containing the scroll area's content
 * 		will be nested. The viewport must be clipped, must have its height set, and must have
 *		overflow: hidden.
 * 	contentDiv - The div inside which the actual content of the scrolling window resides. This
 * 		div must be nested inside the viewport div.
 * 	vertScrollTrack - The div inside of which the vertical scrollbar's thumb will travel, if present.
 * 	vertScrollThumb - The vertical scrollbar's thumb, if present.
 * 	horizScrollTrack - The div inside of which the horizontal scrollbar's thumb will travel, if present.
 * 	horizScrollThumb - The horizontal scrollbar's thumb, if present.
 */ 
	
function ScrollArea(viewDiv, contentDiv, vertScrollTrack, vertScrollThumb, horizScrollTrack, horizScrollThumb)
{	if(!ScrollArea.collection) ScrollArea.collection = new Array();
	this.index = ScrollArea.collection.length;
	ScrollArea.collection[this.index] = this;
		
	this.view = viewDiv;
	this.content = contentDiv;
		
	this.scrollIncrement = ScrollArea.defaultIncrement;
	this.scrollDelay = ScrollArea.defaultDelay;
	
	this.verticalScroll = (vertScrollTrack && vertScrollTrack != null && vertScrollThumb && vertScrollThumb != null) 
		? new ScrollBar(this, vertScrollTrack, vertScrollThumb, ScrollBar.VERTICAL) : null;
	this.horizontalScroll = (horizScrollTrack && horizScrollTrack != null && horizScrollThumb && horizScrollThumb != null) 
		? new ScrollBar(this, horizScrollTrack, horizScrollThumb, ScrollBar.HORIZONTAL) : null;	
		
	this.onscroll = null;
	this.onscrollstop = null;

	this.timeout = null;
	
	return this;
}

/*
 * Default increment, or number of pixels the scrolling content will move each time.
 */
ScrollArea.defaultIncrement = 5;

/*
 * Default delay, or number of milliseconds between movements of the scrolling content.
 */
ScrollArea.defaultDelay = 30;

/*
 * Adjusts the scrollbars (if present) to be aligned with the proportional position of the content
 * in relation to its viewport.
 */
ScrollArea.prototype.adjustScrollbars = function()
{	if(this.verticalScroll != null)
		this.verticalScroll.moveThumbTo(((this.verticalScroll.track.getHeight() - this.verticalScroll.thumb.getHeight()) 
			* ((-this.content.getTop() / (this.content.getHeight() - this.view.getHeight())) * 100)) / 100);
		
	if(this.horizontalScroll != null)
		this.horizontalScroll.moveThumbTo(((this.horizontalScroll.track.getHeight() - this.horizontalScroll.thumb.getHeight()) 
			* ((-this.content.getLeft() / (this.content.getWidth() - this.view.getWidth())) * 100)) / 100);
}

/*
 * Scrolls the content area to the position marked by x and y.
 * If a handler has been provided for onscroll, it is invoked.
 */
ScrollArea.prototype.scrollTo = function(x, y)
{	if(!isNaN(x) && !isNaN(y))
	{	this.content.moveTo(x, y);
		if(this.onscroll != null) eval(this.onscroll);
				
		this.adjustScrollbars();
	}
}

/*
 * Scrolls the content area by the number of pixels indicated by dx and dy.
 */
ScrollArea.prototype.scrollBy = function(dx, dy)
{	this.scrollTo(this.content.getLeft() + dx, this.content.getTop() + dy);
}

/*
 * Scrolls the content area up until either cancelScroll() is called or the
 * content area is unable to travel further.
 */
ScrollArea.prototype.scrollUp = function()
{	if(this.canScrollUp())
	{	ScrollArea.collection[this.index].scrollBy(0, this.scrollIncrement);
		this.timeout = window.setTimeout("ScrollArea.collection[" + this.index + "].scrollUp();", this.scrollDelay);		
	}
	else this.cancelScroll();
}

/*
 * Scrolls the content area down until either cancelScroll() is called or the
 * content area is unable to travel further.
 */
ScrollArea.prototype.scrollDown = function()
{	if(this.canScrollDown())
	{	ScrollArea.collection[this.index].scrollBy(0, -this.scrollIncrement);
		this.timeout = window.setTimeout("ScrollArea.collection[" + this.index + "].scrollDown();", this.scrollDelay);
	}
	else this.cancelScroll();
}

/*
 * Scrolls the content area left until either cancelScroll() is called or the
 * content area is unable to travel further.
 */
ScrollArea.prototype.scrollLeft = function()
{	if(this.canScrollLeft())
	{	ScrollArea.collection[this.index].scrollBy(this.scrollIncrement, 0);
		this.timeout = window.setTimeout("ScrollArea.collection[" + this.index + "].scrollLeft();", this.scrollDelay);		
	}
	else this.cancelScroll();
}

/*
 * Scrolls the content area right until either cancelScroll() is called or the
 * content area is unable to travel further.
 */
ScrollArea.prototype.scrollRight = function()
{	if(this.canScrollRight())
	{	ScrollArea.collection[this.index].scrollBy(-this.scrollIncrement, 0);
		this.timeout = window.setTimeout("ScrollArea.collection[" + this.index + "].scrollRight();", this.scrollDelay);
	}
	else this.cancelScroll();
}

/*
 * Cancels the scroll area's scrolling.
 * If a handler has been provided for onscrollstop, it is invoked.
 */
ScrollArea.prototype.cancelScroll = function()
{	if(this.timeout != null)
	{	if(this.onscrollstop != null) eval(this.onscrollstop);
		window.clearTimeout(this.timeout);
		ScrollBar.endDrag();
	}
}

/*
 * Tests whether this scroll area can scroll in any direction.
 */
ScrollArea.prototype.canScroll = function()
{	return (this.canScrollVertically() || this.canScrollHorizontally());
}

/*
 * Tests whether this scroll area can scroll vertically.
 */
ScrollArea.prototype.canScrollVertically = function()
{	return (this.canScrollUp() || this.canScrollDown());
}

/*
 * Tests whether this scroll area can scroll horizontally.
 */
ScrollArea.prototype.canScrollHorizontally = function()
{	return (this.canScrollLeft() || this.canScrollRight());
}

/*
 * Tests whether this scroll area can scroll down.
 */
ScrollArea.prototype.canScrollDown = function()
{	return (this.content.getTop() > (this.view.getHeight() - this.content.getHeight()));
}

/*
 * Tests whether this scroll area can scroll up.
 */
ScrollArea.prototype.canScrollUp = function()
{	return (this.content.getTop() < 0);
}

/*
 * Tests whether this scroll area can scroll left.
 */
ScrollArea.prototype.canScrollLeft = function()
{	return (this.content.getLeft() < 0);
}

/*
 * Tests whether this scroll area can scroll right.
 */
ScrollArea.prototype.canScrollRight = function()
{	return (this.content.getLeft() > (this.view.getWidth() - this.content.getWidth()));
}

/*
 * Activates this scroll area's vertical scrollbar, if present.
 */
ScrollArea.prototype.activateVertical = function()
{	if(this.verticalScroll && this.verticalScroll != null)
		ScrollBar.activate(this.verticalScroll);	
}

/*
 * Activates this scroll area's horizontal scrollbar, if present.
 */
ScrollArea.prototype.activateHorizontal = function()
{	if(this.horizontalScroll && this.horizontalScroll != null)
		ScrollBar.activate(this.horizontalScroll);	
}

/*
 * A string representation of this ScrollArea object.
 */
ScrollArea.prototype.toString = function()
{	return "[object ScrollArea]";
}

/*
 * Constructs a ScrollBar object. A ScrollArea object can contain
 * two scrollbars, one vertical and one horizontal. 
 *
 * Parameters:
 * 	scrollAreaParent - The ScrollArea object that is this 
 *		scroll bar's parent.
 *	trackDiv - A Div object representing this scrollbar's track.
 *  thumbDiv - A Div object representing this scrollbar's thumb.
 * 	orient - A number which must be one of ScrollBar.VERTICAL or
 * 		ScrollBar.HORIZONTAL, with the default being VERTICAL. 
 *		This number tells the scrollbar whether to act as a
 *		vertical or horizontal scrollbar for the scroll area.
 */
function ScrollBar(scrollAreaParent, trackDiv, thumbDiv, orient)
{	this.parent = scrollAreaParent;
	this.track = trackDiv;
	this.thumb = thumbDiv;
	this.orientation = (orient && !isNaN(orient)) ? orient : ScrollBar.VERTICAL;
		
	return this;
}

ScrollBar.VERTICAL = 1;
ScrollBar.HORIZONTAL = 2;
ScrollBar.current = null;

/*
 * Gets the current position of this scrollbar's thumb within the track.
 */
ScrollBar.prototype.getThumbPosition = function()
{	return (this.orientation == ScrollBar.VERTICAL) ? this.thumb.getTop() 
		: (this.orientation == ScrollBar.HORIZONTAL) ? this.thumb.getLeft() : -1;
}

/*
 * Gets either the width or the height of the thumb, depending on this
 * scrollbar's defined orientation.
 */
ScrollBar.prototype.getThumbLength = function()
{	return (this.orientation == ScrollBar.VERTICAL) ? this.thumb.getHeight()
		: (this.orientation == ScrollBar.HORIZONTAL) ? this.thumb.getWidth() : -1;
}

/*
 * Gets either the width or the height of the track, depending on this
 * scrollbar's defined orientation.
 */
ScrollBar.prototype.getTrackLength = function()
{	return (this.orientation == ScrollBar.VERTICAL) ? this.track.getHeight()
		: (this.orientation == ScrollBar.HORIZONTAL) ? this.track.getWidth() : -1;
}

/*
 * Moves this scrollbar's thumb to the specified position. The argument
 * shouldAdjust is a boolean indicating whether or not to readjust the
 * content to reflect this change.
 */
ScrollBar.prototype.setThumbPosition = function(pos, shouldAdjust)
{	if(!isNaN(pos) && pos >= 0)
	{	thumbMax = (this.getTrackLength() - this.getThumbLength());
		if(this.orientation == ScrollBar.VERTICAL)
		{	if(pos > thumbMax) pos = thumbMax;
			this.thumb.setTop(pos);
		}
		else if(this.orientation == ScrollBar.HORIZONTAL)
		{	if(pos > thumbMax) pos = thumbMax;
			this.thumb.setLeft(pos);
		}
		if(shouldAdjust) ScrollBar.adjustContent();	
	}
}

/*
 * Moves this scrollbar's thumb to the specified position. The argument
 * shouldAdjust is a boolean indicating whether or not to readjust the
 * content to reflect this change.
 */
ScrollBar.prototype.moveThumbTo = function(pos, shouldAdjust)
{	this.setThumbPosition(pos, shouldAdjust);
}

/*
 * Moves this scrollbar's by the specified change in pixels. The argument
 * shouldAdjust is a boolean indicating whether or not to readjust the
 * content to reflect this change.
 */
ScrollBar.prototype.moveThumbBy = function(dPos, shouldAdjust)
{	this.moveThumbTo(parseInt(this.getThumbPosition() + dPos), shouldAdjust);
}

/*
 * Adjusts the content in the scroll area to be aligned with the proportional 
 * position of the thumb in relation to its track.
 */
ScrollBar.adjustContent = function()
{	if(ScrollBar.current != null)
	{	sa = ScrollBar.current.parent;
		
		if(ScrollBar.current.orientation == ScrollBar.VERTICAL)
			sa.content.setTop(-(((sa.content.getHeight() - sa.view.getHeight()) 
				* ((ScrollBar.current.thumb.getTop() / (ScrollBar.current.track.getHeight() - ScrollBar.current.thumb.getHeight())) * 100)) / 100));
	
		else if(ScrollBar.current.orientation == ScrollBar.HORIZONTAL)
			sa.content.setLeft(-(((sa.content.getWidth() - sa.view.getWidth()) 
				* ((ScrollBar.current.thumb.getLeft() / (ScrollBar.current.track.getWidth() - ScrollBar.current.thumb.getWidth())) * 100)) / 100));
	}	
}

/*
 * Sets the specified scroll bar object to the currently active scrollbar.
 * This is necessary for NS 4 to start capturing the correct events.
 */
ScrollBar.activate = function(scrollBarObj)
{	ScrollBar.current = scrollBarObj;
	document.onmousedown = ScrollBar.startDrag;
	document.onmouseup = ScrollBar.endDrag;
	document.onmousemove = ScrollBar.drag;
	
	if(document.layers)
		document.captureEvents(Event.MOUSEDOWN | Event.MOUSEMOVE | Event.MOUSEUP);
	else if(document.all || document.getElementById) 
		document.onselectstart = function() { return false; }
}

ScrollBar.tap = function(evt) 
{	if(ScrollBar.current != null)
	{	offset = .2;
	
		direction = (ScrollBar.current.orientation == ScrollBar.VERTICAL)
			? ScrollBar.lastY ? document.all ? (event.y > ScrollBar.lastY) ? 1 : -1 : (evt.pageY > ScrollBar.lastY) 
				? 1 : -1 : 1 : (ScrollBar.current.orientation == ScrollBar.HORIZONTAL) ? ScrollBar.lastX
					? document.all ? (event.x > ScrollBar.lastX) ? 1 : -1 : (evt.pageX > ScrollBar.lastX) 
						? 1 : -1 : 1 : 0;
				
		if(direction != 0)
			ScrollBar.current.moveThumbBy(parseInt(direction * offset * ScrollBar.current.getTrackLength()), true);	
		
		ScrollBar.endDrag();
	}
}

/*
 * Prepares the thumb to be dragged by recording the last x and y
 * positions of the mouse.
 */
ScrollBar.startDrag = function(evt)
{	ScrollBar.lastX = (document.all) ? event.x : evt.pageX;
	ScrollBar.lastY = (document.all) ? event.y : evt.pageY;
}

/*
 * Handler for thumb dragging. Compares the current x and y positions
 * of the mouse to their original values, then moves the thumb by
 * that amount.
 */
ScrollBar.drag = function(evt)
{	if(ScrollBar.current != null && ScrollBar.current.parent.canScroll())
	{	if(ScrollBar.current.orientation == ScrollBar.VERTICAL)
		{	newY = (document.all) ? event.y : evt.pageY;
			dy = (newY - ScrollBar.lastY);	
			if(dy != 0)
			{	ScrollBar.current.moveThumbBy(dy, true);
				ScrollBar.lastY = newY;
			}
		}
		else if(ScrollBar.current.orientation == ScrollBar.HORIZONTAL)
		{	newX = (document.all) ? event.x : evt.pageX;
			dx = (newX - ScrollBar.lastX);
			if(dx != 0)
			{	ScrollBar.current.moveThumbBy(dx, true);
				ScrollBar.lastX = newX;
			}
		}
	}		
}

/*
 * Cleans up after the dragging functionality is no longer needed.
 */
ScrollBar.endDrag = function() 
{	if(document.layers) 
		document.releaseEvents(Event.MOUSEDOWN | Event.MOUSEMOVE | Event.MOUSEUP);
		
	ScrollBar.current = null;
	document.onselectstart = null;	
}

/*
 * A string representation of this ScrollBar object.
 */
ScrollBar.prototype.toString = function()
{	return "[object ScrollBar {orientation: " + 
		((this.orientation == ScrollBar.VERTICAL) ? "vertical" : (this.orientation == ScrollBar.HORIZONTAL) ? "horizontal" : "unknown") + "}]";
}
