
/* --- util.js --- */
/**
 *
 * $Id: util.js 41653 2007-08-17 17:31:07Z aaron $
 * $Author: aaron $
 * $Revision: 41653 $
 * $Name$
 * $Date: 2007-08-17 10:31:07 -0700 (Fri, 17 Aug 2007) $
 *
 * @package     PriceGrabber
 * @category    JavaScript
 *
 * @author      Philip Snyder <philip@pricegrabber.com>
 * @copyright   Copyright &copy; 2006 2007, Philip Snyder, PriceGrabber.com
 * @version     $Revision: 41653 $
 *
 * @todo        Finish documentation.
 */

/**
 * Checks the object to see if it implements a certain function.
 *
 * @access public
 * @since  v1.1
 * @param  string   funcName   Name of function to check for
 * @return boolean
 */
/*
InterfaceObject.prototype.implementsFunc = function(funcName) {
    return this[funcName] && this[funcName] instanceof Function;
}
*/


/**
 * Generates an xml string representation of the object.
 *
 * @access public
 * @since  v1.1
 * @param  string   tagname   Tag name to use for the object
 * @return string
 */
/*
Object.prototype.simpleXmlify = function(tagname) {
    var xml = "<"+tagname;
    for (var prop in this) {
        if (!(this[prop] instanceof Function)) {
            xml += " "+prop+"=\""+this[prop]+"\"";
        }
    }
    xml += "/>";
    return xml;
}
*/


/**
 * Finds the index of a value in the array or returns false if not found.
 *
 * @acccess public
 * @since   v1.1
 * @param   mixed val     Value to search array for
 * @return  int | false
 */
Array.prototype.inArray = function(val) {
    for (var i=0; i<this.length; i++) {
        if (this[i] == val) return i;
    }
    return false;
}
// Remove object helper methods from array
Array.prototype.implementsFunc = null;
Array.prototype.simpleXmlify   = null;
// Remove object helper methods from Error
Error.prototype.implementsFunc = null;
Error.prototype.simpleXmlify   = null;






/**
 * Generalize namespace for utility functions
 *
 * @access public
 * @since  v1.1
 */
var util = new Object;






/**
 * Manages image cacheing
 *
 * @todo   Confirm this image cacheing scheme works in IE & FF via fiddler
 *
 * @access public
 * @since  v1.1
 */
util.resourceManager = {

    cache: new Array,

    index: new Array,

    /**
     * Handles cacheing and returns the image src of a url.
     * 
     * @access public
     * @since  v1.1
     * @param  string   url
     * @return Image.src
     */ 
    get: function(url) {
        var ptr = util.resourceManager.index.inArray(url);
        if (ptr === false) {
            ptr                                 = util.resourceManager.index.length;
            util.resourceManager.index[ptr]     = url;
            util.resourceManager.cache[ptr]     = new Image;
            util.resourceManager.cache[ptr].src = url;
        }
        return util.resourceManager.cache[ptr].src;
    }
    
};



/**
 * This function returns the name of a given function.
 *
 * It does this by converting the function to a string,
 * then using a regular expression to extract the
 * function name from the resulting code.
 *
 * @access public
 * @since  v1.1
 * @param  Function  f
 * @return string
 */
function funcname(f) {
    var matches = f.toString().match(/function (\w*)/);
    if ((matches == null) || (matches.length == 0)) return "anonymous";
    if (matches.length == 2) return matches[1];
    else return matches.join(',');
}

/**
 * This function returns a string that contains a "stack trace".
 *
 * @access public
 * @since  v1.1
 * @return string
 */
function stacktrace() {
    var s = "";  // This is the string we'll return.
    // Loop through the stack of functions, using the caller property of
    // one arguments object to refer to the next arguments object on the
    // stack.
    for (var a=arguments.caller; a!=null; a=a.caller) {
        // Add the name of the current function to the return value.
        s += funcname(a.callee) + "\n";
        // Because of a bug in Navigator 4.0, we need this line to break.
        // a.caller will equal a rather than null when we reach the end 
        // of the stack. The following line works around this.
        if (a.caller == a) break;
    }
    return s;
}















/***** BEGIN MESSAGE QUEUE *******/




util.MessageQueue = function(type) {
    this.queued = new Array;
    this.containerId = 'message';
    this.type = type || util.MessageQueue.types.ALL;
    this.queueId = util.MessageQueue.queues.length;
    this.waitingForResponse = false;
    util.MessageQueue.queues[this.queueId] = this;
    return this;
};

util.MessageQueue.settings = {
    timer:     null,
    interval:  5, // in seconds
    immediate: true
};

util.MessageQueue.types  = { NONE:0, ERROR:1, DEBUG:2, USER:4, ALL:7 };
util.MessageQueue.queues = [];


util.MessageQueue.attach = function(queue) {
    if (!util.MessageQueue.queues.inArray(queue)) {
        util.MessageQueue.queues.push(queue);
    }
    return true;
}

util.MessageQueue.detach = function(queue) {
    var retain = [];
    var found  = false;
    while (util.MessageQueue.queues.length) {
        var queuePop = util.MessageQueue.queues.pop();
        if (queue !== queuePop) retain.push(queuePop);
        else                    found = queuePop;
    }
    util.MessageQueue.queues = retain;
    return found;
}


util.MessageQueue.prototype.add = function(obj, test) {
    //alert('util.MessageQueue.add called: '+obj.title+' -> '+obj.content);
    test = test === false ? false : true;
    test = true;
    if ((test && this.isMessage(obj)) || !test) {
        //alert(obj);
        this.queued.push(obj);
        //alert(this.queued.toString());
    }
    if (util.MessageQueue.settings.immediate) this.renderNext();
}

util.MessageQueue.prototype.isMessage = function(obj) {
    return obj.implementsProp('title') && obj.implementsProp('content') && obj.implementsProp('type');
} 

util.MessageQueue.prototype.render = function(type) {
    if (this.queued.length > 0) {
        var body = document.getElementsByTagName('body')[0];
        if (body) {
            var types = util.MessageQueue.types;
            type = type <= types.ALL ? type : types.ALL;
            var messages = new Array;
            if (type == types.ALL) {
                messages = this.queued;
                this.queued = new Array;
            } else {
                var retain = new Array;
                for (var i=0; i<this.queued.length; i++) {
                    var msg = this.queued[i];
                    if (msg.type == type) {
                        messages.push(msg);
                    } else {
                        retain.push(msg);
                    }
                }
                this.queued = retain;
            }
            if (!this.container) {
                this.container = document.createElement('div');
                this.container.className = 'messageContainer';
                body.appendChild(this.container);
            }
            for (var i=0; i<messages.length; i++) {
                var mesg = messages[i];
                mesg.displayHandler();
            }
        }
    }
}









util.MessageQueue.prototype.renderNext = function(force) {
    force = force || false;
    if (force) {
        this.waitingForResponse = false;
        clearInterval(this.renderNextTimeout);
        this.renderNextTimeout = null;
    }
    if (this.queued.length > 0) {
        if (!this.waitingForResponse) {
            //Assert.warn('util.MessageQueue.renderNext:displaying from queue '+this.queueId+', queue length = '+this.queued.length);
            var body = document.getElementsByTagName('body')[0];
            if (!body) throw new Error('Unable to find body element');
            var message = null;
            var queueHead = new Array;
            do {
                message = this.queued.shift();
                //Assert.warn('util.MessageQueue.renderNext:shift message type = '+message.type);
                if (message.type != this.type && this.type != util.MessageQueue.types.ALL) {
                    //Assert.warn('util.MessageQueue.renderNext:not all queue, not our message type');
                    queueHead.push(message);
                    message = null;
                }
            } while (!message && this.queued.length > 0);
            while (queueHead.length > 0) {
                this.queued.unshift(queueHead.shift());
            }
            if (message) {
                this.waitingForResponse = true;
                message.displayHandler();
            }
        } else if (!this.renderNextTimer) {
            //Assert.warn('util.MessageQueue.renderNext:setting renderNext timer -- '+this.queued.length+' messages remain');
            this.renderNextTimer = setInterval('util.MessageQueue.queues['+this.queueId+'].renderNext()', 1000);
        }
    }
}




window.messageQueue = new util.MessageQueue(util.MessageQueue.types.ALL);


/***** END MESSAGE QUEUE *******/

















/***** COOKIE MANAGEMENT *****/

/**
 * Creates a cookie via javascript
 *
 * @since  1.1.2.8
 * @access public
 * @param  string    name
 * @param  string    value
 * @param  int       ttl      Time to live, in seconds - defaults to session [optional]
 * @param  string    path     [optional]
 * @param  string    domain   [optional]
 * @param  boolean   secure   [optional]
 * @return void
 */
util.createCookie = function createCookie(name,value) {
    // Support optional arguments
    var argv    = arguments;
    var argc    = arguments.length;
    var ttl     = (argc > 2) ? argv[2]*1000 : null;
    var path    = (argc > 3) ? argv[3]      : null;
    var domain  = (argc > 4) ? argv[4]      : null;
    var secure  = (argc > 5) ? argv[5]      : false;
    // If we have a ttl to work with, calculate the expires
    if (ttl) {
        var expires = new Date();
        expires.setTime(expires.getTime()+ttl);
    }
    // Create the actual cookie
    document.cookie = name + "=" + escape (value) + 
                      ((expires == null) ? "" : ("; expires=" + expires.toGMTString())) + 
                      ((path    == null) ? "" : ("; path=" + path)) + 
                      ((domain  == null) ? "" : ("; domain=" + domain)) + 
                      ((secure  == true) ? "; secure" : "");
}

/**
 * Returns the value of a cookie
 * 
 * @since  1.1.2.8
 * @access public
 * @param  string    name
 * @return string
 */
util.readCookie = function readCookie(name) {
    var nameEQ = name + "=";
    var ca = document.cookie.split(';');
    for(var i=0;i < ca.length;i++) {
        var c = ca[i];
        while (c.charAt(0)==' ') c = c.substring(1,c.length);
        if (c.indexOf(nameEQ) == 0) return unescape(c.substring(nameEQ.length,c.length));
    }
    return null;
}

/**
 * Removes a cookie
 *
 * @since  1.1.2.8
 * @access public
 * @param  string    name
 * @return void
 */
util.eraseCookie = function eraseCookie(name) {
    util.createCookie(name,"",-1);
}



/* --- classes/InterfaceObject.js --- */
/**
 * $Id: InterfaceObject.js 28317 2006-03-28 22:26:34Z philip $
 * $Author: philip $
 * $Revision: 28317 $
 * $Name$
 * $Date: 2006-03-28 14:26:34 -0800 (Tue, 28 Mar 2006) $
 *
 * @jsRequire interfaces.Interface
 *
 * InterfaceObject is a starter object for any JavaScript object
 * created. Once an object adds the InterfaceObject to its prototype
 * chain, it gains the functionality of all methods and variables
 * defined in this object, making InterfaceObject the perfect place
 * to incorporate other JavaScript libraries such as the Interface
 * library.
 *
 * @version    $Revision: 28317 $
 * @author     Philip Snyder <philip@pricegrabber.com>
 * @copyright  Copyright &copy; 2006, Philip Snyder
 * @see        interfaces.Interface
 */



/**
 * InterfaceObject Constructor / Definition
 *
 * @access public
 * @since  v1.1
 * @param  string      id
 * @return InterfaceObject
 */
function InterfaceObject(id) {
    // Method definitions
    this.getProperty = InterfaceObject_GetProperty;
    this.setProperty = InterfaceObject_SetProperty;
    this.getId       = InterfaceObject_GetId;
    this.setId       = InterfaceObject_SetId;
    // Member definitions
    this.properties = [];
    // Initialization
    this.setId(id);
    return this;
}

// Setup InterfaceObject prototype chain
InterfaceObject.prototype             = new Object;
InterfaceObject.prototype.constructor = InterfaceObject;
InterfaceObject.superclass            = Object.prototype;



/**
 * Returns the value of a property.
 *
 * @access public
 * @since  v1.1
 * @return mixed
 */
function InterfaceObject_GetProperty(name) {
    if (this.properties[name]) return this.properties[name];
    else                       return null;
}

/**
 * Sets the value of a property.
 *
 * @access public
 * @since  v1.1
 * @param  string  name
 * @param  mixed   value
 * return  boolean
 */
function InterfaceObject_SetProperty(name, value) {
    this.properties[name] = value;
    return true;
}

/**
 * Returns the id of the object.
 *
 * @access public
 * @since  v1.1
 * @return string
 */
function InterfaceObject_GetId() {
    return this.getProperty('id');
}

/**
 * Sets the id of the object.
 *
 * @access public
 * @since  v1.1
 * @param  string   id
 * @return boolean
 */
function InterfaceObject_SetId(id) {
    return this.setProperty('id', id);
}


/**
 * Extend the InterfaceObject class with any known included
 * functionality (for example Interface.implement)
 *
 * The reverse of this test can be found in interfaces.Interface
 * so that either file can be included first and the
 * functionality is still in place.
 */
if (typeof(Interface_Implement) == 'function') InterfaceObject.prototype.implement = Interface_Implement;

/* --- interfaces/Interface.js --- */
/**
 * $Id: Interface.js 28308 2006-03-28 22:01:11Z philip $
 * $Author: philip $
 * $Revision: 28308 $
 * $Name$
 * $Date: 2006-03-28 14:01:11 -0800 (Tue, 28 Mar 2006) $
 *
 *
 *
 * This Interface JavaScript library serves to implement
 * a sort-of hacked interface extension to the standard
 * JavaScript language.
 *
 * The Interface function is never intended to be
 * invoked directly but rather in a prototype chain.
 *
 * Documentation will be available at:
 *
 * http://wiki.pricegrabber.com/moin.cgi/PhilipSnyder
 *
 *
 *
 * @version    $Revision: 28308 $
 * @author     Philip Snyder <philip@pricegrabber.com>
 * @copyright  Copyright &copy; 2006, Philip Snyder
 */

/**
 * Interface Constructor / Definition
 *
 * This is NOT intended to be instantiated directly. See
 * documentation for a complete explanation.
 *
 * @access public
 * @since  v1.1
 * @return Interface
 */
function Interface() {
    this.implement = Interface_Implement;
    return this;
}

/**
 * Confirms complete implementation of passed in Interface function reference.
 *
 * This method checks the object in question and validates that it has
 * either implemented or borrowed every method & variable defined in
 * the interfaces it claims to implement.
 *
 * @access public
 * @since  v1.1
 * @return void
 */
function Interface_Implement(ifaceRef) {
    var tmpObj  = new ifaceRef();
    var objName = (this.constructor+'').substr(('function ').length, (this.constructor+'').indexOf('(') - 'function '.length);
    for (var prop in tmpObj) {
        var typeCheck = false;
        var isFunc    = false;
        eval('isFunc    = (typeof(tmpObj.'+prop+') == "function");');
        eval('typeCheck = (typeof(this.'+prop+')   == typeof(tmpObj.'+prop+'));');
        if (prop != 'implement' && !typeCheck) {
            if (isFunc) throw new Error(objName+'.'+prop+"() not implemented!");
            else        throw new Error(objName+'.'+prop+" is either a wrong type or missing.");
        }
    }
}

/**
 * Attach the implement() method to our InterfaceObject as
 * well, and all classes extending InterfaceObjects will have
 * it available to them (and all at the cost of 1
 * function loaded into memory).
 */
if (typeof(InterfaceObject) == 'function') InterfaceObject.prototype.implement = Interface_Implement;

/* --- interfaces/AnchoredInterface.js --- */
/**
 * $Id: AnchoredInterface.js 40205 2007-07-06 18:21:33Z dominique $
 * $Author: dominique $
 * $Revision: 40205 $
 * $Name$
 * $Date: 2007-07-06 11:21:33 -0700 (Fri, 06 Jul 2007) $
 *
 * @jsRequire DomUtils
 * @jsRequire interfaces.Interface
 *
 *
 * @version    $Revision: 40205 $
 * @author     Philip Snyder <philip@pricegrabber.com>
 * @copyright  Copyright &copy; 2006, Philip Snyder, PriceGrabber.com
 * @see        interfaces.Interface
 */

/**
 * AnchoredInterface Constructor / Definition
 *
 * This interface is built on top of the Interface object
 * and is NOT intended to be instantiated directly. See
 * documentation on interfaces.Interface for a complete
 * explanation.
 *
 * @access public
 * @since  v1.1
 * @return AnchoredInterface
 */
function AnchoredInterface() {
    this.elemId            = null;
    this.anchorId          = null;
    this.disableScroll     = false;
    this.getAnchorX        = AnchoredInterface_GetAnchorX;
    this.getAnchorY        = AnchoredInterface_GetAnchorY;
    this.getAnchorZ        = AnchoredInterface_GetAnchorZ;
    this.getAnchorWidth    = AnchoredInterface_GetAnchorWidth;
    this.getAnchorHeight   = AnchoredInterface_GetAnchorHeight;
    this.getElemWidth      = AnchoredInterface_GetElemWidth;
    this.getElemHeight     = AnchoredInterface_GetElemHeight;
    this.getAnchorPosition = AnchoredInterface_GetAnchorPosition;
    this.setAnchor         = AnchoredInterface_SetAnchor;
    this.alignElement      = AnchoredInterface_AlignElement;
}

// Setup AnchoredInterface prototype chain
AnchoredInterface.prototype             = new Interface;
AnchoredInterface.prototype.constructor = AnchoredInterface;
AnchoredInterface.superclass            = Interface.prototype;

/**
 * Constants defining anchor points around the element.
 *
 * !DO NOT MODIFY THIS!
 *
 * @access public
 * @since  v1.1
 * @var    AnchoredInterface.ALIGN   struct
 */
AnchoredInterface.ALIGN = { RIGHT_TOP: 1, RIGHT_BOTTOM: 2, LEFT_BOTTOM: 3, LEFT_TOP: 4 };

/**
 * Returns the anchor element's X coordinate.
 *
 * @access public
 * @since  v1.1
 * @return integer
 */
function AnchoredInterface_GetAnchorX() {
    return DomUtils.getElementLeft(document.getElementById(this.anchorId));
}

/**
 * Returns the anchor element's Y coordinate.
 *
 * @access public
 * @since  v1.1
 * @return integer
 */
function AnchoredInterface_GetAnchorY() {
    return DomUtils.getElementTop(document.getElementById(this.anchorId));
}

/**
 * Returns the anchor element's Z index.
 *
 * @access public
 * @since  v1.1
 * @return integer
 */
function AnchoredInterface_GetAnchorZ() {
    return DomUtils.getZIndex(document.getElementById(this.anchorId));
}

/**
 * Returns the anchor element's width.
 *
 * @access public
 * @since  v1.1
 * @return integer
 */
function AnchoredInterface_GetAnchorWidth() {
    //window.messageQueue.add( new Message(funcname(this), 'getting anchor width') );
    return DomUtils.getElementWidth(document.getElementById(this.anchorId));
}

/**
 * Returns the anchor element's height.
 *
 * @access public
 * @since  v1.1
 * @return integer
 */
function AnchoredInterface_GetAnchorHeight() {
    return DomUtils.getElementHeight(document.getElementById(this.anchorId));
}

/**
 * Returns the element's width.
 *
 * @access public
 * @since  v1.1
 * @return integer
 */
function AnchoredInterface_GetElemWidth() {
    //window.messageQueue.add( new Message(funcname(this), 'getting element width') );
    return DomUtils.getElementWidth(document.getElementById(this.elemId));
}

/**
 * Returns the element's height.
 *
 * @access public
 * @since  v1.1
 * @return integer
 */
function AnchoredInterface_GetElemHeight() {
    return DomUtils.getElementHeight(document.getElementById(this.elemId));
}

/**
 * Calculates the best alignment position for the element based on window size and position of the anchor element.
 *
 * Returns a value from the AnchoredInterface.ALIGN struct.
 *
 * @access public
 * @since  v1.1
 * @return integer
 */
function AnchoredInterface_GetAnchorPosition() {
    //window.messageQueue.add( new Message('AnchoredInterface_GetAnchorPosition', 'called') );
    var pos    = false;
    var elem   = document.getElementById(this.elemId);
    if (!elem)   throw new Error('Unable to find element: '+this.elemId);
    var anchor = document.getElementById(this.anchorId);
    if (!anchor) throw new Error('Unable to find anchor element: '+this.anchorId);

    //window.messageQueue.add( new Message(funcname(this), 'starting getElementWidth') );
    var wWidth  = parseInt(DomUtils.getWindowWidth(window));
    var wHeight = parseInt(DomUtils.getWindowHeight(window));
    var scrollX = parseInt(DomUtils.getWindowScrollX(window));
    var scrollY = parseInt(DomUtils.getWindowScrollY(window));
    var aWidth  = parseInt(DomUtils.getElementWidth(anchor));
    var aHeight = parseInt(DomUtils.getElementHeight(anchor));
    var aX      = parseInt(DomUtils.getElementLeft(anchor));
    var aY      = parseInt(DomUtils.getElementTop(anchor));
    var aWidth  = parseInt(DomUtils.getElementWidth(anchor));
    var aHeight = parseInt(DomUtils.getElementHeight(anchor));
    var eWidth  = parseInt(DomUtils.getElementWidth(elem));
    var eHeight = parseInt(DomUtils.getElementHeight(elem));
    //window.messageQueue.add( new Message(funcname(this), 'passed getElementWidth') );


    if (this.disableScroll) {
    
        /**
         * DO NOT TOUCH. I'M SERIOUS. This is difficult enough to figure out the first time. ;)
         */
        var enoughRoomOnTop    = (aY - scrollY > eHeight ? true : false);
        var enoughRoomOnBottom = (wHeight + scrollY - aHeight - aY > eHeight ? true : false);
        var enoughRoomOnLeft   = (aX - scrollX + aWidth > eWidth ? true : false);
        var enoughRoomOnRight  = (wWidth - aX + scrollX > eWidth ? true : false);
    
        /**
         * Preferred position order:
         *
         *   left top
         *   left bottom
         *   right bottom
         *   right top
         *
         * Please note that this is the position of the anchor in
         * relation to the main element, NOT the other way around
         */
//alert('enoughRoomOnTop= '+enoughRoomOnTop+'\nenoughRoomOnBottom= '+enoughRoomOnBottom+'\nenoughRoomOnLeft= '+enoughRoomOnLeft+'\nenoughRoomOnRight= '+enoughRoomOnRight);
        // Left Top
        if (enoughRoomOnBottom && enoughRoomOnRight) {
            pos = AnchoredInterface.ALIGN.LEFT_TOP;
        // Left Bottom
        } else if (enoughRoomOnTop && enoughRoomOnRight) {
            pos = AnchoredInterface.ALIGN.LEFT_BOTTOM;
        // Right Bottom
        } else if (enoughRoomOnTop && enoughRoomOnLeft) {
            pos = AnchoredInterface.ALIGN.RIGHT_BOTTOM;
        // Right Top
        } else if (enoughRoomOnBottom && enoughRoomOnLeft) {
            pos = AnchoredInterface.ALIGN.RIGHT_TOP;
        } else if (!enoughRoomOnRight) {
            pos = AnchoredInterface.ALIGN.LEFT_TOP;
        } else {
            pos = AnchoredInterface.ALIGN.LEFT_TOP;
        }
        return pos;
    
    } else {
    
        //how much is missing to display on the right (<=0 means enough to display)
        var not_enough_right = ((aX-scrollX) +  eWidth) - wWidth;
        //how much is missing to display on the left (<=0 means enough to display)
        var not_enough_left =  eWidth - (aX-scrollX);
        //how much is missing to display on the left if not scrolled (<=0 means enough to display)
        var not_enough_absolute_left = eWidth - aX;
    
        //how much is missing to display on the bottom (<=0 means enough to display)
        var not_enough_bottom = ((aY-scrollY) +  eHeight) - wHeight;
        //how much is missing to display on the top (<=0 means enough to display)
        var not_enough_top =  eHeight - (aY-scrollY);
        //how much is missing to display on the top if not scrolled (<=0 means enough to display)
        var not_enough_absolute_top = eHeight - aY;
    
        //alert("window is "+wWidth+" by "+wHeight+" and scrolled to "+scrollX+" by "+scrollY+"\nthumb is "+aWidth+" by "+aHeight+" and at "+aX+" by "+aY+"\npop is "+eWidth+" by "+eHeight+"\n\nif there is enough room, value <=0:\n\nright: "+not_enough_right+"\nleft: "+not_enough_left+"\nabsleft: "+not_enough_absolute_left+"\nbottom: "+not_enough_bottom+"\ntop: "+not_enough_top+"\nabstop: "+not_enough_absolute_top);
    
        var horiz = '';
        var vertic = '';
        var ss = 10;
    
        // #1 pref position: top right
        // #2 pref position: bottom right
        // #3 pref position: top left
        // #4 pref position: bottom left
        // if we fit or are closer from fitting to the right than to the left or there is not enough room on the left
        if (not_enough_right <= 0 || not_enough_right < not_enough_left || not_enough_absolute_left > 0) {
            horiz = 'right';
            // scroll if necessary
            if (not_enough_right > 0) {
                for (var i=0; i<=(not_enough_right/(ss*2))+1; i++) {
                    setTimeout('window.scroll(parseInt(DomUtils.getWindowScrollX(window))+'+ss+',parseInt(DomUtils.getWindowScrollY(window)))',100);
                }
            }
        // else if we are closer from the left and there is room if we scroll
        } else {
            horiz = 'left';
            // scroll if necessary
            if (not_enough_left > 0 && !this.disableScroll) {
                for (var i=0; i<=(not_enough_left/(ss*2))+1; i++) {
                    setTimeout('window.scroll(parseInt(DomUtils.getWindowScrollX(window))-'+ss+',parseInt(DomUtils.getWindowScrollY(window)))',100);
                }
            }
        }
    
        // if we are closer from fitting to the top than to the bottom and there is enough room at the top
        if (not_enough_top <= 0 || (not_enough_top < not_enough_bottom && not_enough_absolute_top <= 0)) {
            vertic = 'top';
            // scroll if necessary
            if (not_enough_top > 0 && !this.disableScroll) {
                for (var i=0;i<=(not_enough_top/(ss*2))+1;i++) {
                    setTimeout('window.scroll(parseInt(DomUtils.getWindowScrollX(window)),parseInt(DomUtils.getWindowScrollY(window))-'+ss+')',100);
                }
            }
        // else if we are closer from the top and there is room if we scroll
        } else {
            vertic = 'bottom';
            // scroll if necessary
            if (not_enough_bottom > 0 && !this.disableScroll) {
                for (var i=0; i<=(not_enough_bottom/(ss*2))+1; i++) {
                    setTimeout('window.scroll(parseInt(DomUtils.getWindowScrollX(window)),parseInt(DomUtils.getWindowScrollY(window))+'+ss+')',100);
                }
            }
        }
    
        if      (horiz == 'right' && vertic == 'top'   ) pos = AnchoredInterface.ALIGN.RIGHT_TOP;
        else if (horiz == 'right' && vertic == 'bottom') pos = AnchoredInterface.ALIGN.RIGHT_BOTTOM;
        else if (horiz == 'left'  && vertic == 'top'   ) pos = AnchoredInterface.ALIGN.LEFT_TOP;
        else if (horiz == 'left'  && vertic == 'bottom') pos = AnchoredInterface.ALIGN.LEFT_BOTTOM;
    
        return pos;
    }
}

/**
 * Sets the anchor element.
 *
 * @access public
 * @since  v1.1
 * @param  DOMElement  anchor
 * @return void
 */
function AnchoredInterface_SetAnchor(anchor) {
    //alert('anchor id: '+anchor.id);
    this.anchorId = anchor.id;
}

/**
 * Aligns the element based on the best placement determined by AnchoredInterface_GetAnchorPosition.
 *
 * @access public
 * @since  v1.1
 * @param  integer   pos   A value from the AnchoredInterface.ALIGN struct
 * @return void
 */
function AnchoredInterface_AlignElement(pos) {
    //window.messageQueue.add( new Message('AnchoredInterface_AlignElement', 'called') );
    var elem   = document.getElementById(this.elemId);
    if (!elem)   throw new Error('Unable to find element: '+this.elemId);

    var anchor = document.getElementById(this.anchorId);
    if (!anchor) throw new Error('Unable to find anchor element: '+this.anchorId);

    pos = pos || this.getAnchorPosition();

    //window.messageQueue.add( new Message('AnchoredInterface_AlignElement', 'starting getElementWidth') );
    var aLeft   = DomUtils.getElementLeft(anchor);
    var aTop    = DomUtils.getElementTop(anchor);
    var aHeight = DomUtils.getElementHeight(anchor);
    var aWidth  = DomUtils.getElementWidth(anchor);
    var eHeight = DomUtils.getElementHeight(elem);
    var eWidth  = DomUtils.getElementWidth(elem);
    /*alert('aLeft: '+aLeft+"\n"+
          'aTop: '+aTop+"\n"+
          'aHeight: '+aHeight+"\n"+
          'aWidth: '+aWidth+"\n"+
          'eHeight: '+eHeight+"\n"+
          'eWidth: '+eWidth+"\n");*/
    //window.messageQueue.add( new Message('AnchoredInterface_AlignElement', 'passed getElementWidth') );
    switch (pos) {
        /**
         * Right Top alignment means:
         *
         *             +--------+
         *             | anchor |
         *             +--------+
         *    +-----------------+
         *    | elem            |
         *    +-----------------+
         */
        case AnchoredInterface.ALIGN.RIGHT_TOP:
            elem.style.left = parseInt( aLeft - eWidth  + aWidth  )+'px';
            elem.style.top  = parseInt( aTop  + aHeight )+'px';
            break;
        /**
         * Right Bottom alignment means:
         *
         *    +-----------------+
         *    | elem            |
         *    +-----------------+
         *             +--------+
         *             | anchor |
         *             +--------+
         */
         case AnchoredInterface.ALIGN.RIGHT_BOTTOM:
            elem.style.left = parseInt( aLeft - eWidth + aWidth )+'px';
            elem.style.top  = parseInt( aTop  + aHeight )+'px';
            break;
        /**
         * Left Top alignment means:
         *
         *    +--------+
         *    | anchor |
         *    +--------+
         *    +-----------------+
         *    | elem            |
         *    +-----------------+
         */
         case AnchoredInterface.ALIGN.LEFT_TOP:
            elem.style.left = parseInt( aLeft )+'px';
            elem.style.top  = parseInt( aTop + aHeight)+'px';
            break;
        /**
         * Left Bottom alignment means:
         *
         *    +-----------------+
         *    | elem            |
         *    +-----------------+
         *    +--------+
         *    | anchor |
         *    +--------+
         */
         case AnchoredInterface.ALIGN.LEFT_BOTTOM:
        default:
            elem.style.left = parseInt( aLeft )+'px';
            elem.style.top  = parseInt( aTop - eHeight )+'px';
            break;
    }
    // fanatic memory cleanup
    elem   = null;
    anchor = null;
    //window.messageQueue.add( new Message('AnchoredInterface_AlignElement', 'finished') );
}


/* --- interfaces/CalloutInterface.js --- */
/**
 * $Id: CalloutInterface.js 31337 2006-10-19 21:39:16Z philip $
 * $Author: philip $
 * $Revision: 31337 $
 * $Name$
 * $Date: 2006-10-19 14:39:16 -0700 (Thu, 19 Oct 2006) $
 *
 * @jsRequire DomUtils
 * @jsRequire interfaces.Interface
 * @jsRequire interfaces.AnchoredInterface
 *
 *
 * @version    $Revision: 31337 $
 * @author     Philip Snyder <philip@pricegrabber.com>
 * @copyright  Copyright &copy; 2006, Philip Snyder, PriceGrabber.com
 * @see        interfaces.Interface
 * @see        interfaces.AnchoredInterface
 */

/**
 * CalloutInterface Constructor / Definition
 *
 * This interface is built on top of the Interface object
 * and is NOT intended to be instantiated directly. See
 * documentation on interfaces.Interface for a complete
 * explanation.
 *
 * @access public
 * @since  v1.1
 * @return CalloutInterface
 */
function CalloutInterface() {
    this.calloutId      = null;
    this.elemId         = null;
    this.anchorId       = null;
    this.calloutPadding = 0;
    // Callout interface implementation
    this.erase             = CalloutInterface_Erase;
    this.draw              = CalloutInterface_Draw;
    this.alignElement      = CalloutInterface_AlignElement;
    // Anchored interface implementation
    this.disableScroll     = false;
    this.getAnchorX        = AnchoredInterface_GetAnchorX;
    this.getAnchorY        = AnchoredInterface_GetAnchorY;
    this.getAnchorZ        = AnchoredInterface_GetAnchorZ;
    this.getAnchorWidth    = AnchoredInterface_GetAnchorWidth;
    this.getAnchorHeight   = AnchoredInterface_GetAnchorHeight;
    this.getElemWidth      = AnchoredInterface_GetElemWidth;
    this.getElemHeight     = AnchoredInterface_GetElemHeight;
    this.getAnchorPosition = AnchoredInterface_GetAnchorPosition;
    this.setAnchor         = AnchoredInterface_SetAnchor;
    this.implement(AnchoredInterface);
    return this;
} // End CalloutInterface

// Setup CalloutInterface prototype chain
CalloutInterface.prototype             = new Interface;
CalloutInterface.prototype.constructor = CalloutInterface;
CalloutInterface.superclass            = Interface.prototype;






/****** BEGIN EDIT SECTION ******/

/**
 * General Settings
 *
 * Defines general settings for the callout interface. These can
 * be overridden by any script after inclusion of this file. 
 *
 * Example:
 *
 *    <script language="JavaScript">
 *    CalloutInterface.settings.images.left.src = 'http://ai.pricegrabber.com/images/callout/left_arrow.gif';
 *    // Or even...
 *    CalloutInterface.settings.images.left = { src: 'http://ai.pricegrabber.com/images/callout/left_arrow.gif', height: 20, width: 10 };
 *    </script>
 *
 * Note that 'http://ai.pricegrabber.com' is included... if you
 * don't include the full path, the url will be relative to your
 * web server -- which in general is wrong except for during
 * development.
 *
 * @access public
 * @since  v1.1
 * @var    CalloutInterface.settings   struct
 */
CalloutInterface.settings = {
    images: {
        left:   { src: util.resourceManager.get('http://ai.pricegrabber.com/images/balloon/left_arrow.gif'),   height: 23, width: 11 },
        top:    { src: util.resourceManager.get('http://ai.pricegrabber.com/images/balloon/top_arrow.gif'),    height: 11, width: 23 },
        right:  { src: util.resourceManager.get('http://ai.pricegrabber.com/images/balloon/right_arrow.gif'),  height: 21, width: 11 },
        bottom: { src: util.resourceManager.get('http://ai.pricegrabber.com/images/balloon/bottom_arrow.gif'), height: 11, width: 23 }
    }
};

/****** END EDIT SECTION ******/







/**
 * CalloutInterface.erase()
 *
 * "Erases" the callout by removing the dom elements created with CalloutInterface.draw().
 *
 * @access public
 * @since  v0.0.1a
 * @return void
 */
function CalloutInterface_Erase() {
    //window.messageQueue.add( new Message('CalloutInterface_Erase', 'called') );
    var callout = document.getElementById(this.calloutId);
    if (callout) {
        //callout.parentNode.removeChild(callout);
        DomUtils.removeElement(callout);
    }
    callout = null;
    //window.messageQueue.add( new Message('CalloutInterface_Erase', 'finished') );
} // End CalloutInterface_Erase


/**
 * CalloutInterface.draw()
 *
 * "Draws" the actual callout by creating the necessary dom elements and applying them to
 * the object's element.
 *
 * @access public
 * @since  v0.0.1a
 * @return void
 */
function CalloutInterface_Draw() {
    //window.messageQueue.add( new Message('CalloutInterface_Draw', 'called') );
    if (!this.calloutId) this.calloutId = this.id+'_Callout';
    var elem    = document.getElementById(this.elemId);
    if (!elem) throw new Error('Unable to find element: '+this.elemId);
    var anchor  = document.getElementById(this.anchorId);
    if (!anchor) throw new Error('Unable to find anchor element: '+this.anchorId);
    if (elem && anchor) {
        elem.style.padding = '1px';
        elem.style.border  = '1px solid #dadada';
        //elem.style.backgroundColor = '#ffffff';
        var callout = document.getElementById(this.calloutId);
        if (callout && callout.parentNode) DomUtils.removeElement(callout);
        var pos                  = this.getAnchorPosition();
        callout                  = document.createElement('img');
        callout.id               = this.calloutId;
        callout.style.position   = 'absolute';
        callout.style.visibility = 'hidden';
        var body = document.getElementsByTagName('body')[0];
        body.appendChild(callout);
        var imgDetails;
        //alert('Callout interface: '+pos);

        switch (pos) {
            case AnchoredInterface.ALIGN.RIGHT_TOP:
                //if (typeof(writeDebug) == 'function') writeDebug('right_top');
                imgDetails        = CalloutInterface.settings.images.top;
                //window.messageQueue.add( new Message('callout img', 'right_top: '+imgDetails.src) );
                callout.src       = util.resourceManager.get(imgDetails.src);
                callout.height    = imgDetails.height;
                callout.width     = imgDetails.width;
                //callout.style.top = (DomUtils.getElementHeight(elem)+2)+'px';
                //if (DomUtils.browser.isIE() && document.compatMode == 'BackCompat') {
                //    eStyle  = DomUtils.getCurrentStyle(elem);
                //    height  = parseInt(DomUtils.getElementHeight(elem));
                //    callout.style.top = parseInt(height+1)+'px';
                //}
                callout.style.left = this.calloutPadding+'px';
                break;
            case AnchoredInterface.ALIGN.RIGHT_BOTTOM:
                //if (typeof(writeDebug) == 'function') writeDebug('right_bottom');
                imgDetails         = CalloutInterface.settings.images.top;
                //window.messageQueue.add( new Message('callout img', 'right_bottom: '+imgDetails.src) );
                callout.src        = util.resourceManager.get(imgDetails.src);
                callout.height     = imgDetails.height;
                callout.width      = imgDetails.width;
                //callout.style.top  = '-'+(callout.height+(DomUtils.browser.isIE() ? -1 : -1))+'px';
                //callout.style.left = this.calloutPadding+'px';
                break;
            case AnchoredInterface.ALIGN.LEFT_TOP:
                imgDetails         = CalloutInterface.settings.images.top;
                //window.messageQueue.add( new Message('callout img', 'left_top: '+imgDetails.src) );
                callout.src        = util.resourceManager.get(imgDetails.src);
                callout.height     = imgDetails.height;
                callout.width      = imgDetails.width;
                //callout.style.top  = (DomUtils.getElementHeight(elem)+2)+'px';
                //window.messageQueue.add( new Message('CalloutInterface_Draw', 'getting elem width for callout') );
                //callout.style.left = (DomUtils.getElementWidth(elem)-1-callout.width-this.calloutPadding)+'px';
                //window.messageQueue.add( new Message('CalloutInterface_Draw', 'finished elem width for callout') );
                break;
            case AnchoredInterface.ALIGN.LEFT_BOTTOM:
            default:
                //if (typeof(writeDebug) == 'function') writeDebug('left_bottom');
                imgDetails         = CalloutInterface.settings.images.bottom;
                //window.messageQueue.add( new Message('callout img', 'left_bottom: '+imgDetails.src) );
                callout.src        = util.resourceManager.get(imgDetails.src);
                callout.height     = imgDetails.height;
                callout.width      = imgDetails.width;
                //callout.style.top  = '-'+(callout.height+(DomUtils.browser.isIE() ? -1 : -1))+'px';
                //window.messageQueue.add( new Message('CalloutInterface_Draw', 'getting elem width for callout') );
                //callout.style.left = (DomUtils.getElementWidth(elem)-1-callout.width-this.calloutPadding)+'px';
                //callout.style.left = (DomUtils.getElementLeft(elem)-1-callout.width-this.calloutPadding)+'px';
                //window.messageQueue.add( new Message(funcname(this), 'finished elem width for callout') );
                break;
        }

        //elem.appendChild(callout);
        //callout.style.zIndex = parseInt(DomUtils.getZIndex(elem))+1;
        //this.alignElement(pos);
        //CalloutInterface_AlignElement.call(this, pos);
    }
    //window.messageQueue.add( new Message('CalloutInterface_Draw', 'finished') );
} // End CalloutInterface_Draw


/**
 * CalloutInterface.alignElement()
 *
 * Aligns the object's element by adjusting its left & top by the proper calculations (which are done
 * in this function) on the height & width & style of the callout.
 *
 * @access public
 * @since  v0.0.1a
 * @see    AnchoredInterface
 * @param  string    pos      One of the constants defined in AnchoredInterface.ALIGN
 * @return void
 */
function CalloutInterface_AlignElement(pos) {
    //window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'called') );
    pos = pos || this.getAnchorPosition();
    var elem    = document.getElementById(this.elemId);
    if (!elem)    throw new Error('Unable to find element: '+this.elemId);
    var callout = document.getElementById(this.calloutId);
    if (!callout) throw new Error('Unable to find callout element: '+this.calloutId);
    var anchor  = document.getElementById(this.anchorId);
    if (!anchor)  throw new Error('Unable to find anchor element: '+this.anchorId);

    var aTop    = parseInt( DomUtils.getElementTop(anchor) );
    var aLeft   = parseInt( DomUtils.getElementLeft(anchor) );
    var aHeight = parseInt( DomUtils.getElementHeight(anchor) );
    var aWidth  = parseInt( DomUtils.getElementWidth(anchor) );

    var eTop    = parseInt( DomUtils.getElementTop(elem) );
    var eLeft   = parseInt( DomUtils.getElementLeft(elem) );
    var eHeight = parseInt( DomUtils.getElementHeight(elem) );
    var eWidth  = parseInt( DomUtils.getElementWidth(elem) );

    var cHeight = parseInt( DomUtils.getElementHeight(callout) );
    var cWidth  = parseInt( DomUtils.getElementWidth(callout) );
    switch (pos) {
        /**
         * Right Top alignment means:
         *
         *             +--------+
         *             | anchor |
         *             +--------+
         *                 /\
         *    +-----------'  '--+
         *    | elem            |
         *    +-----------------+
         */
        case AnchoredInterface.ALIGN.RIGHT_TOP:
            // Adjust main element's position to accomodate callout element
            elem.style.top       = parseInt( eTop  + cHeight )+'px';
            elem.style.left      = parseInt( eLeft + this.calloutPadding )+'px';
            // Position the callout element
            callout.style.left   = parseInt( aLeft + parseInt( aWidth / 2 ) - parseInt(cWidth / 2) )+'px';
            callout.style.top    = parseInt( aTop  + aHeight + 1)+'px';
            callout.style.zIndex = parseInt(DomUtils.getZIndex(elem))+1;
/*
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'alignment position: right_top') );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'anchor.style.left   = '+DomUtils.getElementLeft(anchor)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'anchor.style.top    = '+DomUtils.getElementTop(anchor)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'anchor.style.height = '+DomUtils.getElementHeight(anchor)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'anchor.style.width  = '+DomUtils.getElementWidth(anchor)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'elem.style.left     = '+DomUtils.getElementLeft(elem)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'elem.style.top      = '+DomUtils.getElementTop(elem)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'elem.style.height   = '+DomUtils.getElementHeight(elem)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'elem.style.width    = '+DomUtils.getElementWidth(elem)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'callout.style.left  = '+DomUtils.getElementLeft(callout)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'callout.style.top   = '+DomUtils.getElementTop(callout)) );
*/
            break;
        /**
         * Right Bottom alignment means:
         *
         *    +-----------------+
         *    | elem            |
         *    +-----------,  ,--+
         *                 \/
         *             +--------+
         *             | anchor |
         *             +--------+
         */
        case AnchoredInterface.ALIGN.RIGHT_BOTTOM:
            // Adjust main element's position to accomodate callout element
            elem.style.top       = parseInt( eTop  + cHeight - 1 )+'px';
            elem.style.left      = parseInt( eLeft + this.calloutPadding )+'px';
            // Position the callout element
            callout.style.left   = parseInt( aLeft + parseInt(aWidth/2) - parseInt( cWidth / 2) )+'px';
            callout.style.top    = parseInt( aTop + aHeight )+'px';
            callout.style.zIndex = parseInt( DomUtils.getZIndex(elem) )+1;
/*
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'alignment position: right_bottom') );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'anchor.style.left   = '+DomUtils.getElementLeft(anchor)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'anchor.style.top    = '+DomUtils.getElementTop(anchor)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'anchor.style.height = '+DomUtils.getElementHeight(anchor)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'anchor.style.width  = '+DomUtils.getElementWidth(anchor)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'elem.style.left     = '+DomUtils.getElementLeft(elem)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'elem.style.top      = '+DomUtils.getElementTop(elem)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'elem.style.height   = '+DomUtils.getElementHeight(elem)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'elem.style.width    = '+DomUtils.getElementWidth(elem)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'callout.style.left  = '+DomUtils.getElementLeft(callout)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'callout.style.top   = '+DomUtils.getElementTop(callout)) );
*/
            break;
        /**
         * Left Top alignment means:
         *
         *    +--------+
         *    | anchor |
         *    +--------+
         *        /\
         *    +--'  '-----------+
         *    | elem            |
         *    +-----------------+
         */
        case AnchoredInterface.ALIGN.LEFT_TOP:
            // Adjust main element's position to account for the callout
            elem.style.top       = parseInt( eTop + cHeight - 1 )+'px';
            elem.style.left      = parseInt( eLeft - this.calloutPadding )+'px';
            // Position the callout element
            callout.style.left   = parseInt( aLeft + parseInt(aWidth / 2) - parseInt(cWidth / 2) - 1 )+'px';
            callout.style.top    = parseInt( aTop + aHeight )+'px';
            callout.style.zIndex = parseInt( DomUtils.getZIndex(elem) )+1;
/*
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'alignment position: left_top') );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'anchor.style.left   = '+DomUtils.getElementLeft(anchor)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'anchor.style.top    = '+DomUtils.getElementTop(anchor)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'anchor.style.height = '+DomUtils.getElementHeight(anchor)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'anchor.style.width  = '+DomUtils.getElementWidth(anchor)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'elem.style.left     = '+DomUtils.getElementLeft(elem)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'elem.style.top      = '+DomUtils.getElementTop(elem)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'elem.style.height   = '+DomUtils.getElementHeight(elem)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'elem.style.width    = '+DomUtils.getElementWidth(elem)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'callout.style.left  = '+DomUtils.getElementLeft(callout)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'callout.style.top   = '+DomUtils.getElementTop(callout)) );
*/
            break;
        /**
         * Left Bottom alignment means:
         *
         *    +-----------------+
         *    | elem            |
         *    +--,  ,-----------+
         *        \/
         *    +--------+
         *    | anchor |
         *    +--------+
         */
        case AnchoredInterface.ALIGN.LEFT_BOTTOM:
        default:
            // Adjust main element's position to account for the callout
            elem.style.top       = parseInt( eTop - cHeight - 2)+'px';
            elem.style.left      = parseInt( eLeft - this.calloutPadding )+'px';
            // Position the callout element
            callout.style.left   = parseInt( aLeft - parseInt( cWidth / 2 ) + parseInt( aWidth / 2 ) - 1 )+'px';
            callout.style.top    = parseInt( aTop - cHeight )+'px';
            callout.style.zIndex = parseInt( DomUtils.getZIndex(elem) )+1;
/*
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'alignment position: left_bottom') );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'anchor.style.left   = '+DomUtils.getElementLeft(anchor)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'anchor.style.top    = '+DomUtils.getElementTop(anchor)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'anchor.style.height = '+DomUtils.getElementHeight(anchor)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'anchor.style.width  = '+DomUtils.getElementWidth(anchor)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'elem.style.left     = '+DomUtils.getElementLeft(elem)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'elem.style.top      = '+DomUtils.getElementTop(elem)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'elem.style.height   = '+DomUtils.getElementHeight(elem)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'elem.style.width    = '+DomUtils.getElementWidth(elem)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'callout.style.left  = '+DomUtils.getElementLeft(callout)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'callout.style.top   = '+DomUtils.getElementTop(callout)) );
*/
            break;
    }
    //window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'finished') );
} // End CalloutInterface_AlignElement


/* --- interfaces/DropShadowInterface.js --- */
/**
 * $Id: DropShadowInterface.js 44406 2007-10-11 21:09:15Z tmacneil $
 * $Author: tmacneil $
 * $Revision: 44406 $
 * $Name$
 * $Date: 2007-10-11 14:09:15 -0700 (Thu, 11 Oct 2007) $
 *
 * @jsRequire DomUtils
 * @jsRequire interfaces.Interface
 * @jsRequire classes.Browser
 * @jsRequire classes.EventManager
 * @jsRequire classes.ResourceManager
 *
 *
 * @version    $Revision: 44406 $
 * @author     Philip Snyder <philip@pricegrabber.com>
 * @copyright  Copyright &copy; 2006, Philip Snyder, PriceGrabber.com
 * @see        interfaces.Interface
 */

/**
 * DropShadowInterface Constructor / Definition
 *
 * This interface is built on top of the Interface object
 * and is NOT intended to be instantiated directly. See
 * documentation on interfaces.Interface for a complete
 * explanation.
 *
 * @access public
 * @since  v1.1
 * @return DropShadowInterface
 */
function DropShadowInterface(color, xOffset, yOffset, blur, opacity, xShrink, yShrink) {
    this.dropShadowId = null;
    this.elemId       = null;
    this.color        = null;
    this.xOffset      = null;
    this.yOffset      = null;
    this.xShrink      = null;
    this.yShrink      = null;
    this.blur         = null;
    this.opacity      = null;
    this.shadowStyle  = null;
    this.images       = new Array;
    this.draw         = DropShadowInterface_Draw;
    this.erase        = DropShadowInterface_Erase;
    this.init         = DropShadowInterface_Init;
    this.setBlur      = DropShadowInterface_SetBlur;
    this.alignElement = DropShadowInterface_AlignElement;
    return this;
} // End DropShadowInterface

// Setup DropShadowInterface prototype chain
DropShadowInterface.prototype             = new Interface;
DropShadowInterface.prototype.constructor = DropShadowInterface;
DropShadowInterface.superclass            = Interface.prototype;




/****** BEGIN EDIT SECTION ******/

/**
 * Drop shadow style definitions.
 *
 * This data structure allows for the definition of various different
 * drop shadow "styles" that can be applied by setting the shadowStyle
 * of the object implementing DropShadowInterface.
 *
 * Example:
 *
 *    <script language="JavaScript">
 *    var popup = new DropShadowPopup();
 *    popup.shadowStyle = 'Default';
 *    </script>
 *
 * You can also define a shadow style at any time after inclusion of
 * this file and then reference that style instead if you like.
 *
 * @access public
 * @since  v1.1
 * @var    DropShadowInterface.styles   struct
 */
DropShadowInterface.styles = {
    DefaultMenu: { color: 'rgb(0,0,0)', xOffset: 10, yOffset: 15, blur: { iRadius: 12, iSigma: 20}, opacity: .2, xShrink: 16, yShrink: 20 },
    Default:     { color: 'rgb(0,0,0)', xOffset: 17, yOffset: 17, blur: { iRadius: 20, iSigma: 20}, opacity: .2, xShrink: 29, yShrink: 24 },
    IEDefault:   { color: 'rgb(0,0,0)', xOffset:  7, yOffset: 10, blur: { iRadius: 10, iSigma: 20}, opacity: .2, xShrink:  9, yShrink: 10 },
    CheckAll:    { color: 'rgb(0,0,0)', xOffset:  7, yOffset:  7, blur: { iRadius: 10, iSigma: 10}, opacity: .2, xShrink:  9, yShrink:  4 },
    oldIe:       { color: 'rgb(0,0,0)', xOffset:  7, yOffset: 10, blur: { iRadius: 10, iSigma: 20}, opacity: .2, xShrink:  9, yShrink: 10 }
};

/**
 * engineUrl holds the path to the php gradient script.
 *
 * This value can be changed at any time after this file
 * is included as follows:
 *
 * <script>
 *  DropShadowInterface.engineUrl = '/any/url/you/want.php';
 * </script>
 *
 * @access public
 * @since  v1.1
 * @var    string
 */
DropShadowInterface.engineUrl = '/gfx/gradient.php';

/****** END EDIT SECTION ******/




/**
 * Internal image ids used for keeping track of image resources.
 *
 * !DO NOT MODIFY THIS!
 *
 * @access private
 * @since  v1.1
 * @var    DropShadowInterface.imgIds   struct
 */
//DropShadowInterface.imgIds = { top: 0, left: 1, bottom: 2, right: 3, topLeft: 4, bottomLeft: 5, bottomRight: 6, topRight: 7, callout: 8 };
DropShadowInterface.imgIds = { top: 0, left: 1, bottom: 2, right: 3, topLeft: 4, bottomLeft: 5, bottomRight: 6, topRight: 7 };






/**
 * Initializes an object that implements the DropShadowInterface.
 * Call this function prior to any draw() functions.
 *
 * @access public
 * @since  v1.1
 * @return void
 */
function DropShadowInterface_Init(color, xOffset, yOffset, blur, opacity, xShrink, yShrink) {
    //window.messageQueue.add( new Message('DropShadowInterface_Init', 'called') );

    if (!this.shadowStyle && DomUtils.browser.isIE()) this.shadowStyle = 'IEDefault';
    else if (!this.shadowStyle)              this.shadowStyle = 'Default';
    var shadowStyle  = DropShadowInterface.styles[this.shadowStyle];
    this.color       = (color)          ? color   : shadowStyle.color;
    this.xOffset     = !isNaN(xOffset)  ? xOffset : shadowStyle.xOffset;
    this.yOffset     = !isNaN(yOffset)  ? yOffset : shadowStyle.yOffset;
    this.blur        = isString(blur)   ? blur    : shadowStyle.blur;
    this.opacity     = isFloat(opacity) ? opacity : shadowStyle.opacity;
    this.xShrink     = !isNaN(xShrink)  ? xShrink : shadowStyle.xShrink;
    this.yShrink     = !isNaN(yShrink)  ? yShrink : shadowStyle.yShrink;

    /**
     * Handle color parameter
     */
    this.hexColor = '';
    this.rgbColor = '';
    var  rgbRegex = /^rgb\(\s?([0-9]{1,3})\s?\,\s?([0-9]{1,3})\s?\,\s?([0-9]{1,3})\s?\)/;
    var  hexRegex = /^\#?[0-9A-Fa-f]{3}$|\#[0-9A-Fa-f]{6}$/;
    if (hexRegex.test(this.color)) {
        var triplets = new Array;
        if (this.color.length > 4) {
            this.hexColor = this.color;
            for (var start=1; start+2<=this.color.length; start += 2) triplets[triplets.length] = this.color.substr(start, 2);
            for (var i=0;     i<triplets.length;          i++)        triplets[i] = parseInt(triplets[i], 16);
            this.rgbColor = triplets.join(',');
        } else if (this.color.length == 4) {
            for (var start=1; start+1<=this.color.length; start++) triplets[triplets.length] = this.color.substr(start, 1)+this.color.substr(start, 1);
            this.hexColor = '#'+triplets.join('');
            for (var i=0;     i<triplets.length;          i++)     triplets[i] = parseInt(triplets[i], 16);
            this.rgbColor = triplets.join(',');
        } else throw new Error('No valid color setting defined! color: '+this.color);
    } else {
        var matches = this.color.match(rgbRegex);
        if (matches && matches.length > 1) {
            matches.shift();
            for (var i=0; i<matches.length; i++) {
                if (parseInt(matches[i]) > 255) throw new Error('Invalid RGB color specified: '+this.color);
            }
            this.rgbColor = matches.join(',');
        } else throw new Error('No valid color setting defined! color: '+this.color);
    }
    /**
     * Handle blur parameter
     */
    if (isString(this.blur)) {
        var radius = parseInt(this.blur);
        var sigma  = 0;
        if (this.blur.indexOf('x') > 0) sigma = parseInt(this.blur.split('x')[1]);
        this.setBlur(radius, sigma);
    }
    
    this.images = new Array;
    this.images[DropShadowInterface.imgIds.top]         = DropShadowInterface.engineUrl+'?grad=linear&dir=btt&height='+this.blur.iRadius+
         '&width='+this.blur.iRadius+'&color='+this.rgbColor+'&gaussian='+this.blur.iRadius+'x'+this.blur.iSigma+'&opacity='+this.opacity;
    this.images[DropShadowInterface.imgIds.bottom]      = DropShadowInterface.engineUrl+'?grad=linear&dir=ttb&height='+this.blur.iRadius+
         '&width='+this.blur.iRadius+'&color='+this.rgbColor+'&gaussian='+this.blur.iRadius+'x'+this.blur.iSigma+'&opacity='+this.opacity;
    this.images[DropShadowInterface.imgIds.left]        = DropShadowInterface.engineUrl+'?grad=linear&dir=rtl&height='+this.blur.iRadius+
         '&width='+this.blur.iRadius+'&color='+this.rgbColor+'&gaussian='+this.blur.iRadius+'x'+this.blur.iSigma+'&opacity='+this.opacity;
    this.images[DropShadowInterface.imgIds.right]       = DropShadowInterface.engineUrl+'?grad=linear&dir=ltr&height='+this.blur.iRadius+
         '&width='+this.blur.iRadius+'&color='+this.rgbColor+'&gaussian='+this.blur.iRadius+'x'+this.blur.iSigma+'&opacity='+this.opacity;
    this.images[DropShadowInterface.imgIds.bottomRight] = DropShadowInterface.engineUrl+'?grad=radial&radius='+(this.blur.iRadius*2)+
         '&height='+this.blur.iRadius+'&width='+this.blur.iRadius+'&opacity='+this.opacity+
         '&x=0&y=0'+'&color='+this.rgbColor+'&gaussian='+this.blur.iRadius+'x'+this.blur.iSigma;
    this.images[DropShadowInterface.imgIds.topRight]    = DropShadowInterface.engineUrl+'?grad=radial&radius='+(this.blur.iRadius*2)+
         '&height='+this.blur.iRadius+'&width='+this.blur.iRadius+'&opacity='+this.opacity+
         '&x=0&y='+(this.blur.iRadius+1)+'&color='+this.rgbColor+'&gaussian='+this.blur.iRadius+'x'+this.blur.iSigma;
    this.images[DropShadowInterface.imgIds.bottomLeft]  = DropShadowInterface.engineUrl+'?grad=radial&radius='+(this.blur.iRadius*2)+
         '&height='+this.blur.iRadius+'&width='+this.blur.iRadius+'&opacity='+this.opacity+
         '&x='+(this.blur.iRadius+1)+'&y=0'+'&color='+this.rgbColor+'&gaussian='+this.blur.iRadius+'x'+this.blur.iSigma;
    this.images[DropShadowInterface.imgIds.topLeft]     = DropShadowInterface.engineUrl+'?grad=radial&radius='+(this.blur.iRadius*2)+
         '&height='+this.blur.iRadius+'&width='+this.blur.iRadius+'&opacity='+this.opacity+
         '&x='+(this.blur.iRadius+1)+'&y='+(this.blur.iRadius+1)+'&color='+this.rgbColor+'&gaussian='+this.blur.iRadius+'x'+this.blur.iSigma;
    //this.images[DropShadowInterface.imgIds.callout]     = '/images/callout_right.png';
    if (typeof(window.preloadImage) == 'function') {
        for (i=0; i<this.images.length; i++) {
            util.resourceManager.get(this.images[i]);
            //util.resourceManager.get(this.images[i], this.blur.iRadius, this.blur.iRadius);
        }
    }

    //window.messageQueue.add( new Message('DropShadowInterface_Init', 'finished') );
} // End DropShadowInterface_Init




/**
 * "Draws" the actual drop shadow by creating the necessary dom elements and applying them to
 * the object's element.
 *
 * @access public
 * @since  v1.1
 * @return void
 */
function DropShadowInterface_Draw() {
    //window.messageQueue.add( new Message('DropShadowInterface_Draw', 'called') );
    var opacity = this.opacity;
    // remove the old drop shadow if it exists
    var dropShadow = document.getElementById(this.dropShadowId);
    if (dropShadow) {
        DomUtils.removeElement(dropShadow);
        dropShadow = null;
    }
    var elem = document.getElementById(this.elemId);
    if (elem) {
        // Clone the elem node now before any other draw changes it.
        var zIndex                       = parseInt(DomUtils.getZIndex(elem))-2;
        this.dropShadowId                = this.id+'_DropShadow';
        var dropShadow                   = elem.cloneNode(false);
        dropShadow.id                    = this.dropShadowId;
        dropShadow.style.visibility      = 'hidden';
        dropShadow.style.zIndex          = zIndex;
        dropShadow.className             = '';
        dropShadow.style.position        = 'absolute';
        dropShadow.style.backgroundColor = 'transparent';
        dropShadow.style.border          = 'none';
        dropShadow.style.left            = parseInt(DomUtils.getElementLeft(elem))+'px';
        dropShadow.style.top             = parseInt(DomUtils.getElementTop(elem))+'px';
        dropShadow.xOffset               = parseInt(this.xOffset);
        dropShadow.yOffset               = parseInt(this.yOffset);
        dropShadow.overflow              = 'visible !important';
        // DO NOT move this to the end since it messes up dimension calculations in IE
        //elem.parentNode.appendChild(dropShadow);
        document.getElementsByTagName('body')[0].appendChild(dropShadow);
/**
 * This is the BETTER / easier way to make the drop shadow follow
 * the element. However, this doesn't work on IE. Retain code for
 * possible future uses.
 */
//    if (!DomUtils.browser.isIE()) {
//        window.addEvent(this.elem, 'DOMAttrModified', function(e) {
//            if (e.type == 'DOMAttrModified' && e.attrName == 'style') {
//                dropShadow.setAttribute('style', e.newValue);
//                dropShadow.style.zIndex   = parseInt(this.style.zIndex)-2;
//                dropShadow.style.overflow = 'visible !important';
//                dropShadow.style.border   = 'none';
//                dropShadow.style.left     = DomUtils.getElementLeft(this)+'px';
//                dropShadow.style.top      = DomUtils.getElementTop(this)+'px';
//            }
//        }, this.elem, true);
//    }
//    this.elem.parentNode.insertBefore(this.dropShadow, this.elem.nextSibling);
        if (this.xShrink) {
            //window.messageQueue.add( new Message('DropShadowInterface_Draw', 'getting drop shadow width') );
            dropShadow.style.width = parseInt(parseInt(DomUtils.getElementWidth(elem))+(parseInt(this.xShrink)/2))+'px';
        }
        if (this.yShrink) {
            //window.messageQueue.add( new Message('DropShadowInterface_Draw', 'getting drop shadow height') );
            dropShadow.style.height = parseInt(parseInt(DomUtils.getElementHeight(elem))+(parseInt(this.yShrink)/2))+'px';
        }
    
        if (DomUtils.browser.isIE()) {
            var eStyle = DomUtils.getCurrentStyle(elem);
            pLeft   = parseInt(eStyle.paddingLeft);
            pRight  = parseInt(eStyle.paddingRight);
            pTop    = parseInt(eStyle.paddingTop);
            pBottom = parseInt(eStyle.paddingBottom);
            pWidth  = parseInt((!isNaN(pLeft) ? pLeft : 0)+(!isNaN(pRight)  ? pRight  : 0));
            pHeight = parseInt((!isNaN(pTop)  ? pTop  : 0)+(!isNaN(pBottom) ? pBottom : 0));
        } else {
            pHeight = 0;
            pWidth  = 0;
        }
        
        var img;
        var shadow                         = document.createElement('div');
        shadow.id                          = this.id+'_DropShadow_Main';
        shadow.style.position              = 'absolute';
        //window.messageQueue.add( new Message('DropShadowInterface_Draw', 'getting shadow width') );
        shadow.style.width                 = parseInt(parseInt(DomUtils.getElementWidth(elem))-parseInt(this.xShrink))+'px';
        shadow.style.height                = parseInt(parseInt(DomUtils.getElementHeight(elem))-parseInt(this.yShrink))+'px';
        shadow.style.left                  = parseInt(this.xOffset)+'px';
        shadow.style.top                   = parseInt(this.yOffset)+'px';
        shadow.style.border                = 'none';
        shadow.style.backgroundColor       = 'rgb('+this.rgbColor+')';
        shadow.style.zIndex                = zIndex;
        if (DomUtils.browser.isIE()) shadow.style.filter    += 'progid:DXImageTransform.Microsoft.Alpha(opacity='+(this.opacity*100)+')';
        else                         shadow.style.MozOpacity = this.opacity;
        dropShadow.appendChild(shadow);
    
        var bottomEdge                     = document.createElement('div');
        bottomEdge.id                      = this.id+'_DropShadow_Bottom';
        bottomEdge.className               = 'shadow';
        bottomEdge.style.position          = 'absolute';
        bottomEdge.style.top               = parseInt(parseInt(DomUtils.getElementHeight(shadow))+parseInt(this.yOffset))+'px';
        bottomEdge.style.left              = parseInt(this.xOffset)+'px';
        bottomEdge.style.height            = parseInt(this.blur.iRadius)+'px';
        //window.messageQueue.add( new Message('DropShadowInterface_Draw', 'getting bottom width') );
        bottomEdge.style.width             = parseInt(DomUtils.getElementWidth(shadow))+'px';
        //window.messageQueue.add( new Message('DropShadowInterface_Draw', 'bottom width: '+bottomEdge.style.width) );
        bottomEdge.style.zIndex            = zIndex;
        img                                = document.createElement('img');
        img.src                            = util.resourceManager.get(this.images[DropShadowInterface.imgIds.bottom]);
        img.height                         = parseInt(this.blur.iRadius);
        //window.messageQueue.add( new Message('DropShadowInterface_Draw', 'getting image width') );
        img.width                          = parseInt(DomUtils.getElementWidth(shadow));
        img.style.position                 = 'absolute';
        img.style.left                     = '0px';
        img.style.top                      = '0px';

        DropShadowInterface_FixPNG(img);

        bottomEdge.appendChild(img);
        dropShadow.appendChild(bottomEdge);
    
        var rightEdge                      = document.createElement('div');
        rightEdge.id                       = this.id+'_DropShadow_Right';
        rightEdge.className                = 'shadow';
        rightEdge.style.position           = 'absolute';
        //window.messageQueue.add( new Message('DropShadowInterface_Draw', 'getting right width') );
        rightEdge.style.left               = parseInt(parseInt(DomUtils.getElementWidth(shadow))+parseInt(this.xOffset))+'px';
        rightEdge.style.top                = parseInt(this.yOffset)+'px';
        rightEdge.style.height             = parseInt(DomUtils.getElementHeight(shadow))+'px';
        rightEdge.style.width              = parseInt(this.blur.iRadius)+'px';
        rightEdge.style.zIndex             = zIndex;
        img                                = document.createElement('img');
        img.src                            = util.resourceManager.get(this.images[DropShadowInterface.imgIds.right]);
        img.height                         = parseInt(DomUtils.getElementHeight(shadow));
        img.width                          = parseInt(this.blur.iRadius);
        img.style.position                 = 'absolute';
        img.style.left                     = '0px';
        img.style.top                      = '0px';

        DropShadowInterface_FixPNG(img);

        rightEdge.appendChild(img);
        dropShadow.appendChild(rightEdge);
    
        var leftEdge                       = document.createElement('div');
        leftEdge.id                        = this.id+'_DropShadow_Left';
        leftEdge.className                 = 'shadow';
        leftEdge.style.position            = 'absolute';
        leftEdge.style.left                = parseInt(parseInt(this.xOffset)-parseInt(this.blur.iRadius))+'px';
        leftEdge.style.top                 = parseInt(this.yOffset)+'px';
        leftEdge.style.height              = parseInt(DomUtils.getElementHeight(shadow))+'px';
        leftEdge.style.width               = parseInt(this.blur.iRadius)+'px';
        leftEdge.style.zIndex              = zIndex;
        img                                = document.createElement('img');
        img.src                            = util.resourceManager.get(this.images[DropShadowInterface.imgIds.left]);
        img.height                         = parseInt(DomUtils.getElementHeight(shadow));
        img.width                          = parseInt(this.blur.iRadius);
        img.style.position                  = 'absolute';
        img.style.left                      = '0px';
        img.style.top                       = '0px';

        DropShadowInterface_FixPNG(img);

        leftEdge.appendChild(img);
        dropShadow.appendChild(leftEdge);
    
        var topEdge                         = document.createElement('div');
        topEdge.id                          = this.id+'_DropShadow_Top';
        topEdge.className                   = 'shadow';
        topEdge.style.position              = 'absolute';
        topEdge.style.left                  = parseInt(this.xOffset)+'px';
        topEdge.style.top                   = parseInt(parseInt(this.yOffset)-parseInt(this.blur.iRadius))+'px';
        topEdge.style.height                = parseInt(this.blur.iRadius)+'px';
        //window.messageQueue.add( new Message('DropShadowInterface_Draw', 'getting top width') );
        topEdge.style.width                 = parseInt(DomUtils.getElementWidth(shadow))+'px';
        topEdge.style.zIndex                = zIndex;
        img                                 = document.createElement('img');
        img.src                             = util.resourceManager.get(this.images[DropShadowInterface.imgIds.top]);
        img.height                          = parseInt(this.blur.iRadius);
        //window.messageQueue.add( new Message('DropShadowInterface_Draw', 'getting image width') );
        img.width                           = parseInt(DomUtils.getElementWidth(shadow));
        img.style.position                  = 'absolute';
        img.style.left                      = '0px';
        img.style.top                       = '0px';

        DropShadowInterface_FixPNG(img);

        topEdge.appendChild(img);
        dropShadow.appendChild(topEdge);
    
        var brCorner                        = document.createElement('div');
        brCorner.id                         = this.id+'_DropShadow_BottomRightCorner';
        brCorner.className                  = 'shadow';
        brCorner.style.position             = 'absolute';
        //window.messageQueue.add( new Message('DropShadowInterface_Draw', 'getting br width') );
        brCorner.style.top                  = parseInt(parseInt(DomUtils.getElementHeight(shadow))+parseInt(this.yOffset))+'px';
        brCorner.style.left                 = parseInt(parseInt(DomUtils.getElementWidth(shadow))+parseInt(this.xOffset))+'px';
        brCorner.style.height               = parseInt(this.blur.iRadius)+'px';
        brCorner.style.width                = parseInt(this.blur.iRadius)+'px';
        brCorner.style.zIndex               = zIndex;
        img                                 = document.createElement('img');
        img.src                             = util.resourceManager.get(this.images[DropShadowInterface.imgIds.bottomRight]);
        img.height                          = parseInt(this.blur.iRadius);
        img.width                           = parseInt(this.blur.iRadius);
        img.style.position                  = 'absolute';
        img.style.left                      = '0px';
        img.style.top                       = '0px';
        
        DropShadowInterface_FixPNG(img);

        brCorner.appendChild(img);
        dropShadow.appendChild(brCorner);
    
        var trCorner                        = document.createElement('div');
        trCorner.id                         = this.id+'_DropShadow_TopRightCorner';
        trCorner.className                  = 'shadow';
        trCorner.style.position             = 'absolute';
        //window.messageQueue.add( new Message('DropShadowInterface_Draw', 'getting tr width') );
        trCorner.style.left                 = parseInt(parseInt(DomUtils.getElementWidth(shadow))+parseInt(this.xOffset))+'px';
        trCorner.style.top                  = parseInt(parseInt(this.yOffset)-parseInt(this.blur.iRadius))+'px';
        trCorner.style.height               = parseInt(this.blur.iRadius)+'px';
        trCorner.style.width                = parseInt(this.blur.iRadius)+'px';
        trCorner.style.zIndex               = zIndex;
        img                                 = document.createElement('img');
        img.src                             = util.resourceManager.get(this.images[DropShadowInterface.imgIds.topRight]);
        img.height                          = parseInt(this.blur.iRadius);
        img.width                           = parseInt(this.blur.iRadius);
        img.style.position                  = 'absolute';
        img.style.left                      = '0px';
        img.style.top                       = '0px';

        DropShadowInterface_FixPNG(img);

        trCorner.appendChild(img);
        dropShadow.appendChild(trCorner);
    
        var blCorner                        = document.createElement('div');
        blCorner.id                         = this.id+'_DropShadow_BottomLeftCorner';
        blCorner.className                  = 'shadow';
        blCorner.style.position             = 'absolute';
        blCorner.style.left                 = parseInt(parseInt(this.xOffset)-parseInt(this.blur.iRadius))+'px';
        //window.messageQueue.add( new Message('DropShadowInterface_Draw', 'getting bl width') );
        blCorner.style.top                  = parseInt(parseInt(DomUtils.getElementHeight(shadow))+parseInt(this.yOffset))+'px';
        blCorner.style.height               = parseInt(this.blur.iRadius)+'px';
        blCorner.style.width                = parseInt(this.blur.iRadius)+'px';
        blCorner.style.zIndex               = zIndex;
        img                                 = document.createElement('img');
        img.src                             = util.resourceManager.get(this.images[DropShadowInterface.imgIds.bottomLeft]);
        img.height                          = parseInt(this.blur.iRadius);
        img.width                           = parseInt(this.blur.iRadius);
        img.style.position                  = 'absolute';
        img.style.left                      = '0px';
        img.style.top                       = '0px';

        DropShadowInterface_FixPNG(img);

        blCorner.appendChild(img);
        dropShadow.appendChild(blCorner);
    
        var tlCorner                        = document.createElement('div');
        tlCorner.id                         = this.id+'_DropShadow_TopLeftCorner';
        tlCorner.className                  = 'shadow';
        tlCorner.style.position             = 'absolute';
        tlCorner.style.left                 = parseInt(parseInt(this.xOffset)-parseInt(this.blur.iRadius))+'px';
        tlCorner.style.top                  = parseInt(parseInt(this.yOffset)-parseInt(this.blur.iRadius))+'px';
        tlCorner.style.height               = parseInt(this.blur.iRadius)+'px';
        tlCorner.style.width                = parseInt(this.blur.iRadius)+'px';
        tlCorner.style.zIndex               = zIndex;
        img                                 = document.createElement('img');
        img.src                             = util.resourceManager.get(this.images[DropShadowInterface.imgIds.topLeft]);
        img.height                          = parseInt(this.blur.iRadius);
        img.width                           = parseInt(this.blur.iRadius);
        img.style.position                  = 'absolute';
        img.style.left                      = '0px';
        img.style.top                       = '0px';

        DropShadowInterface_FixPNG(img);

        tlCorner.appendChild(img);
        dropShadow.appendChild(tlCorner);
        dropShadow.style.visibility = DomUtils.getCurrentStyle(elem).visibility;

    } else {
        throw new Error('unable to find content element');
    }
    //window.messageQueue.add( new Message('DropShadowInterface_Draw', 'finished') );
} // End DropShadowInterface_Draw




/**
 * "Erases" the drop shadow by removing the dom elements created with DropShadowInterface.draw().
 *
 * @access public
 * @since  v0.0.1a
 * @return void
 */
function DropShadowInterface_Erase() {
    //window.messageQueue.add( new Message('DropShadowInterface_Erase', 'called') );
    var dropShadow = document.getElementById(this.dropShadowId);
    if (dropShadow) DomUtils.removeElement(dropShadow);
    // Clean up dom references for IE
    dropShadow = null;
    //window.messageQueue.add( new Message('DropShadowInterface_Erase', 'finished') );
} // End DropShadowInterface_Erase




/**
 * Aligns the object's element by adjusting its left & top by the proper calculations (which are done
 * in this function) on the left & top of the element.
 *
 * @access public
 * @since  v0.0.1a
 * @return void
 */
function DropShadowInterface_AlignElement(pos) {
    //window.messageQueue.add( new Message('DropShadowInterface_AlignElement', 'called') );
    var elem       = document.getElementById(this.elemId);
    if (!elem)       throw new Error('Unable to find element: '+this.elemId);
    var dropShadow = document.getElementById(this.dropShadowId);
    if (!dropShadow) throw new Error('Unable to find drop shadow element: '+this.dropShadowId);
    // Align the drop shadow with the main element
    dropShadow.style.position = 'absolute';
    dropShadow.style.left     = DomUtils.getElementLeft(elem)+'px';
    dropShadow.style.top      = DomUtils.getElementTop(elem)+'px';
    // fanatic memory cleanup
    dropShadow = null;
    elem       = null;
    //window.messageQueue.add( new Message('DropShadowInterface_AlignElement', 'finished') );
} // End DropShadowInterface_AlignElement




/**
 * Sets the blur values of the drop shadow.
 *
 * radius:   radius of area to blur
 * sigma:    amount of pixels to consider when
 *           averaging pixel values
 *
 * @access public
 * @since  v1.1
 * @param  integer  radius   radius of area to blur
 * @param  integer  sigma    amount of pixels to consider when averaging pixel values
 * @return void
 */
function DropShadowInterface_SetBlur(radius, sigma) {
    this.blur = { iRadius: radius, iSigma: sigma };
} // End DropShadowInterface_SetBlur


/**
 * Fixes alpha transparency issue with PNGs on Microsoft Internet Explorer.
 * Did I ever mention how much MSIE sucks?
 *
 * @access public
 * @since  v1.1
 * @return void
 */
function DropShadowInterface_FixPNG(img) {
    if (DomUtils.browser.isIE()) {
        var arVersion = navigator.appVersion.split("MSIE");
        var version = 0.0;
        if (arVersion && arVersion.length > 1) version = parseFloat(arVersion[1]);
        if ((version >= 5.5) && (version < 7) && (document.body.filters)) {
            var imgID    = (img.id) ? "id='" + img.id + "' " : "";
            var imgClass = (img.className) ? "class='" + img.className + "' " : "";
            var imgTitle = (img.title)                    ?
                            "title='" + img.title  + "' " :
                            "title='" + img.alt    + "' " ;
            var imgStyle = "display:inline-block;";
            if (img && img.style) { 
              imgStyle += img.style.cssText || ''; 
              img.style.cssText = "display: none";
            }
            var strNewHTML = "<span " + imgID + imgClass + imgTitle
                           + " style=\"" + "width:" + img.width 
                           + "px; height:" + img.height 
                           + "px;" + imgStyle + ";"
                           + "filter: progid:DXImageTransform.Microsoft.AlphaImageLoader"
                           + "(src=\'"+img.src+"\', sizingMethod='scale');\"></span>";
            img.src = "";
            img.outerHTML = strNewHTML;
        }
    }
}
