// ScrollBar.js - JavaScript scroll bar implmentation.

// Define the names of the images used for horizontal and vertical scroll bars.
var imgNamesH = [ "Images/arrowleft.gif",		// <-
				  "Images/back.gif",			//   XX
				  "Images/thumb20h.gif",		//     []
				  "Images/arrowright.gif" ];	//       ->
var imgNamesV = [ "Images/arrowup.gif",			//   ^
				  "Images/back.gif",			//   X
				  "Images/thumb20v.gif",		//  [ ]
				  "Images/arrowdown.gif" ];		//   v
				  
var scrollBarImageWidth = 20;

// This is the constructor function for our ScrollBar class.
// Calling it creates a ScrollBar object and emits all the
// required HTML tags into the specified document at the 
// current location.
// doucument	is the Document object in which the scrollbar is to be created.
// orientation	specifies which direction the scrollbar is: "h" or "v"
function ScrollBar( document, orientation )
	{
	//debug( "»ScrollBar()" );

	// The first time we are called (and only the first time) we have
	// to do some speciall stuff. Now that the prototype object is
	// created we can set up our methods.
	if( !ScrollBar.prototype.hide )
		{
		// Initialize the prototype object to create our methods.
		ScrollBar.prototype.hide     = ScrollBarHide;
		ScrollBar.prototype.show     = ScrollBarShow;
		ScrollBar.prototype.update   = ScrollBarUpdate;
		ScrollBar.prototype.scrollBy = ScrollBarScrollBy;

		// setHeight is not provided. Scroll bars resize to fit in their parent element.
		ScrollBar.prototype.setItems     = ScrollBarSetItems;
		ScrollBar.prototype.setVisible   = ScrollBarSetVisible;
		ScrollBar.prototype.setThumbSize = ScrollBarSetThumbSize;
		ScrollBar.prototype.setTop       = ScrollBarSetTop;

		ScrollBar.prototype.getHeight    = ScrollBarGetHeight;
		ScrollBar.prototype.getItems     = ScrollBarGetItems;
		ScrollBar.prototype.getVisible   = ScrollBarGetVisible;
		ScrollBar.prototype.getThumbSize = ScrollBarGetThumbSize;
		ScrollBar.prototype.getTop       = ScrollBarGetTop;
		}

	// Validate the orientation.
	this.orientation = orientation;
	this.horizontal = orientation == "h";
	this.vertical   = orientation == "v";
	if( !this.horizontal && !this.vertical )
		{
		document.write( "<div class='B2020H1'>ScrollBar orientation must be 'h' or 'v'.</div>" );
		return;
		}
	this.myWidth  = this.vertical   ? "width" : "height";
	this.myHeight = this.horizontal ? "width" : "height";

	// Preload the images that we will need.
	var imageNames = this.horizontal ? imgNamesH : imgNamesV;
	this.images = new Array( imageNames.length );
	for( var i = 0; i < imageNames.length; i++ )
		{
		this.images[ i ] = new Image();
		this.images[ i ].src = imageNames[ i ];
		}

	// Save the documen argument.
	this.document = document;

	// Initialize the parameters of this scroll bar.
	this.height		=  0;		// "Height" in pixels of displayed scrollbar.
	this.items		=  1;		// Total number of items in the "list".
	this.visible	=  1;		// Number of items currently visible.
	this.top		=  0;		// First item that is visible.
	this.thumbSize	= 20;		// Size of the "thumb" (needed to drag properly).

	this.heightOld	 = -1;		// Previous values of these parameters.
	this.itemsOld	 = 0;
	this.visibleOld	 = 0;
	this.topOld		 = 0;

	this.imageWidth = scrollBarImageWidth;	// Width of the scroll bar image.  Not the current width
											// of the scroll bar, but the width of the scroll bar when
											// it is displayed.
	// Initialize the call backs.
	this.showToolTip   = null;
	this.hideToolTip   = null;
	this.performScroll = null;

	// Figure out what entry in the document.images[] array the images
	// for this scrollbar will be stored in.
	var index = document.images.length;
	
	// Now output the HTML code for the scrollbar.
	var br = this.vertical ? "<br>\n" : "";
	document.write( "<img alt='' src='" + imageNames[ 0 ] + "' onmousedown='imgClick(-1,this,event);' />" + br );
	document.write( "<img alt='' src='" + imageNames[ 1 ] + "' onmousedown='imgClick(-this.sbar.visible,this,event);' />" + br );
	document.write( "<img alt='' src='" + imageNames[ 2 ] + "' onmousedown='thumbDrag(this,event);' />" + br );
	document.write( "<img alt='' src='" + imageNames[ 1 ] + "' onmousedown='imgClick(this.sbar.visible,this,event);' />" + br );
	document.write( "<img alt='' src='" + imageNames[ 3 ] + "' onmousedown='imgClick(1,this,event);' />" );

	// Now that we have output the <img> tags, save a reference to the Image object
	// that it created in the ScrollBar object. Create links from the images back
	// to the ScrollBar object.
	this.arrowUp = document.images[ index++ ];
	this.arrowUp.sbar = this;
	this.backUp = document.images[ index++ ];
	this.backUp.sbar = this;
	this.thumb = document.images[ index++ ];
	this.thumb.sbar = this;
	this.backDown = document.images[ index++ ];
	this.backDown.sbar = this;
	this.arrowDown = document.images[ index++ ];
	this.arrowDown.sbar = this;

	// Hide the scroll bar.
	function ScrollBarHide()
		{
		//debug( "»ScrollBarHide()" );

		// If the scroll bar is not already hidden, hide it
		// (set height of all components to zero).
		if( this.heightOld != 0 )
			{
			this.thumb[     this.myHeight ] = 0;
			this.backUp[    this.myHeight ] = 0;
			this.backDown[  this.myHeight ] = 0;
			this.arrowUp[   this.myHeight ] = 0;
			this.arrowDown[ this.myHeight ] = 0;

			this.thumb[     this.myWidth ] = 0;
			this.backUp[    this.myWidth ] = 0;
			this.backDown[  this.myWidth ] = 0;
			this.arrowUp[   this.myWidth ] = 0;
			this.arrowDown[ this.myWidth ] = 0;

			this.heightOld = 0;
			}

		//debug( "«ScrollBarHide()" );
		}

	// Show the scroll bar.
	function ScrollBarShow()
		{
		//debug( "»ScrollBarShow()" );

		// Determine the height available to the scroll bar.
		// Leave some space or the scrollbars will force the table size.
		if( this.vertical )
			{
			if( document.all )
				this.height = this.thumb.parentElement.clientHeight;	// IE
			else
				this.height = this.thumb.parentNode.scrollHeight;		// NN
			}
		else
			{
			if( document.all )
				this.height = this.thumb.parentElement.clientWidth;	// IE
			else
				this.height = this.thumb.parentNode.scrollWidth;		// NN
			}

		// If the height available is different than the current height
		if( this.height != this.heightOld )
			{
			// Remember the new height.
			this.heightOld = this.height;

			// If there is any need for a scroll bar
			if( this.visible < this.items )
				{
				// Calculate the size of the thumb. Don't get too small to be useful.
				this.thumbSize = Math.round( ( this.height - 40 ) * this.visible / this.items );
				var thumbExtra = 0;
				if( this.thumbSize < 10 )
					{
					thumbExtra = 10 - this.thumbSize;
					this.thumbSize = 10;
					}

				// Calculate which thumb image to use from the available set of
				// { { 20, 40, ..., 200 }, { 300, 400, ..., 1000 } } pixels high
				// and set the thumb size and image.
				var thumbImgSize = Math.round( this.thumbSize / 20 ) * 20;
				if( thumbImgSize > 200 )
					{
					thumbImgSize = Math.round( this.thumbSize / 100 ) * 100;
					if( thumbImgSize > 1000 )
						thumbImgSize = 1000;
					}
				this.setThumbSize( thumbImgSize );

				// Calculate the size of the page scroll areas around the thumb.
				var backupSize = Math.round( ( this.height - ( 40 + thumbExtra ) ) * this.top / this.items );
				if( backupSize < 0 )
					backupSize = 0;
				var backdownSize = this.height - ( 40 + this.thumbSize + backupSize );
				if( backdownSize < 0 )
					{
					if( backupSize >= -backdownSize )
						backupSize += backdownSize;
					else
						backupSize = 0;
					backdownSize = 0;
					}

				// Update the size of the areas (other than the thumb).
				this.thumb[     this.myHeight ] = this.thumbSize;
				this.thumb[     this.myWidth  ] = 20;
				this.backUp[    this.myHeight ] = backupSize;
				this.backUp[    this.myWidth  ] = 20;
				this.backDown[  this.myHeight ] = backdownSize;
				this.backDown[  this.myWidth  ] = 20;
				this.arrowUp[   this.myHeight ] = 20;
				this.arrowUp[   this.myWidth ] = 20;
				this.arrowDown[ this.myHeight ] = 20;
				this.arrowDown[ this.myWidth ] = 20;
				}
			
			// Otherwise all items are visible so hide the scroll bar.
			else
				this.hide();
			}

		//debug( "«ScrollBarShow()" );
		}

	// Sets the thumb size by updating the image to be displayed.
	function ScrollBarSetThumbSize( size )
		{
		//debug( "»ScrollBarSetThumbSize( " + size + " )" );

		// Get the old file name and strip it into components. Discard any old size infromation.
		var fileName = this.thumb.src;
		var ext = fileName.substring( fileName.lastIndexOf( "." ), fileName.length );
		var path = fileName.substring( 0, fileName.lastIndexOf( "/" ) + 1 );
		var name = fileName.substring( fileName.lastIndexOf( "/" ) + 1, fileName.lastIndexOf( "." ) )
		ext = this.orientation + ext;
		name = name.substring( 0, name.length - 2 );
		while( ( "0" <= name.charAt( name.length - 1 ) ) &&
			( name.charAt( name.length - 1 ) <= "9" ) )
			name = name.substring( 0, name.length - 1 );

		// Create the new file name and if a change is needed update the image.
		var newFileName = path + name + size + ext;
		if( this.thumb[ this.verticalHeight ] != size )
			this.thumb.src = newFileName;

		//debug( "«ScrollBarSetThumbSize()" );
		}

	// Update and validate the scroll bar items counts and positions.
	// Return true if any changes occurred.
	function ScrollBarUpdate()
		{
		//debug( "»ScrollBarUpdate()" );

		var changed = false;

		// Number of items must be greater than or equal to zero.
		if( this.items < 0 )
			this.items = 0;

		// Number of visible items must also be greater than or equal to zero,
		// less than or equal to the number of items, and can not be zero if there are any items.
		if( this.visible < 0 )
			this.visible = 0;
		if( this.visible > this.items )
			this.visible = this.items;
		if( ( this.visible == 0 ) && ( this.items != 0 ) )
			this.visible = 1;

		// The top position must be less than or equal to the number of invisible items and
		// must be greater than or eqaul to zero.
		if( this.top > ( this.items - this.visible ) )
			this.top = ( this.items - this.visible );
		if( this.top < 0 )
			this.top = 0;

		// If any of the values have changed, then the scroll bar has changed.
		changed = ( this.items   != this.itemsOld   ) ||
				  ( this.visible != this.visibleOld ) ||
				  ( this.top     != this.topOld     );

		// If so, update the old values.
		if( changed )
			{
			this.itemsOld   = this.items;
			this.visibleOld = this.visible;
			this.topOld     = this.top;
			}

		//debug( "«ScrollBarUpdate()" );
		return changed;
		}

	// Scroll the scroll bar by the specified amount.
	function ScrollBarScrollBy( delta )
		{
		// Update the top position by the specified ammount.
		this.top += delta;

		// If the scroll occurs (not at a limit)
		if( this.update() )
			{
			// Scroll the data.
			if( this.performScroll != null )
				this.performScroll();
			}
		}

	// Set the number of items in the scroll bar
	function ScrollBarSetItems( items )
		{
		//debug( "»ScrollBarSetItems( " + items + " )" );

		this.items = items;
		this.top   = 0;		// Since we have changed the number of items, go back to the top.

		//debug( "«ScrollBarSetItems()" );
		}

	// Set the number of visible items in the scroll bar
	function ScrollBarSetVisible( visible )
		{
		//debug( "»ScrollBarSetVisible( " + visible + " )" );

		this.visible = visible;

		//debug( "«ScrollBarSetVisible()" );
		}

	// Set the first visible item in the scroll bar
	function ScrollBarSetTop( top )
		{
		//debug( "»ScrollBarSetTop( " + top + " )" );

		this.top = top;

		//debug( "«ScrollBarSetTop()" );
		}

	// Get various attributes of the scroll bar.
	function ScrollBarGetHeight()    { return this.height;    }
	function ScrollBarGetItems()     { return this.items;     }
	function ScrollBarGetVisible()   { return this.visible;   }
	function ScrollBarGetThumbSize() { return this.thumbSize; }
	function ScrollBarGetTop()       { return this.top;       }

	//debug( "«ScrollBar()" );
	}

// imgClick() is designed to be called from an onmousedown event handler.
// amount	specifies the number of items to scroll (typically 1, -1, or +/- visible page size).
// obj		must specify the image that was clicked.
// e		must be the Event object for the mousedown event.
// This implementation works with both the DOM Level 2 event model and the IE Event model.

var timeToWait = 500;		// Initially delay for 500 mS before autorepeating, then repeat at 100 mS intervals.
var timeOut = 0;			// Timeout object for autorepeat

var doRepeat = null;		// This is a kludge to allow us to callback a local function.

function imgClick( amount, obj, e )
	{
	//debug( "»imgClick( " + amount + " )" );

	if( !e )
		e = window.event; // IE event model

	// Update the image to have the pressed bitmap.
	if( obj )
		setButtonState( "press" );

	// Allow the repeat local function to be called back and then do the first operation.
	doRepeat = repeat;
	doRepeat();

	// Register the event handlers that will respond to the mouseout events
	// and the mouseup event that follow this mousedown event.
	if( document.addEventListener )		// DOM Level 2 Event Model
		{
		// Register capturing event handlers
		document.addEventListener( "mouseout", outHandler, true );
		document.addEventListener( "mouseup",  upHandler,  true );
		}
	else if( document.attachEvent )		// IE 5+ Event Model
		{
		// In the IE Event model, we can't capture events, so these handlers
		// are triggered when only if the event bubbles up to them.
		// This assumes that there aren't any intervening elements that
		// handle the events and stop them from bubbling.
		document.attachEvent( "onmouseout", outHandler );
		document.attachEvent( "onmouseup",  upHandler  );
		}
	else								// IE 4 Event Model
		{
		// In IE 4 we can't use attachEvent(), so assign the event handlers
		// directly after storing any previously assigned handlers so they
		// can be restored. Note that this also relies on event bubbling.
		var oldouthandler   = document.onmouseout;
		var olduphandler    = document.onmouseup;
		document.onmouseout = outHandler;
		document.onmouseup  = upHandler;
		}

	// We've handled this event. Don't let anybody else see it.
	if( e.stopPropagation )
		e.stopPropagation();		// DOM Level 2
	else
		e.cancelBubble = true;		// IE

	// Now prevent any default action.
	if( e.preventDefault )
		e.preventDefault();			// DOM Level 2
	else
		e.returnValue = false;		// IE

	// Function to be executed to do autorepeat.
	function repeat()
		{
		//debug( "»repeat()" );

		// Ensure the timeout is complete.
		timeOut = 0;

		// Update the top position by the specified ammount.
		obj.sbar.setTop( obj.sbar.getTop() + amount );

		// If the scroll occurs (not at a limit)
		if( obj.sbar.update() )
			{
			// Set up another event to do the autorepeat
			// (500 mS delay first time, 100 mS repeat rate).
			timeOut = setTimeout( "doRepeat();", timeToWait );
			timeToWait = 100;

			// Scroll the data.
			if( obj.sbar.performScroll != null )
				obj.sbar.performScroll();
			}

		// Otherwise we are at a limit, so don't autorepeat any more and reset the timeout.
		else
			timeToWait = 500;

		//debug( "«repeat()" );
		}

	// This is the handler that captures the final mouseup event that
	// occurs at the end of a click (up or out). Both up and out are handled
	// the same way.
	function upHandler( e ) { outHandler( e ) }
	
	function outHandler( e )
		{
		//debug( "»outHandler()" );

		if( !e )
			e = window.event; // IE event model

		// Clear the autorepeat.
		clearTimeout( timeOut );
		timeOut = 0;
		timeToWait = 500;

		// Restore the image to have the unpressed bitmap.
		setButtonState( "" );

		// Unregister the capturing event handlers.
		if( document.removeEventListener )
			{							// DOM Event Model
			document.removeEventListener( "mouseup",  upHandler,  true );
			document.removeEventListener( "mouseout", outHandler, true );
			}
		else if( document.detachEvent )	// IE 5+ Event Model
			{
			document.detachEvent( "onmouseup",  upHandler  );
			document.detachEvent( "onmouseout", outHandler );
			}
		else							// IE 4 Event Model
			{
			document.onmouseup  = olduphandler;
			document.onmouseout = oldouthandler;
			}

		// And don't let the event propagate any further.
		if( e.stopPropagation )
			e.stopPropagation();		// DOM Level 2
		else
			e.cancelBubble = true;		// IE

		//debug( "«outHandler()" );
		}

	// Sets the button state by updating the image to be displayed. State should either be "",
	// or should be the tail end of the file names, added with a leading underscore.
	function setButtonState( state )
		{
		//debug( "»setButtonState( " + state + " )" );

		// Get the old file name and strip it into components. Discard any suffix.
		var fileName = obj.src;
		var ext = fileName.substring( fileName.lastIndexOf( "." ), fileName.length );
		var path = fileName.substring( 0, fileName.lastIndexOf( "/" ) + 1 );
		var name = fileName.substring( fileName.lastIndexOf( "/" ) + 1, fileName.lastIndexOf( "." ) )
		if( name.indexOf( "_" ) > 0 )
			name = name.substring( 0, name.indexOf( "_" ) );

		// If there is a new suffix, add it.
		if( state != "" )
			state = "_" + state;

		// Create the new file name and if a change is needed update the image.
		var newFileName = path + name + state + ext;
		if( newFileName != fileName )
			obj.src = newFileName;

		//debug( "«setButtonState()" );
		}

	//debug( "«imgClick()" );
	}

// thumbDrag() is designed to be called from an onmousedown event handler.
// obj	must specify the image that was clicked.
// e	must be the Event object for the mousedown event.
// This implementation works with both the DOM Level 2 event model and the IE Event model.
function thumbDrag( obj, e )
	{
	//debug( "»thumbDrag()" );

	if( !e )
		e = window.event; // IE event model

	// Record the original pixel position of the thumb and the first visible item.
	var posOrig = obj.sbar.horizontal ? e.clientX : e.clientY;
	var topOrig = obj.sbar.getTop();
	
	// Register the event handlers that will respond to the mousemove events
	// and the mouseup event that follow this mousedown event.
	if( document.addEventListener )		// DOM Level 2 Event Model
		{
		// Register capturing event handlers
		document.addEventListener( "mousemove", moveHandler, true );
		document.addEventListener( "mouseup",   upHandler,   true );
		}
	else if( document.attachEvent )		// IE 5+ Event Model
		{
		// In the IE Event model, we can't capture events, so these handlers
		// are triggered when only if the event bubbles up to them.
		// This assumes that there aren't any intervening elements that
		// handle the events and stop them from bubbling.
		document.attachEvent( "onmousemove", moveHandler );
		document.attachEvent( "onmouseup",   upHandler   );
		}
	else								// IE 4 Event Model
		{
		// In IE 4 we can't use attachEvent(), so assign the event handlers
		// directly after storing any previously assigned handlers so they
		// can be restored. Note that this also relies on event bubbling.
		var oldmovehandler   = document.onmousemove;
		var olduphandler     = document.onmouseup;
		document.onmousemove = moveHandler;
		document.onmouseup   = upHandler;
		}

	// We've handled this event. Don't let anybody else see it.
	if( e.stopPropagation )
		e.stopPropagation();		// DOM Level 2
	else
		e.cancelBubble = true;		// IE

	// Now prevent any default action.
	if( e.preventDefault )
		e.preventDefault();			// DOM Level 2
	else
		e.returnValue = false;		// IE

	// This is the handler that captures mousemove events when the thumb
	// is being dragged. It is responsible for scrolling the data.
	function moveHandler( e )
		{
		//debug( "»moveHandler()" );

		if( !e )
			e = window.event; // IE event model

		// Calculate the new top position and refresh.
		// If the scroll bar is collapsed to just the thumb and two buttons,
		// scroll by the number of pixels the thumb is "moved". Otherwise,
		// use the proportion of the visible items to total items and
		// the movement to the total size.
		var h = obj.sbar.getHeight() - ( 40 + obj.sbar.getThumbSize() );
		var posDiff = ( obj.sbar.horizontal ? e.clientX : e.clientY ) - posOrig;
		if( h > 0 )
			posDiff *= ( obj.sbar.getItems() - obj.sbar.getVisible() ) / h;
		obj.sbar.setTop( Math.round( topOrig + posDiff ) );

		// Perform the actual scrolling, but allow it to proceeed asynchronously
		// so that this event completes first. If a timeout is already in progress
		// then show the tooltip since we are scrolling faster than the table is
		// being updated.
		if( obj.sbar.update() )
			{
			// Force the scrollbar to redraw to show the new thumb position.
			obj.sbar.hide();
			obj.sbar.show();
			
			if( timeOut != 0 )
				{
				clearTimeout( timeOut );
				if( obj.sbar.showToolTip != null )
					obj.sbar.showToolTip( e.clientX, e.clientY );
				}
			if( obj.sbar.performScroll != null )
				timeOut = setTimeout( obj.sbar.performScroll, 200 );
			}

		// And don't let anyone else see this event.
		if( e.stopPropagation )
			e.stopPropagation();		// DOM Level 2
		else
			e.cancelBubble = true;		// IE

		// Now prevent any default action.
		if( e.preventDefault )
			e.preventDefault();			// DOM Level 2
		else
			e.returnValue = false;		// IE

		//debug( "«moveHandler()" );
		}

	// This is the handler that captures the final mouseup event that
	// occurs at the end of a drag.
	function upHandler( e )
		{
		//debug( "»upHandler()" );

		if( !e )
			e = window.event; // IE event model

		// Hide the tooltip.
		if( obj.sbar.hideToolTip != null )
			obj.sbar.hideToolTip();

		// Unregister the capturing event handlers.
		if( document.removeEventListener )
			{							// DOM Event Model
			document.removeEventListener( "mouseup",   upHandler,   true );
			document.removeEventListener( "mousemove", moveHandler, true );
			}
		else if( document.detachEvent )	// IE 5+ Event Model
			{
			document.detachEvent( "onmouseup",   upHandler   );
			document.detachEvent( "onmousemove", moveHandler );
			}
		else							// IE 4 Event Model
			{
			document.onmouseup   = olduphandler;
			document.onmousemove = oldmovehandler;
			}

		// And don't let the event propagate any further.
		if( e.stopPropagation )
			e.stopPropagation();		// DOM Level 2
		else
			e.cancelBubble = true;		// IE

		//debug( "«upHandler()" );
		}

	//debug( "«thumbDrag()" );
	}

