////////////////////////////////////////////////////////////////////
//  Copyright (c) 2007 Atsushi Nakagawa.
////////////////////////////////////////////////////////////////////
//
//  Hover Comments (hovcomm)
//
//  Description: Text inside the 'title' attribute of HTML elemnts
//  of class 'hovcomm' are displayed in a popup box when the mouse
//  is hovered over or clicked on the element.
//
//  Usage: Call hovcomm_initializeAll() after the page is loaded.
//  This can be done by putting hovcomm_initializeAll() in the
//  onload attribute of <BODY> or by setting window.onload.
//
//  CSS: '.hovcomm .inner' will be used for the appearance of the
//  comments box.  '.hovcomm .show' will be used by the comments
//  box in the shown state.  Normally, 'display: none' will be put
//  in '.hovcomm .inner' and 'display: block' in '.hovcomm .show'.
//
//  Updated: 2005/07/10 (1.3)
//  Updated: 2007/01/11 (1.4): Fixes bug with invalid context calling.
//
////////////////////////////////////////////////////////////////////

// Milliseconds to hover before the comments box opens.
var k_hovcomm_open_delay = 400;
// Milliseconds to wait before the comments box closes when the cursor leaves.
var k_hovcomm_close_delay = 100;
// Rightward offset of the comments box location relative to the cursor.
var k_hovcomm_x_offset = 0;
// Downward offset of the comments box location relative to the cursor.
var k_hovcomm_y_offset = 6;
// Determines if the cursor is allowed to move into the comments box area.
// 'auto' means true if the inner element is pre-made.
var k_hovcomm_allow_selection = true;

// This character is inserted as the pre-dash before every comment.
var k_hovcomm_pre_dash = ' [';
// This character is inserted as the post-dash after every comment.
var k_hovcomm_post_dash = ']';

////////////////////////////////////////////////////////////////////
// Hover Comments functions
////////////////////////////////////////////////////////////////////

function hovcomm_initializeAll()
{
	var spans = document.getElementsByTagName('SPAN');

	// Initialize all hovcomms boxes in the page.
	for (var i = 0; i < spans.length; i++)
		if (spans[i].className == 'hovcomm')
			hovcomm_initialize(spans[i]);
}

function hovcomm_initialize(hovcomm)
{
	var inner = null;

	// Look for an already existing inner.
	for (var i = 0; i < hovcomm.childNodes.length; i++)
		if (hovcomm.childNodes[i].className == 'inner')
			inner = hovcomm.childNodes[i];

	if (inner == null) {
		// There isn't an existing so create one instead.
		inner = document.createElement('SPAN');
		inner.className = 'inner';
		inner.appendChild(document.createTextNode(hovcomm.title));
		hovcomm.appendChild(inner);
		hovcomm._selectable = k_hovcomm_allow_selection == 'auto' ?
			false : k_hovcomm_allow_selection;
	}
	else {
		hovcomm._selectable = k_hovcomm_allow_selection == 'auto' ?
			true : k_hovcomm_allow_selection;
	}

	// Set the inner reference so it can be found later.
	hovcomm._inner = inner;

	// Remove the title attribute so the browser's
	// own tooltip doesn't also show.
	hovcomm.removeAttribute('title');

	hovcomm_insertDashes(hovcomm);

	// Attach the event handlers.
	hovcomm.onmouseover = hovcomm_onmouseover;
	hovcomm.onmousemove = hovcomm_onmousemove;
	hovcomm.onmouseout = hovcomm_onmouseout;
	hovcomm.onmousedown = hovcomm_onmousedown;
}

function hovcomm_insertDashes(hovcomm)
{
	// Create the pre and post dash elements.
	var preDash = document.createElement('SPAN');
	var postDash = document.createElement('SPAN');

	// Set up the pre and post dash elements.
	preDash.appendChild(document.createTextNode(k_hovcomm_pre_dash));
	postDash.appendChild(document.createTextNode(k_hovcomm_post_dash));
	preDash.className = postDash.className = 'dashes';

	// Insert the pre and post dash elemnts.
	hovcomm._inner.appendChild(postDash);
	hovcomm._inner.insertBefore(preDash, hovcomm._inner.firstChild);
}

var g_hovcomm_opened = null;

function hovcomm_open(hovcomm, delayed)
{
	if (g_hovcomm_opened != null) {
		// There's nothing to do if the same hovcomms box is already open.
		if (hovcomm_isOpen(hovcomm))
			return false;
		// Stop the timer of pending opens and close any existing.
		hovcomm_close();
	}

	g_hovcomm_opened = hovcomm;
	if (delayed) {
		// Set a timer for the hovcomms box to be shown.
		g_hovcomm_opened._delayed = true;
		g_hovcomm_opened._timer =
			window.setTimeout('hovcomm_timeoutOpen()', k_hovcomm_open_delay);
	}
	else {
		hovcomm_show(g_hovcomm_opened);
	}
	return true;
}

function hovcomm_isOpen(hovcomm)
{
	// Return whether or not the hovcomm is currently shown.
	return g_hovcomm_opened == hovcomm && !g_hovcomm_opened._delayed;
}

function hovcomm_timeoutOpen()
{
	if (g_hovcomm_opened == null)
		return;
	// Handle the show timer elapse by showing the hovcomms box.
	if (g_hovcomm_opened._delayed) {
		g_hovcomm_opened._delayed = false;
		hovcomm_show(g_hovcomm_opened);
	}
}

function hovcomm_show(hovcomm)
{
	hovcomm._inner.className += ' show';
	// Check and adjust the position if necessary.
	hovcomm_reposition(g_hovcomm_opened);
}

function hovcomm_close()
{
	if (g_hovcomm_opened == null)
		return false;

	// Stop the timer if one is started or hide the box if one is shown.
	if (g_hovcomm_opened._delayed) {
		window.clearTimeout(g_hovcomm_opened._timer);
		g_hovcomm_opened._delayed = false;
		g_hovcomm_opened = null;
	}
	else {
		if (g_hovcomm_opened._closing) {
			window.clearTimeout(g_hovcomm_opened._timer);
			g_hovcomm_opened._closing = false;
		}
		hovcomm_hide(g_hovcomm_opened);
		g_hovcomm_opened = null;
	}
	return true;
}

function hovcomm_timeoutClose()
{
	if (g_hovcomm_opened == null)
		return;
	
	if (g_hovcomm_opened._closing) {
		g_hovcomm_opened._closing = false;
		hovcomm_close();
	}
}

function hovcomm_hide(hovcomm)
{
	hovcomm._inner.className = hovcomm._inner.className.replace(' show', '');
}
function hovcomm_position(hovcomm, event)
{
	// Position the hovcomms box to where it's supposed to go.
	// This is later repositioned to adjust for the edge of the screen.
	hovcomm._inner.style.left = (event.pageX + k_hovcomm_x_offset) + 'px';
	// 6px is about half the size of the cursor
	hovcomm._inner.style.top = (event.pageY + k_hovcomm_y_offset) + 'px';
}

function hovcomm_reposition(hovcomm)
{
	// Reposition the hovcomms box after it's shown so it's off the screen.
	var right = hovcomm._inner.offsetLeft + hovcomm._inner.offsetWidth;
	var bottom = hovcomm._inner.offsetTop + hovcomm._inner.offsetHeight;

	var leftEdge = getScrollLeft();
	var rightEdge = getPageWidth() + leftEdge;
	var topEdge = getScrollTop();
	var bottomEdge = getPageHeight() + topEdge;

	if (right > rightEdge) {
		var left = rightEdge - right + hovcomm._inner.offsetLeft;
		if (left < leftEdge) left = leftEdge;
		hovcomm._inner.style.left = left + 'px';
	}
	if (bottom > bottomEdge) {
		var top = bottomEdge - bottom + hovcomm._inner.offsetTop;
		if (top < topEdge) top = topEdge;
		hovcomm._inner.style.top = top + 'px';
	}
}

function hovcomm_onmouseover(e)
{
	// The process is the same as onmousemove.
	this.onmousemove(e);
}

function hovcomm_onmousemove(e)
{
	var event = getEvent(e);
	var hovcomm = this;

	// Check if the event is in the inner or outer areas.
	var area = getNodeOrParentOfEither(event.target, hovcomm, hovcomm._inner);

	// If the area is not 'this', it's the inner section.
	if (area != hovcomm && !hovcomm._selectable)
		return;

	if (hovcomm_isOpen(hovcomm)) {
		// Cancel any closing count downs.
		if (hovcomm._closing) {
			window.clearTimeout(g_hovcomm_opened._timer);
			g_hovcomm_opened._closing = false;
		}
	}
	// Initiate or restart show hovcomms count down if
	// it's not already shown.
	else if (!hovcomm_isOpen(hovcomm)) {
		hovcomm_position(hovcomm, event);
		hovcomm_open(hovcomm, true);
	}
}

function hovcomm_onmousedown(e)
{
	var event = getEvent(e);
	var hovcomm = this;

	// Check if the event is in the inner or outer areas.
	var area = getNodeOrParentOfEither(event.target, hovcomm, hovcomm._inner);

	// If the area is not 'this', it's the inner section.
	if (area != hovcomm)
		return;

	// Show hovcomms count down if it's not already shown.
	if (hovcomm._inner && !hovcomm_isOpen(hovcomm)) {
		hovcomm_position(hovcomm, event);
		hovcomm_open(hovcomm, false);
	}
}

function hovcomm_onmouseout(e)
{
	var event = getEvent(e);
	var hovcomm = this;

	// Check if the event is in the inner or outer areas.
	var area = getNodeOrParentOfEither(event.target, hovcomm, hovcomm._inner);

	// If the area is not 'this', it's the inner section.
	if (area != hovcomm && !hovcomm._selectable)
		return;

	// If the hovcomm is open, close it after a single yield.  The
	// yield ensures sure any immediate mouse overs from the cursor
	// moving into a sub element are picked up and the closing is
	// cancelled.
	if (hovcomm_isOpen(hovcomm)) {
		hovcomm._closing = true;
		g_hovcomm_opened._timer = window.setTimeout('hovcomm_timeoutClose()', k_hovcomm_close_delay);
	}
	// Stop the open count down.
	else
		hovcomm_close();
}

////////////////////////////////////////////////////////////////////
// Utility functions
////////////////////////////////////////////////////////////////////

function getEvent(e)
{
	// Make NS and IE events into a common object.
	if (window.event != null) {
		var event = new Object();
		event.target = window.event.srcElement;
		event.pageX = window.event.clientX + getScrollLeft();
		event.pageY = window.event.clientY + getScrollTop();
		return event;
	}
	return e;
}

function getScrollTop()
{
	if (document.compatMode == 'BackCompat')
		return document.body.scrollTop;
	return document.documentElement.scrollTop;
}

function getScrollLeft()
{
	if (document.compatMode == 'BackCompat')
		return document.body.scrollLeft;
	return document.documentElement.scrollLeft;
}

function getPageWidth()
{
	// Get the proper page width.
	if (document.compatMode == 'BackCompat')
		return document.body.clientWidth;
	return document.documentElement.clientWidth;
}

function getPageHeight()
{
	// Get the proper page heigth.
	if (document.compatMode == 'BackCompat')
		return document.body.clientHeight;
	return document.documentElement.clientHeight;
}

function getNodeOrParentOfEither(node, match1, match2)
{
	for ( ; node != null; node = node.parentNode)
		if (node == match1 || node == match2)
			break;
	return node;
}

