/**************************************************
 * Object constructor: instantialises a new
 * instance of the Menu object.
 * IN : name : [scalar] Specifies a name for the
 *             new menu object.
 *      style: [scalar] Specifies the style used to
 *             display the menu.
 *      top  : [scalar] Specifies the top position
 *             of where to place the menu.
 *      left : [scalar] Specifies the left edge
 *             position of where to place the menu.
 * OUT: this
 */
function Menu(name, style, top, left)
{
	if (!document.menus)
		document.menus = new Array();
	
	this.type = 'menu';
	this.index = document.menus.length;
	this.name = name || 'menu' + String(this.index);
	this.style = undefined;
	this.hint = '';
	this.top = 0;
	this.left = 0;
	this.items = new Array();
	this.parent = undefined;
	
	this.setStyle = menuSetStyle;
	this.getStyle = menuGetStyle;
	this.setTop = menuSetTop;
	this.getTop = menuGetTop;
	this.setLeft = menuSetLeft;
	this.getLeft = menuGetLeft;
	this.setHint = menuSetHint;
	this.getHint = menuGetHint;
	this.addMenuItem = menuAddMenuItem;
	this.insertMenuItem = menuInsertMenuItem;
	this.deleteMenuItem = menuDeleteMenuItem;
	this.clear = menuClear;
	this.build = menuBuild;
	this.attachMenu = menuAttachMenu;
	this.detachMenu = menuDetachMenu;
	this.showMenu = menuShowMenu;
	this.hideMenu = menuHideMenu;
	this.useImagePosition = menuUseImagePosition;
	this.getMenuLayer = menuGetMenuLayer;
	
	this.setStyle(style);
	this.setTop(top);
	this.setLeft(left);
	
	this.onhint = menuDefaultOnHint;
	
	document.menus[this.index] = this;
	return this;
}





/**************************************************
 * Changes the CSS style used to display the menu.
 * IN : style: [scalar] Specifies a class name or
 *             style directive used to display the
 *             menu.
 * OUT: true
 */
function menuSetStyle(style)
{
	if ((!style) || (typeof(style) != 'string'))
		this.style = undefined;
	else
		this.style = style;
	
	return true;
}





/**************************************************
 * Returns the style used to display the menu.
 * IN : n/a
 * OUT: class name | style directive | undefined
 */
function menuGetStyle()
{
	return this.style;
}





/**************************************************
 * Changes the top most position of where the menu
 * is to be displayed.
 * IN : top: [scalar] Specifies the top most
 *           position of where the menu is to be
 *           displayed.
 * OUT: true
 */
function menuSetTop(top)
{
	if ((!top) || (typeof(top) != 'number'))
		this.top = 0;
	else
		this.top = top;
	
	return true;
}





/**************************************************
 * Returns the top most position of where the menu
 * is to be displayed.
 * IN : n/a
 * OUT: top position
 */
function menuGetTop()
{
	return this.top;
}





/**************************************************
 * Changes the left most position of where the menu
 * is to be displayed.
 * IN : top: [scalar] Specifies the left most
 *           position of where the menu is to be
 *           displayed.
 * OUT: true
 */
function menuSetLeft(left)
{
	if ((!left) || (typeof(left) != 'number'))
		this.left = 0;
	else
		this.left = left;
	
	return true;
}





/**************************************************
 * Returns the left most position of where the menu
 * is to be displayed.
 * IN : n/a
 * OUT: left position
 */
function menuGetLeft()
{
	return this.left;
}





/**************************************************
 * Changes the hint displayed for the menu when
 * the user positions their mouse over it.
 * IN : hint: [scalar] Specifies the hint text for
 *            the menu.
 * OUT: true
 */
function menuSetHint(hint)
{
	if (typeof(hint) == 'string')
		this.hint = hint;
	else
		this.hint = '';
	
	return true;
}





/**************************************************
 * Returns the hint displayed for the menu when
 * the user positions their mouse over it.
 * IN : n/a
 * OUT: hint text
 */
function menuGetHint()
{
	return this.hint;
}





/**************************************************
 * Adds a new menu item to the end of the list of
 * menu items.
 * IN : text : [scalar] Specifies the text to be
 *             displayed for the menu item.
 *      link : [scalar] Specifies a link the menu
 *             item is to call when pressed.
 *      style: [scalar] Specifies a style used to
 *             display the individual menu item.
 *      name : [scalar] Optionaly specifies a name
 *             for the menu item.
 * OUT: menu item
 */
function menuAddMenuItem(text, link, style, name)
{
	var menuItem = new Object();
	menuItem.type = 'menuitem';
	menuItem.index = this.items.length;
	menuItem.name = name || 'menuItem' + String(menuItem.index);
	menuItem.style = undefined;
	menuItem.hint = '';
	
	menuItem.setStyle = menuItemSetStyle;
	menuItem.getStyle = menuItemGetStyle;
	menuItem.setText = menuItemSetText;
	menuItem.getText = menuItemGetText;
	menuItem.setLink = menuItemSetLink;
	menuItem.getLink = menuItemGetLink;
	menuItem.setHint = menuItemSetHint;
	menuItem.getHint = menuItemGetHint;
	
	menuItem.setStyle(style);
	menuItem.setText(text);
	menuItem.setLink(link);
	
	menuItem.onmouseover = undefined;
	menuItem.onmouseout = undefined;
	menuItem.onclick = undefined;
	menuItem.ondblclick = undefined;
	menuItem.onkeydown = undefined;
	menuItem.onkeyup = undefined;
	menuItem.onkeypress = undefined;
	menuItem.onblur = undefined;
	menuItem.onfocus = undefined;
	
	this.items[menuItem.index] = menuItem;
	
	return this.items[menuItem.index];
}





/**************************************************
 * Inserts a new menu item at the specified
 * position in the list of menu items.
 * IN : index: [scalar] The index at which to
 *             insert the menu item.
 *      text : [scalar] Specifies the text to be
 *             displayed for the menu item.
 *      link : [scalar] Specifies a link the menu
 *             item is to call when pressed.
 *      style: [scalar] Specifies a style used to
 *             display the individual menu item.
 *      name : [scalar] Optionaly specifies a name
 *             for the menu item.
 * OUT: menu item | false
 */
function menuInsertMenuItem(index, text, link, style, name)
{
	var result = false;
	
	if ((index < this.items.length) && (index >= 0))
	{
		var menuItem = new Object();
		menuItem.type = 'menuitem';
		menuItem.index = index;
		menuItem.name = name || 'menuItem' + String(this.items.length);
		menuItem.style = undefined;
		menuItem.hint = '';
		
		menuItem.setStyle = menuItemSetStyle;
		menuItem.getStyle = menuItemGetStyle;
		menuItem.setText = menuItemSetText;
		menuItem.getText = menuItemGetText;
		menuItem.setLink = menuItemSetLink;
		menuItem.getLink = menuItemGetLink;
		menuItem.setHint = menuItemSetHint;
		menuItem.getHint = menuItemGetHint;
		
		menuItem.setStyle(style);
		menuItem.setText(text);
		menuItem.setLink(link);
		
		menuItem.onmouseover = undefined;
		menuItem.onmouseout = undefined;
		menuItem.onmouseup = undefined;
		menuItem.onmousedown = undefined;
		menuItem.onmousemove = undefined;
		menuItem.onclick = undefined;
		menuItem.onblur = undefined;
		menuItem.onfocus = undefined;
		
		for (var i = index; i < this.items.length; i++)
			this.items[i].index++;
		this.items.splice(index, 0, menuItem);
		
		result = this.items[index];
	}
	
	return result;
}





/**************************************************
 * Attempts to remove the specified menu item from
 * the list of menu items.
 * IN : index: [scalar] Specifies the index of the
 *             menu item to remove from the list of
 *             menu items.
 * OUT: success [true|false]
 */
function menuDeleteMenuItem(index)
{
	var result = false;
	
	if ((index < this.items.length) && (index >= 0))
	{
		this.items.splice(index, 1);
		for (var i = index; i < this.items.length; i++)
			this.items[i].index--;
		
		result = true;
	}
	
	return result;
}





/**************************************************
 * Clears the list of menu items.
 * IN : n/a
 * OUT: true
 */
function menuClear()
{
	this.items = new Array();
	
	return true;
}





/**************************************************
 * Builds the menu and writes it to the document.
 * After calling this function, it will no longer
 * be possible to change the menu because of
 * browser limitation issues.
 * IN : n/a
 * OUT: success [true|false]
 */
function menuBuild()
{
	var result = false;
	
	var theMenu = this.getMenuLayer();
	if (!theMenu)
	{
		var menuStyle = '';
		if (this.getStyle())
			menuStyle = ((this.getStyle().match(/:|;/))?('style'):('class')) + '="' + this.getStyle() + '"';
		
		document.writeln('<div name="menu_' + this.name + '" id = "menu_' + this.name + '" style="position: absolute; z-index: 50; visibility: hidden;" onMouseOver="this.inside = true;" onMouseOut="this.inside = false; setTimeout(\'document.menus[' + this.index + '].hideMenu(true);\', 10);">');
		document.writeln('<table border="0" cellpadding="3" cellspacing="0">');
		for (var i = 0; i < this.items.length; i++)
		{
			var itemStyle = '';
			if (this.items[i].getStyle())
				itemStyle = ((this.items[i].getStyle().match(/:|;/))?('style'):('class')) + '="' + this.items[i].getStyle() + '"';
			
			var renderedItem = '&nbsp;';
			if (this.items[i].getText() == '-')
				renderedItem = '<hr ' + ((itemStyle)?(itemStyle):('style="width: 100%; height: 2px;"')) + '>';
			else
				renderedItem = '<a href="' + this.items[i].getLink() + '"' +
				' onMouseOver="var result = false; if (document.menus[' + String(this.index) + '].onhint) { document.menus[' + String(this.index) + '].onhint(document.menus[' + String(this.index) + '].items[' + String(i) + '].getHint()); result = (document.menus[' + String(this.index) + '].items[' + String(i) + '].getHint())?(true):(false); } return (document.menus[' + String(this.index) + '].items[' + String(i) + '].onmouseover)?(document.menus[' + String(this.index) + '].items[' + String(i) + '].onmouseover(event)):(result);"' +
				' onMouseOut="if (document.menus[' + String(this.index) + '].onhint) document.menus[' + String(this.index) + '].onhint(); return (document.menus[' + String(this.index) + '].items[' + String(i) + '].onmouseout)?(document.menus[' + String(this.index) + '].items[' + String(i) + '].onmouseout(event)):(true);"' +
				' onMouseUp="return (document.menus[' + String(this.index) + '].items[' + String(i) + '].onmouseup)?(document.menus[' + String(this.index) + '].items[' + String(i) + '].onmouseup(event)):(true);"' +
				' onMouseDown="return (document.menus[' + String(this.index) + '].items[' + String(i) + '].onmousedown)?(document.menus[' + String(this.index) + '].items[' + String(i) + '].onmousedown(event)):(true);"' +
				' onMouseMove="return (document.menus[' + String(this.index) + '].items[' + String(i) + '].onmousemove)?(document.menus[' + String(this.index) + '].items[' + String(i) + '].onmousemove(event)):(true);"' +
				' onClick="return (document.menus[' + String(this.index) + '].items[' + String(i) + '].onclick)?(document.menus[' + String(this.index) + '].items[' + String(i) + '].onclick(event)):(true);"' +
				' onDblClick="return (document.menus[' + String(this.index) + '].items[' + String(i) + '].ondblclick)?(document.menus[' + String(this.index) + '].items[' + String(i) + '].ondblclick(event)):(true);"' +
				' onKeyPress="return (document.menus[' + String(this.index) + '].items[' + String(i) + '].onkeypress)?(document.menus[' + String(this.index) + '].items[' + String(i) + '].onkeypress(event)):(true);"' +
				' onKeyUp="return (document.menus[' + String(this.index) + '].items[' + String(i) + '].onkeyup)?(document.menus[' + String(this.index) + '].items[' + String(i) + '].onkeyup(event)):(true);"' +
				' onKeyDown="return (document.menus[' + String(this.index) + '].items[' + String(i) + '].onkeydown)?(document.menus[' + String(this.index) + '].items[' + String(i) + '].onkeydown(event)):(true);"' +
				' onFocus="return (document.menus[' + String(this.index) + '].items[' + String(i) + '].onfocus)?(document.menus[' + String(this.index) + '].items[' + String(i) + '].onfocus(event)):(true);"' +
				' onBlur="return (document.menus[' + String(this.index) + '].items[' + String(i) + '].onblur)?(document.menus[' + String(this.index) + '].items[' + String(i) + '].onblur(event)):(true);"' +
				((itemStyle)?(' ' + itemStyle):('')) + '>' + this.items[i].text + '</a>';
			
			document.writeln('<tr>');
			document.write('<td' + ((menuStyle)?(' ' + menuStyle):('')) + '>' + renderedItem + '</td>');
			document.writeln('</tr>');
		}
		document.writeln('</table>');
		document.writeln('</div>');
		
		result = true;
	}
	
	return result;
}





/**************************************************
 * Attempts to attach the menu to the specified
 * parent (e.g. an anchor tag), so all other
 * actions - like showing and hiding the menu -
 * will be performed automagicaly.
 * IN : parent   : [scalar] A parent tag to
 *                 attach the menu to.
 *      mouseover: [scalar] Optionaly specifies
 *                 whether the new mouseover
 *                 event should be triggered
 *                 immediately. This does however
 *                 imply that the result value
 *                 will be the result value of
 *                 the mouseover event instead of
 *                 a status value, which is
 *                 required in cases where a
 *                 specific return value needs to
 *                 be returned from the event
 *                 handler (like window.status in
 *                 a mouseover).
 * OUT: success [true|false]
 */
function menuAttachMenu(parent, mouseover)
{
	var result = false;
	
	if ((parent) && ((!this.parent) || (parent.id != this.parent.id)))
	{
		this.detachMenu();
		
		this.parent = parent;
		this._oldMouseOver = parent.onmouseover;
		this._oldMouseOut = parent.onmouseout;
		parent.onmouseover = function()
		{
			var result = true;
			var theMenu = false;
			for (var i = 0; i < document.menus.length; i++)
				if ((document.menus[i].parent) && (document.menus[i].parent.id == this.id))
				{
					theMenu = document.menus[i];
					break;
				}
			if (theMenu)
			{
				theMenu.showMenu(true);
				if (theMenu.onhint)
				{
					theMenu.onhint(theMenu.getHint());
					result = (theMenu.getHint())?(true):(false);
				}
			}
			return result;
		}
		parent.onmouseout = function()
		{
			var theMenu = false;
			var i;
			for (i = 0; i < document.menus.length; i++)
			{
				if ((document.menus[i].parent) && (document.menus[i].parent.id == this.id))
				{
					theMenu = document.menus[i];
					break;
				}
			}
			if (theMenu)
			{
				var menuObj = theMenu.getMenuLayer();
				if (menuObj)
					menuObj.inside = false;
				setTimeout('document.menus[' + i + '].hideMenu(true);', 10);
				if (theMenu.onhint)
					theMenu.onhint();
			}
			return true;
		}
		
		result = true;
		
		if (mouseover)
			result = parent.onmouseover();
	}
	
	return result;
}





/**************************************************
 * Attempts to detach the menu from its parent,
 * restoring the parent object to the state it had
 * before the menu was attached.
 * IN : n/a
 * OUT: success [true|false]
 */
function menuDetachMenu()
{
	var result = false;
	
	if (this.parent)
	{
		parent.onmouseover = this._oldMouseOver;
		parent.onmouseout = this._oldMouseOut;
		this._oldMouseOver = undefined;
		this._oldMouseOut = undefined;
		result = true;
	}
	
	return result;
}





/**************************************************
 * Shows the menu and calls the old mouseOver
 * routine.
 * IN : callMouseOver: [scalar] Whether to call the
 *                     old mouseOver routine.
 * OUT: success [true|false]
 */
function menuShowMenu(callMouseOver)
{
	var result = false;
	
	var theMenu = this.getMenuLayer();
	if (theMenu)
	{
		if ((this._oldMouseOver) && (callMouseOver))
			this._oldMouseOver();
	
		if (theMenu.style)
		{
			theMenu.style.top = this.getTop();
			theMenu.style.left = this.getLeft();
			theMenu.style.visibility = 'visible';
		} else
		{
			theMenu.top = this.getTop();
			theMenu.left = this.getLeft();
			theMenu.visibility = 'show';
		}
		
		theMenu.inside = true;
		
		result = true;
	}
	
	return result;
}





/**************************************************
 * Hides the menu and calls the old mouseOut
 * routine.
 * IN : callMouseOut: [scalar] Whether to call the
 *                    old mouseOut routine.
 * OUT: success [true|false]
 */
function menuHideMenu(callMouseOut)
{
	var result = false;
	
	var theMenu = this.getMenuLayer();
	if (theMenu)
		if (!theMenu.inside)
		{
			if ((this._oldMouseOut) && (callMouseOut))
				this._oldMouseOut();
	
			if (theMenu.style)
				theMenu.style.visibility = 'hidden';
			else
				theMenu.visibility = 'hide';
			result = true;
		}
	
	return result;
}





/**************************************************
 * Attempts to determine the top and left positions
 * from the specified image.
 * IN : image: [object] An image object to extract
 *             positional information from.
 * OUT: success [true|false]
 */
function menuUseImagePosition(image)
{
	var result = false;
	
	if (image)
		if (image.x != undefined)
		{
			this.setLeft(image.x);
			this.setTop(image.y + image.height);
			result = true;
		} else if (image.offsetLeft != undefined)
		{
			var x = 0;
			var y = 0;
			var parent = image.parentNode;
			while (parent)
			{
				if (parent.offsetLeft)
				{
					x += parent.offsetLeft;
					y += parent.offsetTop;
				}
				parent = parent.parentNode;
			}
			this.setLeft(x);
			this.setTop(y + image.height);
			result = true;
		}
	
	return result;
}





/**************************************************
 * Attempts to fetch the menu layer associated with
 * the current menu object.
 * IN : doc: [object] Optionaly specifies the
 *           document or layer in which to search.
 * OUT: menu layer | false
 */
function menuGetMenuLayer(doc)
{
	var result = false;
	
	if (!doc)
		doc = document;
	
	// First check if the element can simply be found by it's ID
	if (doc.all)
	{
		var obj = doc.all['menu_' + this.name];
		if (obj)
			result = obj;
	} else if (doc.getElementById)
	{
		var obj = doc.getElementById('menu_' + this.name);
		if (obj)
			result = obj;
	}
	
	// Next check if it the menu might be part of a layer
	if ((!result) && (doc.layers))
		for (var i = 0; i < doc.layers.length; i++)
		{
			if (doc.layers[i].name == 'menu_' + this.name)
				result = doc.layers[i];
			else
			{
				var obj = this.getMenuLayer(doc.layers[i]);
				if (obj)
				{
					result = obj;
					break;
				}
			}
		}
	
	return result;
}





/**************************************************
 * Changes the hint displayed on the window status
 * bar.
 * IN : hint: [scalar] Specifies a hint text to be
 *            displayed on the status bar.
 * OUT: true
 */
function menuDefaultOnHint(hint)
{
	if (hint)
		window.status = hint;
	else
		window.status = window.defaultStatus;
	
	return true;
}





/**************************************************
 * Changes the CSS style used to display the menu
 * item.
 * IN : style: [scalar] Specifies a class name or
 *             style directive used to display the
 *             menu item.
 * OUT: true
 */
function menuItemSetStyle(style)
{
	if ((!style) || (typeof(style) != 'string'))
		this.style = undefined;
	else
		this.style = style;
	
	return true;
}





/**************************************************
 * Returns the style used to display the menu item.
 * IN : n/a
 * OUT: class name | style directive | undefined
 */
function menuItemGetStyle()
{
	return this.style;
}





/**************************************************
 * Changes the text displayed for the menu item.
 * IN : text: [scalar] Specifies the text to be
 *            displayed for the menu item.
 * OUT: true
 */
function menuItemSetText(text)
{
	this.text = text || this.name;
	
	return true;
}





/**************************************************
 * Returns the text displayed for the menu item.
 * IN : n/a
 * OUT: text
 */
function menuItemGetText()
{
	return this.text;
}





/**************************************************
 * Changes the link the menu items points to.
 * IN : link: [scalar] Specifies the link the menu
 *            item points to.
 * OUT: true
 */
function menuItemSetLink(link)
{
	this.link = link || '#';
	
	return true;
}





/**************************************************
 * Returns the link the menu item points to.
 * IN : n/a
 * OUT: url
 */
function menuItemGetLink()
{
	return this.link;
}





/**************************************************
 * Changes the hint displayed for the menu item
 * when the user positions their mouse over it.
 * IN : hint: [scalar] Specifies the hint text for
 *            the menu item.
 * OUT: true
 */
function menuItemSetHint(hint)
{
	if (typeof(hint) == 'string')
		this.hint = hint;
	else
		this.hint = '';
	
	return true;
}





/**************************************************
 * Returns the hint displayed for the menu item
 * when the user positions their mouse over it.
 * IN : n/a
 * OUT: hint text
 */
function menuItemGetHint()
{
	return this.hint;
}