/**
Script: DOM Tooltip v0.1.3
Author: Dan Allen <dan@mojavelinux.com>
License: LGPL
Supported Browsers: Mozilla, IE 5+, Konqueror
**/

/**
 * Settings
 */
var domTT_offsetX = 0;
var domTT_offsetY = 21;
var domTT_activateDelay = 0;
var domTT_scrollbarWidth = 14;
var domTT_maxWidth = 300;
var domTT_predefined = new Array();

/**
 * Global constants
 */
var domTT_userAgent = navigator.userAgent.toLowerCase();
var domTT_isKonq = domTT_userAgent.indexOf('konq') != -1 ? 1 : 0;
var domTT_isIE = !domTT_isKonq && document.all ? 1 : 0;
var domTT_isGecko = domTT_userAgent.indexOf('gecko') != -1 ? 1 : 0;

var domTT_autoID = 1;
var domTT_zIndex = 100;
var domTT_cssFloat = domTT_isIE ? 'float' : 'cssFloat';
var domTT_eventTarget = domTT_isIE ? 'srcElement' : 'currentTarget';
var domTT_selectElements;
var domTT_activateTimeouts = new Array();
// {{{ class Hash()

function domTT_Hash() {
    var argIndex = 0;
    this.length = 0;
    this.undefined;
    this.items = new Array();
    for (var i = 0; i < arguments.length; i += 2) {
        if (arguments[i + 1] != this.undefined) {
            this.items[arguments[i]] = arguments[i + 1];
            this.length++;
        }
    }

    this.removeItem = function(in_key)
    {
        if (this.items[in_key] != this.undefined) {
            this.length--;
            var tmp_value = this.items[in_key];
            delete this.items[in_key];
            return tmp_value;
        }
    }

    this.setItem = function(in_key, in_value)
    {
        if (in_value != this.undefined) {
            if (this.items[in_key] == this.undefined) {
                this.length++;
            }
            
            return this.items[in_key] = in_value;
        }
    }
    
    this.itemExists = function(in_key)
    {
        return this.items[in_key] != this.undefined;
    }
}

// }}}
// {{{ domTT_activate()

function domTT_activate(e)
{
    // get the unified event object and necessary event variables
    var eventObj = domTT_isIE ? event : e;
    var eventTarget = eventObj[domTT_eventTarget];
    var eventDelay = eventObj.type == 'click' ? 0 : domTT_activateDelay;

    // setup the options hash from the arguments
    // [!] we could do the defaults here, or setup the defaults somewhere else [!]
    var in_options = new domTT_Hash();
    for (var i = 1; i < arguments.length; i += 2) {
        in_options.setItem(arguments[i], arguments[i + 1]);
    }

    // make sure that the sticky option is set right away, since we need this information
    if (!in_options.itemExists('sticky')) {
        in_options.setItem('sticky', false);
    }
    
    var tmp_prefix = 'domTT' + (in_options.items['sticky'] ? ':sticky:' : ':greasy:');
    var tmp_id;
    if (in_options.itemExists('predefined')) {
        var tmp_id = in_options.items['predefined'];
    }
    else {
        var tmp_id = eventTarget.id;
        if (!tmp_id) {
            eventTarget.id = tmp_id = '__auto' + domTT_autoID++;
        }
    }

    // set the id option in the hash which will be used to label our tip
    in_options.setItem('id', tmp_prefix + tmp_id);

    // check for a cached tooltip
    var tipObj = document.getElementById(in_options.items['id']);
    var altTipObj = document.getElementById('domTT' + (in_options.items['sticky'] ? ':greasy:' : ':sticky:') + tmp_id);

    // if this is not a sticky tip event and a sticky tip with the same parent exists and
    // is visible, then don't do anything
    if (!in_options.items['sticky'] && altTipObj && altTipObj.style.visibility == 'visible') {
        return false;
    }

    // get the mouse x and y coordinates
    if (domTT_isKonq && eventObj.type == 'click') {
        var tmp_position = domTT_getPosition(eventTarget);
        var mouse_x = tmp_position.items['right'];
        var mouse_y = tmp_position.items['bottom'];
    }
    else {
        if (domTT_isKonq) {
            var mouse_x = eventObj.x;
            var mouse_y = eventObj.y;
        }
        else if (domTT_isIE) {
            var mouse_x = eventObj.clientX + document.body.scrollLeft
            var mouse_y = eventObj.clientY + document.body.scrollTop
        }
        else {
            var mouse_x = eventObj.pageX;
            var mouse_y = eventObj.pageY;
        }
    }

    // either there is no tooltip for this id, or we are changing our sticky option
    // and we are going to need to recreate the tooltip
    if (!tipObj) {
        for (var i in domTT_activateTimeouts) {
            clearTimeout(domTT_activateTimeouts[i]);
            delete domTT_activateTimeouts[i];
        }

        if (in_options.itemExists('predefined')) {
            var tmp_predefined = in_options.items['predefined'];
            in_options.setItem('caption', domTT_predefined[tmp_predefined][0]);
            in_options.setItem('content', domTT_predefined[tmp_predefined][1]);
        }
        else {
            if (!in_options.itemExists('caption')) {
                in_options.setItem('caption', false);
            }
            if (!in_options.itemExists('content')) {
                in_options.setItem('content', '');
            }
        }

        if (!in_options.itemExists('parent')) {
            in_options.setItem('parent', document.body);
        }
        
        in_options.setItem('target', eventTarget);
        in_options.setItem('type', eventObj.type);

        // we check if tipObj already exists and hide it in the case where we are going
        // from non-stick to sticky (for instance on a click)
        var tmp_function = function() { 
            domTT_deactivate(arguments[0], false); 
            domTT_create(arguments[1], arguments[2], arguments[3]); 
        }

        if (domTT_isKonq) {
            tmp_function(altTipObj, mouse_x, mouse_y, in_options);
        }
        else {
            domTT_activateTimeouts[domTT_activateTimeouts.length] = setTimeout(function() { tmp_function(altTipObj, mouse_x, mouse_y, in_options) }, eventDelay);
        }

        return false;
    }

    // if konqueror does a click, don't use the offset since we are using the right edge
    // of the target element to place the tootip
    if (domTT_isKonq && eventObj.type == 'click') {
        var tip_x = mouse_x;
        var tip_y = mouse_y;
    }
    // offset out tip by the specified amount
    else {
        var tip_x = mouse_x + domTT_offsetX;
        var tip_y = mouse_y + domTT_offsetY;
    }

    // correct for being off the edge of the page to the right
    if (domTT_isIE || domTT_isGecko) {
        var bleed_x;
        var rightMargin = parseInt(domTT_isIE ? document.body.currentStyle.marginRight : document.defaultView.getComputedStyle(document.body, '').getPropertyValue('margin-right'));
        if ((bleed_x = tip_x + tipObj.offsetWidth - (document.body.clientWidth - rightMargin)) > 0) {
            tip_x -= bleed_x;
        }
    }

    // move the tip to the location offset from the mouse cursor
    tipObj.style.left = tip_x + 'px';
    tipObj.style.top = tip_y + 'px';

    // update the collision detection
    var tipCenter_x = tip_x + tipObj.offsetWidth/2;
    var tipCenter_y = tip_y + tipObj.offsetHeight/2;
    var tipRadius = Math.max(tipObj.offsetWidth, tipObj.offsetHeight)/2;

    tipObj.style.zIndex = domTT_zIndex++;
    // re-register the onmouseout event handler
    if (in_options.items['sticky']) {
        eventTarget.onmouseout = function() { return false; };
    }
    else {
        eventTarget.onmouseout = function () { domTT_deactivate(tipObj, true); };
    }

    var tmp_function = function() {
        domTT_deactivate(arguments[0], false);
        arguments[1].style.visibility = 'visible';
        domTT_detectCollisions(arguments[1], arguments[2], arguments[3], arguments[4]);
    }

    if (domTT_isKonq) {
        tmp_function(altTipObj, tipObj, tipCenter_x, tipCenter_y, tipRadius);
    }
    else {
        domTT_activateTimeouts[domTT_activateTimeouts.length] = setTimeout(function() { tmp_function(altTipObj, tipObj, tipCenter_x, tipCenter_y, tipRadius) }, eventDelay);
    }
}

// }}}
// {{{ domTT_create()

function domTT_create(in_x, in_y, in_options)
{
    var tipObj = document.createElement('div');
    tipObj.className = 'domTT';
    if (in_options.items['caption'] !== false || in_options.items['sticky']) {
        var tmp_captionBox = document.createElement('div');
        tmp_captionBox.className = 'domTTCaption';
        var tmp_caption = document.createElement('span');
        if (in_options.items['sticky']) {
            tmp_caption.style[domTT_cssFloat] = 'left';
        }

        tmp_caption.appendChild(document.createTextNode(in_options.items['caption']));
        tmp_captionBox.appendChild(tmp_caption);
        if (in_options.items['sticky']) {
            tmp_close = document.createElement('span');
            if (!domTT_isIE) {
                tmp_close.style[domTT_cssFloat] = 'right';
            }
            tmp_close.style.cursor = 'pointer';
            tmp_close.appendChild(document.createTextNode('X'));
            tmp_close = tmp_captionBox.appendChild(tmp_close);
            tmp_close.onclick = function() { domTT_deactivate(tipObj, true); };
            tmp_break = document.createElement('br');
            tmp_break.style.clear = 'both';
            tmp_captionBox.appendChild(tmp_break);
        }
        tipObj.appendChild(tmp_captionBox);
    }

    var tmp_content = document.createElement('div');
    // [!] would like to use document fragment here [!]
    if (typeof(in_options.items['content']) == 'object') {
        tmp_content.appendChild(in_options.items['content']);
    }
    else {
        tmp_content.innerHTML = in_options.items['content'];
    }

    tmp_content.className = 'domTTContent';
    tipObj.appendChild(tmp_content);
    if (domTT_isKonq && in_options.items['type'] == 'click') {
        var tip_x = in_x;
        var tip_y = in_y;
    }
    else {
        var tip_x = in_x + domTT_offsetX;
        var tip_y = in_y + domTT_offsetY;
    }

    tipObjStyle = tipObj.style;
    tipObjStyle.position = 'absolute';
    tipObjStyle.visibility = 'visible';
    // we have to do a little trickster here so it doesn't crunch up at the edge
    // by putting the tooltip off screen and rendering it with max available space
    tipObjStyle.left = 0;
    // [!] should use window height instead of -1000 [!]
    tipObjStyle.top = '-1000px';
    tipObjStyle.zIndex = domTT_zIndex++;
    tipObj.id = in_options.items['id'];
    tipObj = in_options.items['parent'].appendChild(tipObj);

    // {{{ IE/Konq bug workarounds

    // [!] fix lack of maxWidth in CSS [!]
    if (domTT_isIE || domTT_isKonq) {
        if (tipObj.offsetWidth > domTT_maxWidth) {
            tipObj.style.width = domTT_maxWidth + 'px';
        }
    }

    // [!] nasty hacks for IE to get the float and max-width working right [!]
    if (domTT_isIE) {
        var tmp_oldWidth = tipObj.offsetWidth;
        if (in_options.items['sticky']) {
            tipObj.firstChild.firstChild.nextSibling.style[domTT_cssFloat] = 'right';
        }

        tipObj.innerHTML = tipObj.innerHTML;
        tipObj.style.width = tmp_oldWidth + 'px';
        if (in_options.items['sticky']) {
            tipObj.firstChild.firstChild.nextSibling.onclick = function() { domTT_deactivate(tipObj, true); };
        }
    }

    // }}}

    // correct for being off the edge of the page to the right
    if (domTT_isIE || domTT_isGecko) {
        var bleed_x;
        var rightMargin = parseInt(domTT_isIE ? document.body.currentStyle.marginRight : document.defaultView.getComputedStyle(document.body, '').getPropertyValue('margin-right'));
        if ((bleed_x = tip_x + tipObj.offsetWidth - (document.body.clientWidth - rightMargin)) > 0) {
            tip_x -= bleed_x;
        }
    }

    tipObj.style.width = tipObj.offsetWidth + 'px';
    tipObj.style.top = tip_y + 'px';
    tipObj.style.left = tip_x + 'px';

    // clear any previous onmouseout for this object
    if (in_options.items['sticky']) {
        in_options.items['target'].onmouseout = function() { return false; };
    }
    // if this is not sticky, mouse over the tip and mouseout of the target cause destruction
    else {
        in_options.items['target'].onmouseout = tipObj.onmouseover = function() { domTT_deactivate(tipObj, true); };   
    }

    var tipCenter_x = tip_x + tipObj.offsetWidth/2;
    var tipCenter_y = tip_y + tipObj.offsetHeight/2;
    var tipRadius = Math.max(tipObj.offsetWidth, tipObj.offsetHeight)/2;
    
    domTT_detectCollisions(tipObj, tipCenter_x, tipCenter_y, tipRadius);
}

// }}}
// {{{ domTT_deactivate()

function domTT_deactivate(in_tipObj, in_recoverSelects)
{
    // clear any scheduled tooltip activations
    for (var i in domTT_activateTimeouts) {
        clearTimeout(domTT_activateTimeouts[i]);
        delete domTT_activateTimeouts[i];
    }

    if (in_tipObj) {
        in_tipObj.style.visibility = 'hidden';
        
        // unhide all of the selects
        if (in_recoverSelects) {
            domTT_selectElements = document.getElementsByTagName('select');
            for (var cnt = 0; cnt < domTT_selectElements.length; cnt++) {
                if (!domTT_isIE && domTT_selectElements[cnt].size <= 1) {
                    continue;
                }

                domTT_selectElements[cnt].style.visibility = 'visible';
            }
        }
    }
}

// }}}
// {{{ domTT_detectCollisions()

function domTT_detectCollisions(in_tipObj, in_tipCenter_x, in_tipCenter_y, in_tipRadius)
{
    if (typeof(domTT_selectElements) == 'undefined') {
        domTT_selectElements = document.getElementsByTagName('select');
    }

    var thisSelect;
    var selectCoordinates;
    var center2centerDistance;
    var selectCenter_x;
    var selectCenter_y;
    var selectRadius;
    var selectWidth;
    var radiusSum;
    for (var cnt = 0; cnt < domTT_selectElements.length; cnt++) {
        thisSelect = domTT_selectElements[cnt];
        // mozilla doesn't have a problem with regular selects
        if (!domTT_isIE && thisSelect.size <= 1) {
            continue;
        }

        selectCoordinates = domTT_getPosition(thisSelect); 
        // for mozilla we only have to worry about the scrollbar itself
        if (!domTT_isIE) {
            selectCoordinates.items['left'] += thisSelect.offsetWidth - domTT_scrollbarWidth;
            selectWidth = domTT_scrollbarWidth;
        }
        else {
            selectWidth = thisSelect.offsetWidth;
        }

        selectCenter_x = selectCoordinates.items['left'] + selectWidth/2;
        selectCenter_y = selectCoordinates.items['top'] + thisSelect.offsetHeight/2; 
        selectRadius = Math.max(selectWidth/2, thisSelect.offsetHeight); 
        center2centerDistance = Math.sqrt(Math.pow(selectCenter_x - in_tipCenter_x, 2) + Math.pow(selectCenter_y - in_tipCenter_y, 2));
        radiusSum = selectRadius + in_tipRadius;
        if (center2centerDistance < radiusSum) {
            if (selectCenter_x >= in_tipCenter_x && selectCoordinates.items['left'] > (in_tipObj.offsetLeft + in_tipObj.offsetWidth)) {
                thisSelect.style.visibility = 'visible';
            }
            else if (selectCenter_x < in_tipCenter_x && selectCoordinates.items['right'] < in_tipObj.offsetLeft) {
                thisSelect.style.visibility = 'visible';
            }
            else if (selectCenter_y >= in_tipCenter_y && selectCoordinates.items['top'] > (in_tipObj.offsetTop + in_tipObj.offsetHeight)) {
                thisSelect.style.visibility = 'visible';
            }
            else if (selectCenter_y < in_tipCenter_y && selectCoordinates.items['bottom'] < in_tipObj.offsetTop) {
                thisSelect.style.visibility = 'visible';
            }
            else {
                thisSelect.style.visibility = 'hidden';
            }
        }
    }
}

// }}}
// {{{ domTT_getPosition()

function domTT_getPosition(in_object) {
    var originalObject = in_object;
    var offsetLeft = 0;
    var offsetTop = 0;

    while (in_object) {
        offsetLeft += in_object.offsetLeft;
        offsetTop += in_object.offsetTop;
        in_object = in_object.offsetParent;
    }
    
    var offsetRight = offsetLeft + originalObject.offsetWidth;
    var offsetBottom = offsetTop + originalObject.offsetHeight;
    return new domTT_Hash('left', offsetLeft, 'top', offsetTop, 'right', offsetRight, 'bottom', offsetBottom);
}

// }}}

