/**
 * This file provides some tool to fill a web page with dynamic user interface elements.
 * It is basically compatible with MSIE and Firefox, which are considered to be the most
 * popular ones today.
 *
 * This file implements:
 *  - custom tool tip
 *  - custom context menu
 */




/**
 * Some short API documentation:
 *
 * Tooltips
 * ========
 *
 * Create some hook (div or similar) and assign it an event handler of type onmouseover.
 * This event handler should invoke 
 *
 *     DynUI.tooltip( this, id );
 *
 * where id is a string with ID of another div describing the tooltip. The latter one gets 
 * relocated to a) appear near the hook and b) to fit completely into visible area of 
 * screen/page/window.
 *
 * Of course, the second div describing tooltip is considered to have at least CSS style
 * "display: none;"!
 *
 * If tooltip obscures to much of currently visible area, it won't get unhidden. This is either
 * caused by obscuring more than 80 % of visible area or exceeding available width or height.
 *
 *
 *
 * Context Menus
 * =============
 *
 * This API supports use of statically implemented divs to be context menus as well as
 * having some "description" of context menu to be rendered.
 *
 *
 * Common
 * ------
 *
 * 1. Assign these event handlers to your body-tag:
 *
 *       onmousedown="contextMenu(event);" oncontextmenu="return contextMenu(event);"
 *
 * 2. Select element that is going to get its own context menu. Find related tag in HTML
 *    and subordinate a div with either class name "context-menu" (for static version) or
 *    "context-menu-description" (for dynamic version).
 *
 *
 * Static version
 * --------------
 *
 * That div installed in 2. should be box of your context menu. It gets relocated obeying same
 * rules as given above for tooltips, while "Hook" is point of mouse cursor clicked here.
 *
 * Optionally that div may consist of a single text node being subordinated to it (e.g no further
 * markup code or page elements). If that text consists of alphanumerical characters, only, it is
 * considered to be ID of tag to be used as menu box instead of using that div itself.
 *
 * On action the box gets unhidden and that's all. You may close it whenever you like by calling
 *
 *    contextMenuClose( false );
 *
 * at any time.
 *
 *
 * Dynamic version
 * ---------------
 *
 * This version is preferred over static version. The tag created in step 2 above should contain
 * one single text-node (e.g. no further markup or page elements as children!). This text is either
 * considered to hold description of menu using this syntax:
 *
 *  1. The whole consists of entry descriptions, each separated by a pipe '|'.
 *  2. Each entry in that text describes one entry in context menu.
 *  3. A single entry consist of one or more assignments separated by semicolon, ';'.
 *  4. A single assignment consists of a name optionally followed by assignment operator and value
 *     to be assigned. An entry takes at least a label.
 *
 * Some valid descriptors:
 *
 *   label=foo|label=bar;href=http://www.amazon.de
 *
 * This describes two entry menu, one labelled foo, the other labelled bar. Clicking on "bar"
 * will open german amazon pages.
 *
 * These properties are currently supplied:
 *
 *  label      text to be rendered in a text entry
 *  src        URL to image to be used in an image entry
 *  type       string selecting type of entry:
 *              separator    a separating entry (horizontal line) which is not clickable
 *              text         some text, label describes text to be rendered
 *              image        some image, src describes URL of image to be used
 *  href       URL loaded in location.href on clicking related entry
 *  handler    some javascript function called on click before redirecting
 *  submit     some description on how to adjust a form in current page before submitting it
 *             on click
 *
 *  In each function either href, or submit or none of them is set. It is invalid to set both!
 *  handler may describe an existing javascript function that is called before processing href
 *  or submit on click. In either case that handler gets passed provided href or submit value.
 *  It's return will replace the provided href or submit value in further processing the click.
 *  If href or submit is empty after passing handler function nothing is done.
 *
 *  submit has the following format:
 *
 *   [formname:]name=value(,name=value)*
 *
 *  If formname is omitted the first form of page is used. If selected form does not exist,
 *  nothing is processed and the click is ignored.
 *  each assignment is processed as
 *
 *    document.forms[formname].elements[name] = value
 *
 *  After all assignment are processed the form is triggered for submission.
 *
 *
 *  In any case the click handler will close the context menu!
 *
 *
 *  NOTE! Neither ';' nor ',' are available as values, since escaping is not supported, currently!
 *
 */





function DynUI() {
}


/* implement some variables used to detect current browser. */
DynUI.isMSIE  = ( navigator.appName.indexOf( "Explorer" ) >= 0 );
DynUI.isFF    = ( navigator.userAgent.indexOf( "Firefox" ) >= 0 );
DynUI.isOpera = ( navigator.userAgent.indexOf( "Opera" ) >= 0 );

if ( DynUI.isOpera && DynUI.isMSIE )
	/* Opera identifies itself as Microsoft Internet Explorer */
	DynUI.isMSIE = false;


DynUI.isDOM = ( typeof document.getElementById == "function" );
DynUI.isAll = ( typeof document.all != "undefined" );




DynUI.isDOMNode = function( node ) {
	return ( node && ( typeof node == "object" ) && ( typeof node.parentNode != "undefined" ) );
}
DynUI.isDOMElement = function( node, name ) {

	if ( !DynUI.isDOMNode( node ) || ( node.nodeType != 1 ) )
		return false;

	if ( name && ( name.nodeName != name ) )
		return false;

	return true;

}

DynUI.isArray = function( a ) {
	return ( a && ( typeof a == "object" ) && ( typeof a.length != "undefined" ) );
}






/* Iterates tree down from given top node, selecting node using filter_cb callback, handling selected
   nodes using handler_cb callback (both are required!) and passing node and "pass" to handler_cb.
   max_depth is optional and might select maximum sub-levels to enter.
   Iteration breaks whenever return of a handler callback matches term "!result". */
DynUI.iterateTreeDown = function( top, filter_cb, handler_cb, pass, max_depth ) {

	if ( !DynUI.isDOMNode( top ) )
		return false;

	if ( typeof filter_cb != "function" )
		return false;
	if ( typeof handler_cb != "function" )
		return false;


	return DynUI.__iterateTreeDown( top, filter_cb, handler_cb, pass, max_depth );

}
DynUI.__iterateTreeDown = function( top, filter_cb, handler_cb, pass, max_depth ) {

	if ( DynUI.isDOMNode( top ) ) {

		if ( ( ( typeof max_depth == "undefined" ) || ( max_depth > 0 ) ) && ( top.childNodes.length > 0 ) ) {

			var i, depth;

			if ( max_depth )
				depth = max_depth - 1;
			else
				depth = undefined;


			for ( i = top.childNodes.length - 1; i >= 0; i-- )
				if ( !DynUI.__iterateTreeDown( top.childNodes[i], filter_cb, handler_cb, pass, depth ) )
					return false;

		}


		if ( filter_cb( top, pass ) )
			if ( !handler_cb( top, pass ) )
				return false;

	}


	return true;

}








/**
 * Retrieves coordinates of given element's upper-left corner, relative to
 * current page's origin.
 *
 * @param string/node elem either ID of node in document, or node itself
 * @return boolean/array false or two-element array containing x at index 0, y at index 1
 */

DynUI.getPosition = function( elem ) {

	var x, y;
	var list = new Array();


	/*
	 * get element
	 */

	if ( typeof elem == 'string' )
		elem = document.getElementById( elem );

	if ( !DynUI.isDOMElement( elem ) )
		return list;


	/*
	 * get position of element relative to page's origin
	 */

	x = 0;
	y = 0;

	if ( elem.offsetParent ) {

		while ( elem.offsetParent ) {

			x += elem.offsetLeft;
			y += elem.offsetTop;

			elem = elem.offsetParent;

		}

	} else {

		if ( elem.x )
			x = elem.x;
		if ( elem.y )
			y = elem.y;

	}


	list[0] = x;
	list[1] = y;

	return list;

}


DynUI.getWindowDimensions = function() {

	var dim = new Array();


	if ( DynUI.isMSIE ) {

		if ( document.documentElement ) {

			dim[0] = document.documentElement.scrollLeft;
			dim[1] = document.documentElement.scrollTop;
			dim[2] = document.documentElement.offsetWidth;
			dim[3] = document.documentElement.offsetHeight;

		} else {

			dim[0] = document.body.scrollLeft;
			dim[1] = document.body.scrollTop;
			dim[2] = document.body.offsetWidth;
			dim[3] = document.body.offsetHeight;

		}

	} else {

		dim[0] = window.pageXOffset;
		dim[1] = window.pageYOffset;
		dim[2] = window.innerWidth;
		dim[3] = window.innerHeight;

	}


	return dim;

}


/**
 * Retrieves position of provided element "what" relocated to completely fit on screen.
 * The element "where" describes a point or node on page where "what" should be located.
 *
 * This method tries to place "what" in one out of four positions relative to "where",
 * using this priority/order: upper-left, upper-right, lower-right, lower-left.
 *
 * @param node/array node of element being "where", or two-element array with coordinates
 *                   selecting theoretical position of a pixel-size "where"-element
 *                   (for use with mouse pointer coordinates in an event handler)
 * @param node element to be relocated and fitting into visible area of current page
 * @param integer gap number of pixel "what" should keep distance from "where"
 * @return boolean/array false if relocation failed, else two-element array with x at index 0,
 *                       and y at index 1
 */

DynUI.fitInWindowRelated = function( where, what, gap, ignoreWindowEdges ) {

	var coords;


	if ( DynUI.isDOMElement( where ) )

		/* get coords of top-left corner of given page element */
		coords = DynUI.getPosition( where );

	else if ( DynUI.isArray( where ) && ( where.length == 2 ) ) {

		/* get coords of a pixel - e.g. click point of mouse */
		coords = new Array();
		coords[0] = where[0];
		coords[1] = where[1];

	} else
		return false;



	/*
	 * get dimension of element to fit by rendering hidden
	 */

	var pos, left, top, width, height;


	// move off the visible area to prevent trouble with floating context
	pos    = DynUI.getExternalStyle( what, 'position', 'position' );
	left   = DynUI.getExternalStyle( what, 'left', 'left' );
	top    = DynUI.getExternalStyle( what, 'top', 'top' );

	what.style.position = "absolute";
	what.style.left     = "-1000px";
	what.style.top      = "-1000px";

	// get element being rendered so its dimensions become available
	what.style.visibility = "hidden";
	if ( what.nodeName == "SPAN" )
		what.style.display = "inline";
	else
		what.style.display    = "block";

	// store dimensions
	width  = what.offsetWidth;
	height = what.offsetHeight;

	// hide element again
	what.style.display    = "none";
	what.style.visibility = "visible";

	// got hidden again, restore element's location in visible area
	what.style.position = pos;
	what.style.left     = left;
	what.style.top      = top;


	// store coordinates as well ...
	left   = coords[0];
	top    = coords[1];





	/*
	 * retrieve size of window's visible area
	 */

	var wx, wy, ww, wh;

	coords = DynUI.getWindowDimensions();
	if ( !coords || ( coords.length < 4 ) )
		return false;

	wx = coords[0];
	wy = coords[1];

	if ( !ignoreWindowEdges ) {

		ww = coords[2] - 24;	/* sometimes scrollbars are visible but seem to be */
		wh = coords[3] - 24;	/* included in returned width, so hiding parts of page */

	}




	/*
	 * perform basic tests on size of element to fit
	 */

	if ( ( width > ww ) || ( height > wh ) )
		/* element does not fit in window */
		return false;

	if ( ( width * height ) > ( ww * wh * 0.8 ) )
		/* element obscures more than 80 % of visible area, don't show! */
		return false;



	var gx, gy;

	if ( DynUI.isDOMElement( where ) ) {

		gx = where.offsetWidth;
		gy = where.offsetHeight;

	} else {

		/* e.g. if "where" are coordinates of mouse pointer */
		gx = 1;
		gy = 1;

	}



	/*
	 * Try to place element what in relation to element or pixel where.
	 * While trying each quadrant pay attention to the dimension of
	 * element of pixel where.
	 */

	var x, y;

	do {

		/*
		 * try to place what in second quadrant
		 */

		x = left + 2 * gap + gx;
		y = top + 2 * gap + gy;

		if ( ( x + width < wx + ww ) && ( y + height < wy + wh ) )
			/* fits in window, so use */
			break;


		/*
		 * try to place what in first quadrant
		 */

		x = left + 2 * gap + gx;
		y = top - gap - height;

		if ( ( x + width < wx + ww ) && ( y > wy ) )
			/* fits in window, so use */
			break;


		/*
		 * try to place what in fourth quadrant
		 */

		x = left - gap - width;
		y = top - gap - height;

		if ( ( x > wx ) && ( y > wy ) )
			/* fits in window, so use */
			break;


		/*
		 * try to place what in third quadrant
		 */

		x = left - gap - width;
		y = top + 2 * gap + gy;
	
		if ( ( x > wx ) && ( y + height < wy + wh ) )
			/* fits in window, so use */
			break;


		/*
		 * try to place what so it might obscure where itself
		 */

		if ( x < wx ) {

			x = wx + ww - width - gap;

			if ( ( x > wx ) && ( y > wy ) )
				/* fits in window, so use */
				break;

		}


		y = wy + wh - height - gap;

		if ( ( x > wx ) && ( y > wy ) )
			/* fits in window, so use */
			break;



		x = false;

	} while ( false );


	if ( x == false )
		/* haven't found any good location, so fail */
		return false;



	/*
	 * prepare result and return
	 */

	coords[0] = x;
	coords[1] = y;

	coords[2] = width;
	coords[3] = height;


	return coords;

}


DynUI.isEventType = function( event, type ) {

	if ( !event )
		event = window.event;

	return ( event.type == type );

}


DynUI.getEventTarget = function( event ) {

	if ( !event )
		event = window.event;

	if ( event.target )
		return event.target;
	else
		return event.srcElement;

}


DynUI.__coordsReferringElement = function( elem ) {

	var iter, xoff, yoff, match, t, coords;


	/*
	 * get element
	 */

	if ( typeof elem == 'string' )
		elem = document.getElementById( elem );

	if ( !DynUI.isDOMElement( elem ) )
		return false;


	/*
	 * get position of element relative to page's origin
	 */

	iter = elem;
	xoff = 0;
	yoff = 0;

	while ( DynUI.isDOMNode( iter ) && ( iter.nodeName != 'BODY' ) ) {

		match = false;

		if ( iter.style && ( iter.style.position == "relative" ) )
			match = true;
		else
			if ( DynUI.getExternalStyle( iter, "position", "position" ) == "relative" )
				match = true;


		if ( match ) {

			t = DynUI.getPosition( iter );
			if ( typeof t != "object" )
				return false;

			xoff = t[0];
			yoff = t[1];

			break;

		} else
			iter = iter.parentNode;

	}



	/*
	 * now use optionally detected offset to convert coordinates
	 */

	coords    = new Array();
	coords[0] = xoff;
	coords[1] = yoff;


	return coords;

}


DynUI.coordsLocalToDocument = function( x, y, elem ) {

	if ( ( typeof x != "number" ) || ( typeof y != "number" ) )
		return false;


	var coords = DynUI.__coordsReferringElement( elem );
	if ( !coords || ( coords.length < 2 ) )
		return false;


	coords[0] = x + coords[0];
	coords[1] = y + coords[1];


	return coords;

}


DynUI.coordsDocumentToLocal = function( x, y, elem ) {

	if ( ( typeof x != "number" ) || ( typeof y != "number" ) )
		return false;


	var coords = DynUI.__coordsReferringElement( elem );
	if ( !coords || ( coords.length < 2 ) )
		return false;


	coords[0] = x - coords[0];
	coords[1] = y - coords[1];


	return coords;

}


/**
 * Looks up current cascaded style of given element to get selected property.
 *
 */

DynUI.getExternalStyle = function( elem, MSIEname, NSname ) {

	if ( typeof elem == "string" )
		elem = document.getElementById( elem );
	if ( !DynUI.isDOMNode( elem ) )
		return false;


	if ( elem.currentStyle )
		return elem.currentStyle[MSIEname];
	// else

	if ( window.getComputedStyle ) {

		var style = window.getComputedStyle( elem, "" );

		return style.getPropertyValue( NSname );

	}


	// unsupported browser
	return false;

}











/*
 * Custom tooltip management
 */

DynUI.current_tooltip   = false;
DynUI.supporting_iframe = false;


DynUI.tooltip = function( hook, id ) {

	if ( !DynUI.isDOMElement( hook ) )
		return false;


	var tooltip = document.getElementById( id );
	if ( !DynUI.isDOMElement( tooltip ) )
		return false;


	var coords = DynUI.fitInWindowRelated( hook, tooltip, 3 );
	if ( !coords || ( coords.length < 2 ) )
		return false;


	var width  = coords[2];
	var height = coords[3];


	DynUI.__tooltip_hide();


	coords = DynUI.coordsDocumentToLocal( coords[0], coords[1], hook );
	if ( !coords || ( coords.length < 2 ) )
		return false;



	var iframe = DynUI.__getSupportingIFrameMSIE( width, height );
	if ( iframe ) {
		tooltip.insertBefore( iframe );			
		DynUI.supporting_iframe = iframe;
	}



	tooltip.style.position = "absolute";
	tooltip.style.left     = coords[0] + "px";
	tooltip.style.top      = coords[1] + "px";
	tooltip.style.display  = "block";
	tooltip.style.zIndex   = 60;


	hook.onmouseout = DynUI.tooltip_off;
	DynUI.current_tooltip = tooltip;


	return true;

}

DynUI.tooltip_off = function( event ) {

	var hook = DynUI.getEventTarget( event );

	hook.onmouseout = null;

	DynUI.__tooltip_hide();

}


DynUI.__tooltip_hide = function() {

	if ( DynUI.isDOMElement( DynUI.supporting_iframe ) ) {

		var p = DynUI.supporting_iframe.parentNode;
		if ( p )
			p.removeChild( DynUI.supporting_iframe );

		DynUI.supporting_iframe = false;

	}

	if ( DynUI.isDOMElement( DynUI.current_tooltip ) ) {
		DynUI.current_tooltip.style.display = "none";
		DynUI.current_tooltip = false;
	}
}


DynUI.__getSupportingIFrameMSIE = function( width, height ) {

	if ( DynUI.isAll ) {
		// MSIE requires some hack to perfectly obscure SELECTs

		var iframe = document.createElement( 'IFRAME' );
		if ( iframe ) {

			iframe.src            = "javascript:false;";
			iframe.scrolling      = "no";
			iframe.frameBorder    = "0";

			iframe.style.position = "absolute";
			iframe.style.left     = "0px";
			iframe.style.top      = "0px";
			iframe.style.width    = width + "px";
			iframe.style.height   = height + "px";
			iframe.style.display  = "block";
			iframe.style.zIndex   = 50;

			iframe.style.filter   = 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0);';


			return iframe;

		}
	}


	return false;

}




/*
 * "Scroller"-feature ... makes an optionally selected item visible on load of page
 */

DynUI.scrollToOptionalMark = function() {

	var wc = DynUI.getWindowDimensions();
	if ( wc && ( ( wc[0] > 0 ) || ( wc[1] > 0 ) ) )
		/* don't scroll, since user started to scroll before */
		return true;


	var e = document.getElementById( 'scroll_here_and_make_me_visible' );
	if ( !e )
		/* there's no marked element on current page, so don't scroll */
		return true;


	var pos = DynUI.getPosition( e );
	if ( !pos )
		/* failed to get position of marked element, so can't scroll */
		return false;


	/* include slight space on top */
	pos[1] -= 5;
	if ( pos[1] < 0 )
		pos[1] = 0;


	/* then scroll */
	window.scrollTo( 0, pos[1] );


	return true;

}












/*
 * Context menu management
 */



DynUI.currentContextMenu = false;
DynUI.currentContextMenuIsDynamic = false;
DynUI.currentContextMenuEventList = new Array();
DynUI.mouseIsOverContextMenu = false;

DynUI.preventMSIEContextMenu = false;


DynUI.contextMenu = function( event ) {

	var hook, text, menu, desc;


	if ( !event )
		event = window.event;
	if ( !event )
		return false;


	/*
	 * handle mouse down events here and filter out clicks
	 * with left or middle mouse button ...
	 */

	if ( event.type == "mousedown" ) {

		DynUI.preventMSIEContextMenu = false;


		if ( DynUI.currentContextMenu && !DynUI.mouseIsOverContextMenu ) {
			DynUI.contextMenuClose( false );
			return false;
		}


		if ( DynUI.isFF )
			/* ignore this event in Firefox here */
			return true;


		/* only handle mouse down events, if right mouse button was clicked */
		if ( event.which ) {

			if ( event.which != 3 )
				return true;

		} else
			if ( event.button != 2 )
				return true;


		if ( DynUI.isMSIE )
			DynUI.preventMSIEContextMenu = true;

	} else if ( ( event.type == "contextmenu" ) && DynUI.isMSIE && DynUI.preventMSIEContextMenu ) {

		/* prevent once only! */
		DynUI.preventMSIEContextMenu = false;
		return false;

	}



	/*
	 * get clicked element (hook)
	 */

	hook = DynUI.getEventTarget( event );

	if ( !DynUI.isDOMElement( hook ) )
		return true;




	/*
	 * get subordinated div with id "context-menu" or "context-menu-descriptor"
	 */

	var i, isValid, isDynamic;
	for ( i = 0; i < hook.childNodes.length; i++ )
		if ( DynUI.isDOMElement( hook.childNodes[i], 'DIV' ) ) {

			isValid = true;

			switch ( hook.childNodes[i].id ) {
				case 'context-menu' :
					isDynamic = false; break;
				case 'context-menu-descriptor' :
					isDynamic = true; break;

				default :
					isValid = false;
			}

			if ( isValid )
				break;

		}

	if ( i >= hook.childNodes.length )
		return true;



	/*
	 * read text node of found, sub-ordinated div-tag
	 */

	hook = hook.childNodes[i];

	if ( ( hook.childNodes.length > 1 ) || ( hook.nodeType != 1 ) || ( hook.nodeName != 'DIV' ) ) {

		if ( isDynamic )
			return false;
		else
			/* found div is considered to be menu itself */
			return DynUI.contextMenuStatic( event, hook );

	}


	text = hook.firstChild.nodeValue;
	if ( !text || !text.length )
		return false;

	if ( text.match( /^[-_a-z0-9]$/i ) ) {

		/* text is id of another div to be inspected, most obviously */
		menu = document.getElementById( text );
		if ( !DynUI.isDOMElement( menu, 'DIV' ) || ( menu.style.display != 'none' ) )
			return false;


		if ( isDynamic )
			return DynUI.contextMenuDynamic( event, menu.firstChild.nodeValue );
		else
			return DynUI.contextMenuStatic( event, menu );

	} else
		/*
		 * text is considered to be descriptor of dynamic context menu
		 */

		return DynUI.contextMenuDynamic( event, text );

}


DynUI.contextMenuStatic = function( event, menu ) {

	if ( !DynUI.isDOMElement( menu ) )
		return false;


	return DynUI.contextMenuShow( event, menu, false );

}


DynUI.contextMenuDynamic = function( event, desc ) {

	if ( !desc || !desc.length )
		return false;

	desc = desc.split( "|" );


	var i;
	for ( i = 0; i < desc.length; i++ )
		desc[i] = DynUI.contextMenuParseDescriptor( desc[i], { "type": "text", "label": "", "ref": "" } );



	var box = document.createElement( "div" );

	box.className = "context-menu";

	/* make element being invisibly rendered somewhere out of the visible area,
	   required to automatically get width of menubox (MSIE would extend it
	   to the right border of parent element, otherwise :-( ! ) */
	box.style.position   = "absolute";
	box.style.top        = "-1000px";
	box.style.visibility = "hidden";
	box.style.display    = "block";


	var currentWidth = 0;
	var entry, span, label, id;
	var eventDesc, eventList;


	eventList = new Array();

	for ( i = 0; i < desc.length; i++ ) {

		if ( !desc[i] || ( desc[i].length <= 0 ) )
			continue;
		if ( !desc[i]["type"] || ( desc[i]["type"].length <= 0 ) )
			continue;


		id = desc[i]["id"];
		if ( !id || ( id.length <= 0 ) )
			id = "item-" + ( i + 1 );


		/*
		 * create new entry in menu
		 */

		entry = document.createElement( "div" );

		/* set ID */
		entry.id = id;

		/* set class, use different class on disabled entries */
		if ( desc[i]["disabled"] )
			entry.className = desc[i]["type"] + "-disabled";
		else
			entry.className = desc[i]["type"];


		// span tag is required to get width of rendered text/image below */
		span = document.createElement( "span" );

		/* set visual content of entry */
		switch ( desc[i]["type"] ) {

			case "text" :
				label = document.createTextNode( desc[i]["label"] );
				span.appendChild( label );
				break;

			case "image" :
				break;

			case "separator" :
				break;

		}

		entry.appendChild( span );

		/*
		 * check whether entry's width exceeds current width of box or not
		 */

		if ( span.offsetWidth > currentWidth )
			currentWidth = span.offsetWidth;

		/*
		 * on the other hand that span interferes with rollover effect, so
		 * link text to entry's div directly ...
		 */

		entry.removeChild( span );
		entry.appendChild( label );





		/* prepare entry's click handler */
		eventDesc = new Array();

		if ( desc[i]["type"] != 'separator' ) {
			eventDesc["handler"] = desc[i]["handler"] ? desc[i]["handler"] : false;
			eventDesc["href"]    = desc[i]["href"]    ? desc[i]["href"]    : false;
			eventDesc["submit"]  = desc[i]["submit"]  ? desc[i]["submit"]  : false;
		}

		eventList[id] = eventDesc;

		entry.onclick = DynUI.contextMenuClick;


		/* set handler providing rollover effect */
		if ( !desc[i]["disabled"] && ( desc[i]["type"] != "separator" ) ) {
			entry.onmouseover = DynUI.contextMenuRO;
			entry.onmouseout  = DynUI.contextMenuN;
		}


		/* finally add entry to menu box */
		box.appendChild( entry );

	}


	if ( currentWidth < 100 )
		currentWidth = 100;

	box.style.width = currentWidth + "px";

	if ( MSIE )
		for ( i = 0; i < box.childNodes.length; i++ )
			box.childNodes[i].style.width = currentWidth + "px";


	/* remove invisibly rendered menu box from page again */
	box.style.display    = "none";
	box.style.visibility = "visible";


	document.getElementsByTagName( "BODY" )[0].appendChild( box );



	/* invoke generic context menu handler */
	return DynUI.contextMenuShow( event, box, eventList );

}


DynUI.contextMenuParseDescriptor = function( desc, defs ) {

	if ( ( typeof desc != "string" ) || ( desc.length <= 0 ) )
		return false;


	desc = desc.split( ";" );

	var i, j, name, value;

	for ( i = 0; i < desc.length; i++ ) {

		if ( desc[i].length <= 0 )
			continue;

		j = desc[i].indexOf( "=" );
		if ( j == 0 )
			continue;

		if ( j > 0 ) {

			name  = desc[i].substring( 0, j ).toLowerCase();
			value = desc[i].substring( j+1, desc[i].length );

		} else {

			name  = desc[i].toLowerCase;
			value = true;

		}


		defs[name] = value;

	}


	return defs;

}


DynUI.contextMenuShow = function( event, menu, eventList ) {

	var c, wc;

	c = new Array();
	c[0] = event.clientX;
	c[1] = event.clientY;


	wc = DynUI.getWindowDimensions();
	if ( !wc || ( wc.length < 4 ) )
		return false;

	c[0] += wc[0];
	c[1] += wc[1];


	c = DynUI.fitInWindowRelated( c, menu, -5 );
	if ( !c || ( c.length <= 0 ) )
		return false;


	/*
	 * remove any existing context menu
	 */

	if ( DynUI.currentContextMenu != false )
		DynUI.contextMenuClose( false );



	/*
	 * display menu box
	 */

	menu.style.position = "absolute";
	menu.style.left     = c[0] + "px";
	menu.style.top      = c[1] + "px";
	menu.style.display  = "block";


	/*
	 * register new current context menu and enable event handlers
	 */

	DynUI.currentContextMenu          = menu;
	DynUI.currentContextMenuIsDynamic = eventList ? true : false;
	if ( DynUI.currentContextMenuIsDynamic )
		DynUI.currentContextMenuEventList = eventList;
	else
		DynUI.currentContextMenuEventList = new Array();
	DynUI.mouseIsOverContextMenu      = false;

	menu.onmouseover = DynUI.contextMenuEnter;
	menu.onmouseout  = DynUI.contextMenuLeave;


	return false;

}

DynUI.contextMenuEnter = function( event ) {
	DynUI.mouseIsOverContextMenu = true;
}

DynUI.contextMenuLeave = function( event ) {
	DynUI.mouseIsOverContextMenu = false;
}

DynUI.contextMenuClose = function( event ) {

	var menu;


	if ( typeof event == 'boolean' )

		/* provided event is "false" in fact, so drop any 
		 * currently visible context menu ... */
		menu = DynUI.currentContextMenu;

	else {

		if ( !event )
			event = window.event;

		if ( event.target )
			menu = event.target;
		else
			menu = event.srcElement;

	}

	if ( !menu || !menu.parentNode )
		return true;



	/* hide menu, drop event handler */
	menu.style.display = "none";
	menu.onmouseout = null;


	/* if menu was created dynamically, drop it */
	if ( DynUI.currentContextMenu == menu ) {

		if ( DynUI.currentContextMenuIsDynamic ) {

			var parent, i;

			parent = menu.parentNode;
			if ( parent )
				parent.removeChild( menu );

		}


		DynUI.currentContextMenu = false;
		DynUI.currentContextMenuIsDynamic = false;

	}



	return true;

}


DynUI.contextMenuRO = function( event ) {

	if ( !DynUI.isEventType( event, "mouseover" ) )
		return true;

	var entry = DynUI.getEventTarget( event );
	if ( !entry || !entry.parentNode )
		return true;



	if ( ( entry.className != "text" ) && ( entry.className != "image" ) )
		return true;

	entry.className += "-ro";


	return true;

}


DynUI.contextMenuN = function( event ) {

	if ( !DynUI.isEventType( event, "mouseout" ) )
		return true;

	var entry = DynUI.getEventTarget( event );
	if ( !entry || !entry.parentNode )
		return;



	if ( ( entry.className != "text-ro" ) && ( entry.className != "image-ro" ) )
		return;

	entry.className = entry.className.substring( 0, entry.className.length - 3 );


	return;

}



DynUI.contextMenuClick = function( event ) {

	var entry = DynUI.getEventTarget( event );
	if ( !entry || !entry.parentNode )
		return true;


	var submit  = false;
	var handler = false;
	var reference = false;


	if ( DynUI.currentContextMenuEventList && DynUI.currentContextMenuEventList[entry.id] ) {

		submit    = DynUI.currentContextMenuEventList[entry.id]["submit"];
		handler   = DynUI.currentContextMenuEventList[entry.id]["handler"];
		reference = DynUI.currentContextMenuEventList[entry.id]["href"];

	}


	return DynUI.contextMenuClickProcessing( submit, handler, reference );

}

DynUI.contextMenuClickProcessing = function( submit, handler, reference ) {

	if ( typeof submit == "string" ) {

		if ( handler && ( handler.length > 0 ) )
			submit = eval( handler + "( '" + submit + "' )" );

		if ( typeof submit == "string" ) {

			var i, j, name;


			if ( submit.length > 0 ) {

				i = submit.indexOf( ":" );
				if ( i > 0 ) {
					name   = submit.substring( 0, i );
					submit = submit.substring( i+1, submit.length );
				} else
					name = 0;	/* use first form of page by default */


				if ( document.forms[name] ) {

					/* now process any left assignments on existing form */
					submit = submit.split( "," );
					for ( i = 1; i < submit.length; i++ ) {

						j = submit[i].indexOf( "=" );
						if ( j <= 0 )
							continue;

						document.forms[name].elements[submit[i].substring( 0, j )] =
										submit[i].substring( j+1, submit[i].length );

					}
				}
			} else
				name = 0;


			if ( document.forms[name] )
				document.forms[name].submit();

		}
	} else if ( typeof reference == "string" ) {

		if ( handler && ( handler.length > 0 ) )
			reference = eval( handler + "( '" + reference + "' )" );

		if ( ( typeof reference == "string" ) && ( reference.length > 0 ) )
			location.href = reference;

	} else if ( handler )
		eval( handler + "( false )" );


	DynUI.contextMenuClose( false );
	return false;

}







/*
 * Managing some kind of dynamic views ... "loading"-screens and similar
 */


DynUI.hidePage = function( unhide ) {

	var body, i;


	body = document.getElementsByTagName( "body" );
	if ( !body || !body.length || !body[0] || !body[0].parentNode )
		return false;


	for ( i = 0; i < body[0].childNodes.length; i++ )
		if ( body[0].childNodes[i].nodeType == 1 )
			body[0].childNodes[i].style.visibility = unhide ? "visible" : "hidden";
			/* don't adjust style "display" to support unhiding page afterwards */


	if ( !unhide )
		window.scrollTo( 0, 0 );


	return true;

}

DynUI.unhidePage = function() {
	return DynUI.hidePage( true );
}



DynUI.loadingScreenTimer = false;

DynUI.loadingScreen = function( className, href, formToSubmit, message ) {

	/*
	 * dynamically create new "loading screen" elements in background
	 */

	var box, title, bar, barfill;
	var text;


	box     = document.createElement( 'div' );
	title   = document.createElement( 'div' );
	bar     = document.createElement( 'div' );
	barfill = document.createElement( 'div' );

	if ( !box || !title || !bar || !barfill )
		return false;


	text = document.createTextNode( message ? message : "Loading ..." );

	if ( !text )
		return false;



	box.style.position   = "absolute";
	box.style.display    = "none";
	box.id               = "toxA.loading-screen-box";
	box.className        = className ? className : "loading-screen-box";


	/*
	 * derive size of box from current size of window
	 * but ensure minimal dimensions without exceeding
	 * visible area ...
	 */

	var coords = DynUI.getWindowDimensions();
	if ( !coords )
		return false;

	var t;

	t = Math.floor( coords[2] / 2 ); 	/* width */
	if ( t < 300 )
		t = 300;
	if ( t > coords[2] )
		t = coords[2];

	box.style.width = t + "px";
	box.style.left  = Math.floor( ( coords[2] - t ) / 2 ) + "px";

	t = Math.floor( coords[3] / 4 ); 	/* height */
	if ( t < 150 )
		t = 150;
	if ( t > coords[3] )
		t = coords[3];

	box.style.height = t + "px";
	box.style.top    = Math.floor( ( coords[3] - t ) / 2 ) + "px";



	title.className = "title";
	title.appendChild( text );

	barfill.className = "barfill";

	bar.className     = "bar";
	bar.appendChild( barfill );


	box.appendChild( title );
	box.appendChild( bar );



	if ( !DynUI.hidePage() )
		return false;


	var body;

	body = document.getElementsByTagName( "body" );
	if ( !body || !body.length || !body[0] || !body[0].parentNode )
		return false;


	body[0].appendChild( box );
	box.style.display = "block";


	/*
	 * start animation
	 */

	DynUI.loadingScreenTimer = setInterval( "DynUI.animateLoadingScreen();", 100 );


	return true;

}


DynUI.loadingScreenTitleFade      = 0;
DynUI.loadingScreenTitleFadeSpeed = 0.3;

DynUI.loadingScreenBarFillLeft    = 0;
DynUI.loadingScreenBarFillRight   = 0;
DynUI.loadingScreenBarFillMoveDir = +1;
DynUI.loadingScreenBarFillWidth   = 100;

DynUI.animateLoadingScreen = function() {

	var box = document.getElementById( "toxA.loading-screen-box" );

	if ( !box )
		return false;


	/* let title (message) flash smoothly */

	if ( MSIE )
		/* does not work in MSIE6 :-( */
		box.firstChild.style.filter  = "alpha(opacity=" + ( 50 + Math.floor( 50 * Math.cos( DynUI.loadingScreenTitleFade ) ) ) + ");";
	else
		box.firstChild.style.opacity = 0.5 + 0.5 * Math.cos( DynUI.loadingScreenTitleFade );

	DynUI.loadingScreenTitleFade += DynUI.loadingScreenTitleFadeSpeed;


	/* animate "process bar" below title */
	var wb = box.lastChild.offsetWidth;

	if ( wb ) {

		

	}
}










/*
 * Images Management (Rollover, Preloading, etc.)
 *
 *
 * NOTE on preloading!
 *
 * Browsers usually compute JS code before loading required images. Thus
 * any image selected for preloading here will be requested before an image
 * requested in an IMG-tag or similar. For increased load-up of your page
 * you should consider to include initially available images in preload request
 * here as well!
 */





function Images() {
}


Images.__preloaded = new Array();
Images.__selected  = new Array();
Images.__delay     = false;



/*
 * This method preloads a selected list of images, groups them and assigns
 * an internally usable state each.
 *
 * Each argument to this method describes one group of images. Each group
 * has an ID. Writing to an already existing group replaces previously
 * used states in that group.
 *
 * A group ID is an alphanumeric name.
 * A state is an alphanumeric name as well. If a state is omitted it defaults
 * to "default". Each descriptor may have one or more assignments.
 *
 * The full descriptor has the following form:
 *
 * groupID:state=URL;state=URL;state=URL
 *
 *
 * Provide as much descriptors as you like in call to Images.preload()!
 */

Images.preload = function() {

	if ( document.images ) {

		for ( i = 0; i < Images.preload.arguments.length; i++ )
			/* preload images delayed (use delay to get statically linked images/files in document first) */
			window.setTimeout( "Images.__preloadSingle( false, '" + Images.preload.arguments[i] + "' );", 500 );


		return true;

	}


	return false;

}


/*
 * Preloads images using a common base as URL.
 *
 * This method basically works like Images.preload(). So you should first
 * read any information provided on that. Here are the differences:
 *
 * preloadBased() takes a single URL-mask as first argument. The else arguments
 * are handled as described before on preload().
 * On parsing the provided descriptors each extracted URL is applied to the
 * URL-mask by replacing any occurence of %s in mask by a currently extracted URL.
 *
 * Use this method to reduce the size of file listing to be preloaded, e.g.
 *
 *   All files are retrieved from http://www.toxa.de/src/file.php?n=example.gif
 *   Then you may give
 *
 *     http://www.toxa.de/src/file.php?n=%s
 *
 *   for URL base and write descriptors like this:
 *
 *     button5:n=example.gif;selected=example_s.gif;...
 *
 */

Images.preloadBased = function() {

	if ( document.images ) {

		var i, a, base;


		a = Images.preloadBased.arguments;

		base = a[0];
		if ( ( typeof base != "string" ) || !base )
			return false;


		for ( i = 1; i < a.length; i++ )
			Images.__preloadSingle( base, a[i] );


		return true;

	}


	return false;

}


/**
 * Internally used backend to method preload() and preloadBased().
 */

Images.__preloadSingle = function( base, desc ) {

	var p, id, list, k, key, value, temp;


	/*
	 * expect group-ID being prefixed to current argument, e.g.
	 *
	 * button5:n=http://www. ...
	 *
	 * with button5 being a groupID.
	 */

	p = desc.indexOf( ":" );
	if ( p <= 0 )
		id = 'uncategorized';		// missing prefix
	else {

		id = desc.substring( 0, p ).toLowerCase();
		if ( ( id == 'http' ) || ( id == 'https' ) || ( id == 'ftp' ) || ( id == 'file' ) )
			id = 'uncategorized';	// invalid prefix, seems to be URL-scheme

		desc = desc.substring( p + 1, desc.length );

	}



	/*
	 * now split argument into semicolon-separated list of assignments
	 * giving a single state an image's source URL each, e.g.
	 *
	 * n=test.gif;selected=test_s.gif
	 */

	list = desc.split( ';' );
	for ( k = 0; k < list.length; k++ ) {


		/*
		 * extract elements of current assignment, use defaults
		 */

		p = list[k].indexOf( "=" );
		if ( p >= 0 ) {
	
			key   = list[k].substring( 0, p );
			value = list[k].substring( p + 1, list[k].length );

			temp = parseInt( key );
			if ( temp.toString() == key )
				key = temp;
			else if ( !key ) {
				if ( list.length > 1 )
					key = k;
				else
					key = "default";
			}

		} else {

			if ( list.length > 1 )
				key = k;
			else
				key = "default";

			value = list[k];
	
		}


		if ( base )
			value = base.replace( /%s/, value );


		/*
		 * create new image and make it retrieve given URL
		 */

		if ( !Images.__preloaded[id] )
			Images.__preloaded[id] = new Array();

		Images.__preloaded[id][key]        = new Image;
		Images.__preloaded[id][key].src    = value;
		Images.__preloaded[id][key].onload = function() { this.cached = true; }

	}


	return true;

}



/**
 * Selects one or more images to swap with current ones.
 *
 * This method takes a variable-length argument list, where two arguments
 * describe a single selection. The first of each pair selects the element
 * in document, the second describes its new image to be swapped with
 * original one. If number of arguments is not even, the last argument gets
 * ignored.
 *
 * An element is either given directly, or selected by its name (form elements)
 * or ID.
 *
 * The selected image is given by its URL to fetch it from. Alternatively you
 * can use group ID and optional state as given in call to method preload()
 * or preloadBased(). E.g., if second argument in a pair is
 *
 *   button5.selected
 *
 * then new source of image becomes files described accordingly before on preload.
 * See methods preload() and preloadBased() to read more on this.
 *
 * Each selected image is prepared for restoration by a call to restore() or
 * restoreAll().
 */

Images.select = function() {

	var i, j, e, a, temp, pattern = /^([-_a-z0-9]+)(\\.([-_a-z0-9]+))?$/i;


	a = Images.select.arguments;

	for ( i = 0; i < ( a.length - 1 ); i += 2 ) {

		e = Images.getElement( document, a[i] );
		if ( DynUI.isDOMElement( e ) ) {

			// mark current element to be selected, used for restoring
			Images.__selected[Images.__wasSelected( e )] = e;

			// make backup of original src
			if ( !e.originalSrc )
				e.originalSrc = e.src;


			// check for type of selection
			if ( !pattern.exec( a[i+1] ) )
				e.src = a[i+1];
			else {

				if ( RegExp.$2 ) {

					// prefer using numeric as integer over string
					temp = parseInt( RegExp.$3 );
					if ( temp.toString() != RegExp.$3 )
						temp = RegExp.$3;


					try {
						e.src = Images.__preloaded[RegExp.$1][temp].src;
					} catch ( e ) {
						e.src = e.originalSrc;
					}

				} else {
					try {
						e.src = Images.__preloaded[RegExp.$1]['default'].src;
					} catch ( e ) {
						e.src = e.originalSrc;
					}
				}
			}
		}
	}


	return true;

}


Images.__wasSelected = function( e ) {

	var i;


	for ( i = 0; i < Images.__selected.length; i++ )
		if ( Images.__selected[i] == e )
			return i;


	return i;

}


Images.restore = function() {

	var a, i, j;


	a = Images.restore.arguments;

	for ( i = 0; i < a.length; i++ ) {

		a[i] = Images.getElement( document, a[i] );
		if ( !a[i] )
			continue;


		for ( j = 0; j < Images.__selected.length; j++ )
			if ( a[i] == Images.__selected[j] )
				break;

		if ( j < Images.__selected.length ) {

			// given image is in list of previously selected ones

			// restore URL
			Images.__selected[i].src = Images.__selected[i].originalSrc;


			// remove from list of previously selected ones
			Images.__selected.splice( j, 1 );

		}
	}


	return true;

}


Images.restoreAll = function() {

	var i;


	for ( i = 0; i < Images.__selected.length; i++ )
		Images.__selected[i].src = Images.__selected[i].originalSrc;


	Images.__selected = new Array();


	return true;

}


Images.getElement = function( doc, element ) {

	if ( DynUI.isDOMElement( element ) )
		return element;
	if ( ( typeof element != "string" ) || !element )
		return false;


	var p, i, e;


	if ( !doc )
		doc = document;

	p = element.indexOf( '.' );
	if ( p > 0 ) {
		doc     = parent.frames[element.substring( 0, p )].document;
		element = element.substring( p + 1 );
	}


	for ( i = 0; i < doc.forms.length; i++ )
		if ( doc.forms[i][element] )
			return doc.forms[i][element];

	if ( doc.getElementById )
		return doc.getElementById( element );


	return false;

}







/**
 * Swaps image on hover and keeps track of swapping back when mouse
 * leaves given element.
 *
 * swap is passed to Images.select(), so read there for more on
 * how to select preloaded images etc.
 */

Images.hover = function( element, swap ) {

	element = Images.getElement( document, element );
	if ( !DynUI.isDOMElement( element ) )
		return false;


	if ( !Images.select( element, swap ) )
		return false;


	element.onmouseout = Images.unhover;

}

Images.unhover = function( event ) {

	if ( !event )
		event = window.event;

	if ( event ) {

		if ( event.target ) {
			Images.restore( event.target );
			event.target.onmouseout = null;
		} else if ( event.srcElement ) {
			Images.restore( event.srcElement );
			event.srcElement.onmouseout = null;
		}

	}
}



/* EOF */