/*

Copyright _ 2005, Apple Computer, Inc.  All rights reserved.
NOTE:  Use of this source code is subject to the terms of the Software
License Agreement for Mac OS X, which accompanies the code.  Your use
of this source code signifies your agreement to such license terms and
conditions.  Except as expressly granted in the Software License Agreement
for Mac OS X, no other copyright, patent, or other intellectual property
license or right is granted, either expressly or by implication, by Apple.

*/


/*
 ****************************************
 * Objects used by the Scroller	*
 ****************************************
 */	
	var currentContent;			// currently-visible DIV.  Necessary for the Scroller to be shared across DIVs
	var currentContentStyle;	// style object of the currentContent
	var scrollBar;				// Parent scrollbar DIV.  Contains the track and thumb
	var scrollThumb;			// Scroller's thumb control
	var scrollTrack;			// Scroller's base/track
	var trackTimer;				// for extended mousedowns in the scroll track

/*
 ********************************************
 *  Dimensions used by the Scroller	*
 ********************************************
 */
	var currentContentHeight;				// height of currently-visible content DIV
	var currentContentTop;					// top....
	var viewHeight;							// height of the parent (overflow:hidden) view
	var trackMouseY;						// mouse location in the scroll track
	var thumbHeight;						// height of the thumb control
	var tracking					= true;	// is the mouse down in the scroll track
	var thumbStartY					= 0;	// point where we started scrolling with the thumb
	var scrollThumbStartPos			= 0;	// thumb's 'top' value when we started scrolling
	var scrollBarHeight;					// height of our scrollbar.  TBD	
	var numberOfScrollablePixels;			// for calculating thumb size/position and content position ("page number")

/*
 ********************************************************
 * Constants.  Hardcoded to match respective CSS values	*
 ********************************************************
 */
	var SCROLLBAR_TOP		= 0;
	var SCROLL_THUMB_HEIGHT	= 27;
	
	// CSS element names of critical DIVs.  Abstracted for easy customization.
	var SCROLLBAR_DIV_NAME		= 'myScrollBar';		// Name of the parent scroller div, containing the track and the thumb.
	var SCROLLTHUMB_DIV_NAME	= 'myScrollThumb';		// Div containing the scroller thumb
	var SCROLLTRACK_DIV_NAME	= 'myScrollTrack'; 		// Div containing the scroller track
	var TRACK_TOP_DIV_NAME		= 'myScrollTrackTop';	// Top edge of scroller track
	var TRACK_MID_DIV_NAME		= 'myScrollTrackMid';	// variable-size center of scroller track
	var TRACK_BOT_DIV_NAME		= 'myScrollTrackBot';	// Bottom edge of scroller track
	
	var PAGE_SKIP_PAUSE	= 150; // time (ms) between page jumps when holding the mouse down in the track.
	
// Calculate the height of the views and make the thumb proportional.
// If a single scroller is being shared across multiple divs (as in this sample),
// this function must be called whenever the divs swap, to recalibrate the scrollbar.
function calculateAndShowThumb(contentDiv) {

	scrollBar	= document.getElementById(SCROLLBAR_DIV_NAME);
	scrollThumb = document.getElementById(SCROLLTHUMB_DIV_NAME);
	scrollTrack = document.getElementById(SCROLLTRACK_DIV_NAME);
	
	if (contentDiv != null) {
		currentContent = contentDiv;
	} else if (currentContent == null) {
		hideScrollBar();
	}
	
	currentContent.style.display = 'block';
	currentContentStyle = document.defaultView.getComputedStyle(currentContent,'');
		DEBUG("cast: cct=" + currentContentStyle.getPropertyValue('top'));
	currentContentTop = parseInt(currentContentStyle.getPropertyValue('top'));
	currentContentHeight = parseInt(currentContentStyle.getPropertyValue('height'));
	
	viewHeight = parseFloat (document.defaultView.getComputedStyle(currentContent.parentNode, '').getPropertyValue('height'));
		DEBUG("cast: viewHeight=" + viewHeight);
	scrollBarHeight = parseInt(document.defaultView.getComputedStyle(scrollBar, '').getPropertyValue('height'));
		DEBUG("cast: currentContentHeight=" + currentContentHeight + " top=" + currentContentTop + " scrollBarHeight=" + scrollBarHeight);

	var percent = getProportion (viewHeight, currentContentHeight);
		DEBUG("cast: percent=" + percent);
	
	// hide the scrollbar if all the content is showing.  Determined by the calculated scrollbar height and position.
	if (percent == 0) {
			DEBUG("cast: 0% thumbHeight.  Hiding scrollbar");
		hideScrollBar();
	} else {
		// Position the thumb according to where the content is currently scrolled.
		// This is necessary for sharing the same scrollbar between multiple content
		// panes that will likely be at different scroll positions.
		thumbHeight = Math.max(Math.round(scrollBarHeight * percent), SCROLL_THUMB_HEIGHT);
		thumbTop = thumbPositionForPagePosition(currentContentTop);
		
		scrollThumb.style.height = thumbHeight + 'px';
		scrollThumb.style.top = thumbTop;
		
		numberOfScrollablePixels = scrollBarHeight - thumbHeight - SCROLLBAR_TOP;
			DEBUG("cast: new thumb height=" + scrollThumb.style.height);
		
		// This is a safeguard so the content matches the new thumb position.  Necessary for live-resizing to work.
		scrollContent(thumbTop);
		
		showScrollBar();
	}
}

// Hide the thumb and track, but keep the parent scrollbar DIV around to preserve formatting
function hideScrollBar() {
	scrollTrack.style.display = 'none';
	scrollThumb.style.display = 'none';
}

function showScrollBar() {
	scrollTrack.style.display = 'block';
	scrollThumb.style.display = 'block';
}

/*
 ********************************
 *	Thumb Scrolling Functions	*
 ********************************
 */

// This mouseDown is presumably the start of a thumb drag (scroll) action.
function mouseDownScrollThumb (event) {
	// We add these listeners and remove them later; they're only useful while there is mouse activity
	// on the thumb.  This is necessary because there is no mousedrag event in JavaScript.
	document.addEventListener("mousemove", mouseMoveScrollThumb, true);
	document.addEventListener("mouseup", mouseUpScrollThumb, true);
	
	thumbStartY = event.y;

	scrollThumbStartPos = parseInt(document.defaultView.getComputedStyle(scrollThumb,'').getPropertyValue('top'));

		DEBUG("mdThumbHeight:" + thumbHeight + " thumbStartY=" + thumbStartY);
}

// At this point we are dragging the scrollThumb.  We know this because the mousemove listener is only installed
// after a mousedown.
function mouseMoveScrollThumb (event) {
	var deltaY = event.y - thumbStartY;
	
	var newPosition = scrollThumbStartPos + deltaY;
		DEBUG("mmst: event.y=" + event.y + " thumbStartY=" + thumbStartY + " thumbStart=" + scrollThumbStartPos);
	scrollContent(newPosition);
}

function scrollContent(newThumbPosition) {
		DEBUG("scrollContent: newPositionIn=" + newThumbPosition);

	// Correct if we're going to clip above the top or below the bottom
	if (newThumbPosition < SCROLLBAR_TOP) {
			DEBUG("sc: thumb too high (" + newThumbPosition + ")");
		newThumbPosition = SCROLLBAR_TOP;
	} else if ((newThumbPosition + thumbHeight) > scrollBarHeight) {
			DEBUG("sc: thumb too low (" + newThumbPosition + ")");
		newThumbPosition = scrollBarHeight - thumbHeight;
	}
		
		DEBUG("scrollContent: newPosition=" + newThumbPosition);
	scrollThumb.style.top = newThumbPosition + 'px';
	
		DEBUG("calculating delta");
	currentContentTop = pagePositionForThumbPosition(newThumbPosition);
		DEBUG("scrollContent: thumbTop=" + scrollThumb.style.top + " currentContentTop is " + currentContentTop);
	currentContent.style.top = currentContentTop + 'px';
}

function mouseUpScrollThumb (event) {
		DEBUG("must: eventY=" + event.y);
	// After mouseup, these events are just noise. Remove them; they'll be re-added on the next mouseDown
	document.removeEventListener("mousemove", mouseMoveScrollThumb, true);
	document.removeEventListener("mouseup", mouseUpScrollThumb, true);
	
	// reset the starting position
	thumbStartY = -1;
}

/*
 ********************************
 *	Track Scrolling Functions	*
 ********************************
 */

function mouseDownTrack (event) {
	updateTrackMouseY(event);
	
	scrollTrack.addEventListener("mousemove", mouseMoveTrack, false);
	scrollTrack.addEventListener("mouseover", mouseOverTrack, false);
	scrollTrack.addEventListener("mouseout", mouseOutTrack, false);
	
	// This is our handling for clicks in the track.
	var thumbTop = document.defaultView.getComputedStyle(scrollThumb,'').getPropertyValue('top');
		DEBUG("trackClick: mouseY=" + trackMouseY + " scrollThumbY=" + thumbTop);
	if (trackMouseY > parseInt(thumbTop)) {
			DEBUG("click BELOW thumb ");
		pageDown();
		trackTimer = setInterval("pageDown();", PAGE_SKIP_PAUSE);
	} else {
			DEBUG("click ABOVE thumb");
		pageUp();
		trackTimer = setInterval("pageUp();", PAGE_SKIP_PAUSE);
	}
	
	DEBUG("mdt: newPosition=" + trackMouseY);
}

function mouseMoveTrack(event) {
	// If the mouse moved while being held down, update the location so we 
	// stop the track-scrolling in the right place.
	updateTrackMouseY(event);
}

function mouseOutTrack(event) {
	// When the mouse moves out while pressed, we turn track-scrolling off.
	// The timer keeps firing, but pageUp/pageDown exits based on this value
	tracking = false;
}

function mouseOverTrack(event) {
	// The timer is still firing, but pageUp/pageDown are waiting for the mouse to
	// return to the track.  This will resume track-scrolling.
	tracking = true;
}

function mouseUpTrack(event) {
	// stop track-scrolling
	clearInterval(trackTimer);
	
	// After mouseup, these events are just noise. Remove them; they'll be re-added on the next mouseDown
	scrollTrack.removeEventListener("mousemove", mouseMoveTrack, false);
	scrollTrack.removeEventListener("mouseover", mouseMoveTrack, false);
	scrollTrack.removeEventListener("mouseout", mouseMoveTrack, false);
}

// correct the coordinates for the sourceEvent so they properly match the source component
// **YOU MAY NEED TO UPDATE THIS FUNCTION** depending on how deeply the scrollbar div is nested
function updateTrackMouseY (event) {
		DEBUG("utmY: source=" + event.toElement.id + " offsetY=" + event.offsetY + " layerY=" + event.layerY + " offsetTop=" + event.toElement.offsetTop);
	
	if (event.toElement.id == 'myScrollTrackMid') {
		// source is the ctr component of the track; offset by the top component.
		var topHeight = document.defaultView.getComputedStyle(document.getElementById(TRACK_TOP_DIV_NAME)).getPropertyValue('height');
		trackMouseY = event.offsetY + parseInt(topHeight);
			DEBUG("utmY: click in mid, topHeight=" + topHeight);
	} else if (event.toElement.id == TRACK_BOT_DIV_NAME) {
		// source is the bottom component of the track; offset by the top and the middle.
		var midHeight = document.defaultView.getComputedStyle(document.getElementById(TRACK_MID_DIV_NAME)).getPropertyValue('height');
		var topHeight = document.defaultView.getComputedStyle(document.getElementById(TRACK_TOP_DIV_NAME)).getPropertyValue('height');
		trackMouseY = event.offsetY + parseInt(midHeight) + parseInt(topHeight);
			DEBUG("utmY: click in bottom, offsetY=" + event.offsetY + " offsetTop=" + event.toElement.offsetTop + " mid+top=" + (parseInt(midHeight) + parseInt(topHeight)));
	} else {
			DEBUG("utmY: click in top, offsetY=" + event.offsetY + " offsetTop=" + event.toElement.offsetTop + " parentTop=" + event.toElement.parentNode.offsetTop);
		// source is the top of the track
		trackMouseY = event.offsetY - (event.toElement.offsetTop + event.toElement.parentNode.offsetTop);
	}	
	
		DEBUG("utmY: trackMouseY" + trackMouseY);
}

/*
 ********************************************************************
 * pageUp/pageDown													*
 * Used by track-scrolling code, but can be called independently	*
 ********************************************************************
 */ 

// Reposition the content one page (viewHeight) upwards.  Prevent out-of-bounds values.
// Remember that the content top becomes increasingly NEGATIVE (moves upwards) as we scroll down.	
function pageDown() {
	if (!tracking) return;
	// calculate the last page.  This is equal to the content's full height, less one viewHeight
	// Again, the value is negative because that's how far offset the content would need to be.
	currentContentTop = parseInt(currentContentStyle.getPropertyValue('top'));
	var lastPageY = -(currentContentHeight - viewHeight);
	// calculate the next page from the content's current position.
	var nextPageY = currentContentTop - viewHeight;
		DEBUG("pageDown: currentContent.lastPageY=" + lastPageY + " nextPageY=" + nextPageY);
	currentContentTop = Math.max(lastPageY, nextPageY);
	currentContent.style.top = currentContentTop + 'px';

	// reposition the scroll thumb based on the new page position.
	var newThumbTop = thumbPositionForPagePosition(currentContentTop);
	var thumbBottom = newThumbTop + parseInt(scrollThumb.style.height);
	scrollThumb.style.top = newThumbTop;

		DEBUG("pageDown: currentContentTop=" + currentContentTop + " currentContentHeight=" + currentContentHeight + " newThumbTop=" + newThumbTop + " thumbBottom=" + thumbBottom);

	if (trackMouseY < thumbBottom) {
		// the thumb has met the mouse; time to stop track-scrolling.
		clearInterval(trackTimer);
	}
}

// very similar to pageDown, with some values negated to move the content in a different direction.
function pageUp() {
	if (!tracking) return;
	currentContentTop = parseInt(currentContentStyle.getPropertyValue('top'));
	var firstPageY = 0;
	var nextPageY = currentContentTop + viewHeight;
	currentContentTop = Math.min(firstPageY, nextPageY)
	currentContent.style.top = currentContentTop + 'px';
	
	var newThumbTop = thumbPositionForPagePosition(currentContentTop);
		DEBUG("pageUp: contentTop=" + currentContentTop + " newThumbTop=" + newThumbTop);// + " thumbBottom=" + thumbBottom);
	scrollThumb.style.top = newThumbTop;

	if (trackMouseY > newThumbTop) {
		clearInterval(trackTimer);
	}
}

/*
 ********************************
 *	Utility / Math functions	*
 ********************************
 */
 
function getProportion (viewheight, documentheight) {
	if (documentheight <= viewheight)
		return 0;
	else
		return viewheight/documentheight;
}

// Given the position of the thumb, tell us what the content top should be.
// This is the key value that allows us to thumb-scroll.
function pagePositionForThumbPosition (thumbPosition) {
	return -(thumbPosition - SCROLLBAR_TOP) * ((currentContentHeight - viewHeight) / numberOfScrollablePixels);
}

// Given the position of the page, tell us where the thumb should be.
// This is the key value that allows us to track-scroll.
function thumbPositionForPagePosition (pagePosition) {
	return -(pagePosition / ((currentContentHeight - viewHeight) / numberOfScrollablePixels)) + SCROLLBAR_TOP;
}

/*
 ****************************
 *	END SCROLLER FUNCTIONS	*
 ****************************
 */

// debug code uses the div defined in Scroller.html demo

var debugMode = false;

// write to the debug div.
function DEBUG(str) {
	if (debugMode) {
		if (window.widget) {
			alert(str);
		} else {
			document.getElementById('debugDiv').innerHTML += str + "<br>";
		}
	}
}

// toggle the debugMode flag, but only show the debugDiv if we're in Safari
function toggleDebug() {
	debugMode = !debugMode;
	if (debugMode == true && !window.widget) {
		document.getElementById('debugDiv').style.display = 'block';
	} else {
		document.getElementById('debugDiv').style.display = 'none';
	}
}