/*
      Bubblehelp infoboxes, (C) 2007 Klaus Knopper <infobox@knopper.net>
              (rebuilt and enhanced by Bret Alan Hughes in 2008)

    You can copy/modify and distribute this code under the conditions of
    Version 2 of http://en.wikipedia.org/wiki/GNU_General_Public_License

    USAGE: First, copy this JavaScript file to where you want to use it. Next,
    you must include this JavaScript in your HTML file, so put something like
    the following line of code in your HTML header.

        <script type="text/javascript" src="infobox.js"></script>

    You must then add some JavaScript to write the HTML that will define what
    will be in the infobox as well as an identifier for that content.

        <script type="text/javascript">
            write_ib("my_id1", "Here is my title!", "And here's the info!");
            write_ib("my_id2", "And this is so...", "Cool!");
        </script>

    Finally, when you want an infobox to be display over something, use
    something like the following code; it also works for other HTML elements.

        <span onmouseover="up_ib('my_id1')" onmouseout="un_ib()">Yah!!!</span>
        <span onmouseover="up_ib('my_id2')" onmouseout="un_ib()"
            onclick="stick_ib('my_id2')">cool</span>

    Notes:
      o HTML codes are allowed in the infobox.
      o Only one infobox is allowed up at a time.
*/

// Custom settings, with a mess of global variables.
//
var text_color_ib = "#000000";    // Color of the text
var title_color_ib = "#FFFFFF";   // Color of the title text
var border_color_ib = "#0000FF";  // Color of the border of the infobox
var text_bg_color_ib = "#C3D9FF"; // Color of the text background
var title_align_ib = "left";      // Alignment of the title
var border_width_ib = 3;          // Border width of the infobox
var text_padding_ib = 6;          // Pixel width of padding around the text
var height_ib = 90;               // Default height of infobox, mostly ignored
var width_ib = 350;               // Minimum width of the infobox

// The rest is the internals of the code: usually you should not change.

// Define some magic numbers, including the offset of the infobox from the
// mouse, and the default window size assumed if we can't determine it.
//
var y_mouse_separation_ib = 5;
var x_mouse_separation_ib = 12;
var default_win_width_ib = 800;
var default_win_height_ib = 600;

// Create additional globals to identify the top-left position of the mouse,
// whether the infobox has been initialized in general, the width and height
// of the browser window, and a suffix for pixel specifications (required in
// some cases).
//
var x_ib, y_ib;
var initialized_ib;
var win_width_ib, win_height_ib;
var px_ib = "px";

// Make an identifier when the current infobox is stuck (not temporarily
// displayed). Use global variables to hold the node of the currently
// displayed infobox as well as its style. Also make a variable to indicate a
// mouse down click in the body of the infobox.
//
var stuck_ib;
var node_object_ib;
var style_object_ib;
var in_body_mouse_down_ib;

// More global variables to identify when the infobox is being dragged and the
// mouse cursor position of where the infobox was dragged from.
//
var dragon_ib;
var x_dragon_offset_ib;
var y_dragon_offset_ib;

// Identify which browser type we are using, initialize our global variables,
// and track mouse movements with our mouse move function.
//
function initialize_ib()
    {

    // Initialize our global variables, including some default specifications
    // for the window width and height, and indicate we do not have a style
    // object yet for the infobox.
    //
    x_ib = y_ib = 0;
    win_width_ib = default_win_width_ib;
    win_height_ib = default_win_height_ib;

    // The mouse up is best, to kill any drag'n functionality, on the browser
    // window. IE does not handle that, so make a work around for IE.
    //
    var mouse_up_target_type = "window";
    /*@cc_on
        mouse_up_target_type = "document";
    @*/

    // When the mouse moves in the HTML document, call our mouse move
    // function. On a window mouse up event, kill any drag'n functionality.
    // Finally, identify that the infobox has been initialized.
    //
    add_event_function("document", "onmousemove", move_mouse_ib);
    add_event_function(mouse_up_target_type, "onmouseup", kill_dragon_ib);
    initialized_ib = true;
    }

// This function just writes the HTML of the infobox definition by passing
// through our arguments to the infobox html creation function and writing the
// results to the document.
//
function write_ib()
    {document.write(html_ib.apply(this, arguments));}

// This function returns the HTML that defines the infobox divider for the
// passed-in identifier, title, and text, as well as our customized settings.
// This divider is hidden initially. An optional pixel width of the infobox
// may also be passed in.
//
function html_ib(identifier, title, text, width)
    {

    // If the user did not specify an infobox width, use our default minimum.
    //
    if ( ! width)
        {width = width_ib;}

    // Define the infobox divider, initially with the display off, and its
    // contents using the identifier, title, and text passed in, and our
    // customized settings. On mouse down on the infobox, but not the text
    // body, start dragging the infobox. (The mouse up should be on the entire
    // window, to prevent hanging the infobox drag functionality.) There is an
    // "x" in the upper right hand corner when the infobox is stuck (displayed
    // permanently) to close the infobox.
    //
    return '<div onmousedown="return new_dragon_ib(event);" id="' + identifier
        + '" name="' + identifier + '" style="cursor: move; display: none;'
        + ' position: absolute; visibility: hidden; z-index: 100; top: 0'
        + px_ib + '; left: 0' + px_ib + ';">'
        + '<table width="' + width + '" border="0" cellpadding="'
        + border_width_ib + '" cellspacing="0" bgcolor="' + border_color_ib
        + '"><tr><td>'
        + '<table width="100%" border="0" cellpadding="0" cellspacing="0">'
        + '<tr><th class="infobox" align="' + title_align_ib + '">'
        + '<span id="' + identifier + '_title_span_ib"'
        + ' style="position: relative; font-weight: bold; color: '
        + title_color_ib + '; bottom: ' + Math.floor(border_width_ib / 2)
        + 'px">' + title + '</span>'
        + '<span onclick="un_stick_ib(this)" id="' + identifier + '_x_ib"'
        + ' style="position: absolute; color: ' + title_color_ib 
        + '; right: 3px; top: -4px; display: none; cursor: pointer;">x</span>'
        + '</th></tr></table>'
        + '<table style="cursor: default" onmousedown="body_mouse_down_ib()"'
        + ' width="100%" border="0" cellpadding="' + text_padding_ib + '"'
        + ' cellspacing="0" bgcolor="' + text_bg_color_ib + '">'
        + '<tr><td class="infobox"><font id="' + identifier + '_font_ib"'
        + ' class="infobox" color="' + text_color_ib + '">' + text + '</font>'
        + '</td></tr></table></td></tr></table></div>' + "\n";
    }

// If the mouse down event is on the body of the infobox, make a note of it.
//
function body_mouse_down_ib()
    {in_body_mouse_down_ib = true;}

// Pass in the infobox identifier, and this function creates it and shows it,
// after setting some basic globals on browser window size and that of the
// infobox to be created. The mouse event argument is optional, and could
// potentially be used for positioning of the infobox.
//
function up_ib(identifier, mouse_event)
    {

    // Make sure we have been initialized. If the infobox is stuck up, then we
    // have nothing more to do.
    //
    if ( ! initialized_ib)
        {initialize_ib();}
    if (stuck_ib)
        {return;}

    // If an infobox style object already exists, destroy it, because we are
    // to create a new one.
    //
    verify_object_pointers();
    if (style_object_ib)
        {un_ib();}

    // Get the infobox node and its style using the passed-in identifier, or
    // eject.
    //
    var node_object = document.getElementById(identifier);
    if (node_object)
        {style_object_ib = node_object.style;}
    else
        {return;}

    // Make sure the infobox is displayed. It is initially turned off due to a
    // bug with displaying borders in Internet Explorer when the infobox comes
    // into existence as only invisible. We need the display on or else stange
    // positioning things happen.
    //
    if (style_object_ib.display == "none")
        {style_object_ib.display = "inline";}

    // Define "IE standards compliant mode compatibility" (partly contributed
    // by Stefan Neufeind). Strangely enough, clear out for IE.
    //
    var w_adj = 16;
    var h_adj = 20;
    /*@cc_on
        w_adj = 0;
        h_adj = 0;
    @*/

    // Determine the browser window useable width.
    //
    if (window.innerWidth)
        {win_width_ib = window.innerWidth + window.pageXOffset - w_adj;}
    else if (document.documentElement && document.documentElement.clientWidth)
        {
        win_width_ib = document.documentElement.clientWidth 
            + document.documentElement.scrollLeft - w_adj;
        }
    else
        {
        win_width_ib = document.body.offsetWidth + document.body.scrollLeft
            - w_adj;
        }

    // Determine the browser window useable height.
    //
    if (window.innerHeight)
        {win_height_ib = window.innerHeight + window.pageYOffset - h_adj;}
    else if (document.documentElement
        && document.documentElement.clientHeight)
        {
        win_height_ib = document.documentElement.clientHeight
            + document.documentElement.scrollTop - h_adj;
        }
    else
        {
        win_height_ib = document.body.offsetHeight + document.body.scrollTop
            - h_adj;
        }

    // Set our global node object variable, and if we can get the exact width
    // and height of the infobox, use them.
    //
    node_object_ib = node_object;
    if (node_object_ib.offsetWidth && node_object_ib.offsetHeight)
        {
        width_ib = node_object_ib.offsetWidth;
        height_ib = node_object_ib.offsetHeight;
        }

    // Identify the current mouse position, and bring up the infobox with
    // respect to it.
    //
    identify_mouse_position(mouse_event);
    display_ib();
    }

// This function could be called to stick an infobox that is currently up
// without any arguments, or with an identifier of the infobox to bring that
// one up. However if any infobox is currently being dragged or is stuck up,
// then this function will just close that infobox. An optional mouse event
// can be passed in to stick the infobox at the mouse cursor position. The
// recurse argument is only for internal use.
//
function stick_ib(identifier, mouse_event, recurse)
    {

    // If we are already dragging a stuck infobox, just unstick (and close)
    // that infobox because something is awry. Otherwise, if we have a infobox
    // up that we can stick, do so by identifying it as such (which will be
    // paid attention to when the mouse moves) and by displaying the little
    // close "x" in the title. Otherwise, if we know what infobox is trying to
    // be "stuck," apparently is not up yet, bring it up and stick it by
    // calling ourselves. Of course, make sure we don't go recursive loopy.
    //
    verify_object_pointers();
    if ((dragon_ib || stuck_ib) && node_object_ib)
        {
        var id = node_object_ib.id + "_x_ib";
        un_stick_ib(document.getElementById(id));
        }
    else if (style_object_ib && node_object_ib)
        {

        // Identify that the infobox is stuck up, and get the x span node.
        //
        stuck_ib = true;
        var id = node_object_ib.id + "_x_ib";
        var x_span_node = document.getElementById(id);

        // Set the x span node's display on, if we found it.
        //
        if (x_span_node)
            {x_span_node.style.display = "inline";}
        }
    else if (identifier && ! recurse)
        {
        up_ib(identifier, mouse_event);
        stick_ib(identifier, mouse_event, true)
        }
    }

// Call this functin with the span node of the infobox that contains the
// little "x" in the corner that's used to close a stuck (up) infobox. This
// function will return the display of that "x" back on but will close the
// infobox and mark it as not stuck anymore.
//
function un_stick_ib(span_node)
    {

    // Turn off the display of our "x" span.
    //
    if (span_node)
        {span_node.style.display = "none";}

    // Identify the infobox as not stuck, and then take it down.
    //
    stuck_ib = null;
    un_ib();
    }

// Call this function when the currenlty (stuck) up infobox begins being
// dragged via the left mouse button. However, if the infobox is not already
// stuck, this fuction identifies the mouse position and offset the infobox is
// to be dragged from. Selection of text is forcibly turned off.
//
function new_dragon_ib(mouse_event)
    {

    // Define the left mouse button number, which IE needs some special code
    // for, as well as for getting the mouse event.
    //
    var left_mouse_button = 0;
    /*@cc_on
        left_mouse_button = 1;
        if ( ! mouse_event)
            {mouse_event = window.event;}
    @*/

    // If the mouse event is not the left button, just ignore it.
    //
    if ( ! mouse_event || mouse_event.button != left_mouse_button)
        {return;}

    // Make sure we have a valid infobox, in order to continue.
    //
    verify_object_pointers();
    if ( ! (style_object_ib || node_object_ib))
        {return;}

    // If the mouse down was in the body, ignore it and return true to allow
    // the mouse down event to go through as normal.
    //
    if (in_body_mouse_down_ib)
        {
        in_body_mouse_down_ib = false;
        return true;
        }

    // If the infobox is stuck up, identify that we should be dragging it.
    //
    if (stuck_ib)
        {dragon_ib = true;}

    // Grab the left (x) and top (y) position offset of the infobox from the
    // mouse position as the drag began, ignoring any pixel specification.
    //
    identify_mouse_position(mouse_event);
    x_dragon_offset_ib = Number(style_object_ib.left.match("[0-9]+")) - x_ib;
    y_dragon_offset_ib = Number(style_object_ib.top.match("[0-9]+")) - y_ib;
    
    // IE needs some special code, which technically should not work, to
    // prevent text selection during the drag.
    //
    /*@cc_on
        document.body.focus();
        document.onselectstart = function() {return false;};
        node_object_ib.ondragstart = function() {return false;};
    @*/
    
    // Return false to prevent text selection.
    //
    return false;
    }

// Call this function to stop dragging an infobox, when a mouse up event is
// registred in the main window. This function just identifies the infobox as
// not being dragged anymore, by clearing all dragging-related variables.
//
function kill_dragon_ib()
    {

    // Clear out our dragging related variables.
    //
    dragon_ib = null;
    in_body_mouse_down_ib = null;
    x_dragon_offset_ib = y_dragon_offset_ib = null;

    // Undo the special code we needed for IE, which technically should not
    // work, to prevent text selection during the drag.
    //
    /*@cc_on
        document.onselectstart = null;
        verify_object_pointers();
        if (node_object_ib)
            {node_object_ib.ondragstart = null;}
    @*/
    }

// This function should be called when the mouse moves, to record the position
// of the mouse cursor and to update the infobox, which will only happen if
// one is up.
//
function move_mouse_ib(mouse_event)
    {

    // If we have no infobox, we have nothing to do.
    //
    if ( ! style_object_ib)
        {return;}

    // Update the mouse position and display the infobox.
    //
    identify_mouse_position(mouse_event);
    display_ib();
    }

// This function uses the passed in mouse event, or any mouse event it can
// find, to update the internal variables used to identify the mouse position.
//
function identify_mouse_position(mouse_event)
    {

    // If we can't find the mouse event, from it being passed in or from IE's
    // particular way, just eject.
    //
    if ( ! mouse_event && window.event)
        {mouse_event = window.event;}
    else if ( ! mouse_event)
        {return;}

    // Set the mouse cursor position relative to the visible HTML document.
    //
    x_ib = mouse_event.clientX;
    y_ib = mouse_event.clientY;

    // Now take into account page scrolling. If the standard way does not
    // work, then we are probably dealing with IE. IE may use the body or the
    // "document element" scroll fields, but defines both. Therefore, we need
    // to add whatever is set--scary.
    //
    if (window.pageXOffset != undefined)
        {
        x_ib += window.pageXOffset;
        y_ib += window.pageYOffset;
        }
    else if (document.body
        && (document.body.scrollLeft || document.body.scrollTop))
        {
        x_ib += document.body.scrollLeft;
        y_ib += document.body.scrollTop;
        }
    else if (document.documentElement
        && document.documentElement.scrollLeft != undefined)
        {
        x_ib += document.documentElement.scrollLeft;
        y_ib += document.documentElement.scrollTop;
        }
    }

// Set the position of the infobox to the lower right of the mouse cursor, if
// there is room, or the upper right, and display the infobox too. This can be
// called to position the infobox when it is being dragged too.
//
function display_ib()
    {

    // If we don't have an infobox to display, just eject.
    //
    verify_object_pointers();
    if ( ! style_object_ib)
        {return;}

    // If we are dragging the inbofox, move it according to where the mouse
    // has moved relative to where it started the drag from. Otherwise, if the
    // infobox is not stuck up in some position, have the infobox follow the
    // mouse cursor.
    //
    if (dragon_ib)
        {

        // Set the top and left edges of the infobox to match the current
        // mouse position, but don't go beyond the negative edges of the
        // window.
        //
        style_object_ib.top = Math.max(0, y_ib + y_dragon_offset_ib) + px_ib;
        style_object_ib.left = Math.max(0, x_ib + x_dragon_offset_ib) + px_ib;
        }
    else if ( ! stuck_ib)
        {

        // Define the minimum and maximum x position that we might put the
        // infobox. We may set the infobox just to the right or the left of
        // the mouse cursor.
        //
        var x_min = x_ib - width_ib - x_mouse_separation_ib;
        var x_max = x_ib + x_mouse_separation_ib;

        // Set the left edge of the infobox, by default, at the x maximum if
        // there is room to the right or if there is no room to the left.
        // Otherwise, put the infobox to at the x minumum.
        //
        if (x_min < 0 || x_max + width_ib < win_width_ib)
            {style_object_ib.left = x_max + px_ib;}
        else
            {style_object_ib.left = x_min + px_ib;}

        // Define the minimum and maximum y position that we might put the
        // infobox. We may set the infobox just above or below the mouse
        // cursor.
        //
        var y_min = y_ib - height_ib - y_mouse_separation_ib;
        var y_max = y_ib + y_mouse_separation_ib;

        // Set the top edge of the infobox, by default, at the y maximum if
        // there is room below or if there is no room above. Otherwise, put
        // the infobox at the y minimum.
        //
        if (y_min < 0 || y_max + height_ib < win_height_ib)
            {style_object_ib.top = y_max + px_ib;}
        else
            {style_object_ib.top = y_min + px_ib;}

        // Make the infobox visible.
        //
        style_object_ib.visibility = "visible";
        }
    }

// Hide the infobox, and destroy its style node to indicate that the infobox
// is unset. Also, kill any left over dragging settings, of course.
//
function un_ib()
    {

    // Ignore the un (hide) call if the infobox is stuck up, because it must
    // be unstuck first. This un call is kind of like a soft or suggested
    // close.
    //
    if (stuck_ib)
        {return;}

    // Hide the infobox if it exists, using the setting appropriate for the
    // browser type.
    //
    verify_object_pointers();
    if (style_object_ib)
        {style_object_ib.visibility = "hidden";}

    // Clear the style and node variables, to indicate that the infobox is
    // closed. Make sure any dragging settings are also cleared.
    //
    style_object_ib = node_object_ib = null;
    kill_dragon_ib();
    }

// This function just sets any object pointers that we use to null if what
// they point to no longer exists.
//
function verify_object_pointers()
    {

    // If a pointer points to an undefined object, kill the pointer.
    //
    if (style_object_ib && typeof(style_object_ib) == "undefined")
        {style_object_ib = null;}
    if (node_object_ib && typeof(node_object_ib) == "undefined")
        {node_object_ib = null;}
    }

// Pass in a node, such as "window" or "document", a trigger event, such as
// "onload" or "onunload", and an associated function to execute on that
// window event, and it will be executed, with any arguments passed through.
// Any previous event is executed before the passed-in one: events can be
// stacked.
//
function add_event_function(node, trigger_event, new_function)
    {
    var old_function = eval(node + "." + trigger_event);
    eval(node + "." + trigger_event + " = function()\n"
        + "    {\n"
        + "    if (typeof(old_function) == \"function\")\n"
        + "        {old_function.apply(this, arguments);}\n"
        + "    if (typeof(new_function) == \"function\")\n"
        + "        {return new_function.apply(this, arguments);}\n"
        + "    }\n");
    }
