
/* - portal_skins/custom/sifr/dynamic-standard.js - */
var isIE6 = /MSIE ((5\.5)|[6])/.test(navigator.userAgent) &&
				navigator.platform == "Win32";




// Animation class
//
// requires dom.js

// Initialise the animation
//
// o = Object to animate, Array for multiple objects
// t = (string) Animation type: 'width', 'height', 'left', 'top', 'opacity', 'font-size', 'color' (special case)
// s, e = (number)
//
function Animation(o, t, s, e)
{
	var animType = "";
	var startVal = 0;
	var endVal = 0;
	var duration = 1000;
	var fps = 25;
	var moveType = "smooth";
	var moveVal = 0;
	var finishedHandler = undefined;

	// Runtime variables:
	var currVal = 0;
	var timePassed = 0;
	var timeoutHandle = undefined;
	var playingVar = false;
	var me = this;

	if (isString(o)) o = elId(o);

	if (!isSet(s) && !isArray(o))   // Relative movement doesn't work with arrays (yet?)
	{
		if (t == "width") s = DOM.width(o);
		if (t == "height") s = DOM.height(o);
		if (t == "left") s = DOM.left(o);
		if (t == "top") s = DOM.top(o);
		if (t == "opacity") s = DOM.getOpacity(o);
		if (t == "font-size") s = DOM.getEffectiveStyle(o, "font-size");   // TODO: unit?
		e += s;   // End value is relative to dynamic start value
	}
	animType = t;
	startVal = s;
	endVal = e;

	// ti = (int) Total animation time in milliseconds
	//
	this.setDuration = function(d)
	{
		duration = d;
	};

	this.setFPS = function(f)
	{
		fps = f;
	};

	// t = (string) Movement type: "smooth", "exp", "linear" (= "exp", 0)
	// v = (number) Curve parameter [optional if unused by specified type]
	//
	// TODO. now "smooth" is a constant sine movement
	// "smooth": uses a sine function
	//           0 is no acceleration (linear movement).
	//           positive values cause slow movement at start and end, fast in the middle
	//           negative values reverse this
	//
	// "exp": negative values reverse the acceleration.
	//        0 is no acceleration (linear movement).
	//        x is a 2^x acceleration
	//
	this.setMovement = function(t, v)
	{
		moveType = t;
		moveVal = v;
	};

	this.setFinishedHandler = function(f)
	{
		finishedHandler = f;
	};

	this.play = function()
	{
		currVal = startVal;

		if (duration <= 0) return false;
		if (fps < 1) return false;

		playingVar = true;
		doAnimate();
	};

	// pause animation. play() can resume
	this.halt = function()
	{
		clearTimeout(timeoutHandle);
		playingVar = false;
	};

	// go to the start
	//
	this.reset = function()
	{
		timePassed = 0;
		doSet();
	};

	// go to the end
	//
	this.finish = function()
	{
		timePassed = duration;
		doSet();
	};

	this.playing = function()
	{
		return playingVar;
	};

	// returns 0...1 (current progress of animation)
	this.progress = function()
	{
		return (duration <= 0) ? 0 : timePassed / duration;
	};

	this.reverse = function()
	{
		// TODO: reverses animation so that play() will then do the same as playReverse() did before
	};

	function doSet()
	{
		var pos = 0;
		var time = timePassed / duration;
		if (moveType == "smooth")
		{
			// we use the sine function from 3pi/2 to 5pi/2
			// scale down linear time percentage from 0...1 to 3pi/2 to 5pi/2
			pos = Math.sin(1.5 * Math.PI + time * Math.PI);
			// translate sine output from -1...1 to 0...1
			pos = (pos + 1) / 2;
		}
		else if (moveType == "exp")
		{
			pos = Math.pow(time, Math.pow(2, moveVal));
		}
		else   // and "linear" is that case
		{
			pos = time;
		}

		var v = startVal + (endVal - startVal) * pos;

		var oArr = o;
		if (!isArray(o)) oArr = [o];
		oArr.each(function(_o) {
			if (animType == "width") _o.style.width = v + "px";
			if (animType == "height") _o.style.height = v + "px";
			if (animType == "left") _o.style.left = v + "px";
			if (animType == "top") _o.style.top = v + "px";
			if (animType == "opacity") DOM.setOpacity(_o, v);
			if (animType == "font-size") _o.style.fontSize = v + "px";   // TODO: unit?
		});
	};

	function doAnimate()
	{
		timePassed += 1000 / fps;
		if (timePassed >= duration)
		{
			timeoutHandle = undefined;
			me.finish();
			if (isFunction(finishedHandler)) finishedHandler(o);
			return;
		}

		doSet();

		timeoutHandle = setTimeout(doAnimate, 1000 / fps);
	};
}




// Substitute for document.getElementById(id)
//
function elId(id)
{
	return document.getElementById(id);
}

// Substitute for document.getElementsByTagName(name)
//
function elTag_(n)
{
	return document.getElementsByTagName(n);
}

// Find all nodes that have the given tag name
//
// n = (string) tag name
// node = (node) starting node for depth-first search. leave empty for a document-wide search
//
function elTag(n, node)
{
	if (!isSet(node)) node = document;
	if (isString(node)) node = elId(node);
	var r = new Array();
	for (var i = 0; i < node.childNodes.length; i++)
	{
		if (isSet(node.childNodes[i].tagName) && node.childNodes[i].tagName.toLowerCase() == n)
			r.push(node.childNodes[i]);
		if (node.childNodes[i].hasChildNodes())
			r = r.concat(elTag(n, node.childNodes[i]));
	}
	return r;
}

// Find all nodes that have the given class name set
//
// n = (string) class name
// node = (node) starting node for depth-first search. leave empty for a document-wide search
//
function elCls(n, node)
{
	if (!isSet(node)) node = document;
	if (isString(node)) node = elId(node);
	var r = new Array();
	for (var i = 0; i < node.childNodes.length; i++)
	{
		if (DOM.hasClass(node.childNodes[i], n))
			r.push(node.childNodes[i]);
		if (node.childNodes[i].hasChildNodes())
			r = r.concat(elCls(n, node.childNodes[i]));
	}
	return r;
}

// Find the document's body element
//
function elBody()
{
	return document.body || elTag("body")[0];
}

// DOM helper class
//
// requires runtime.js
//
function DOMClass()
{
	var pageLoaded = false;

	// ----- Element geometry functions -----

	// Determine upper border position of the object
	//
	this.top = function(o)
	{
		if (isString(o)) o = elId(o);
		if (!isSet(o)) return 0;
		var top = o.offsetTop;
		while (o = o.offsetParent) top += o.offsetTop;
		return top;
	};

	// Determine left border position of the object
	//
	this.left = function(o)
	{
		if (isString(o)) o = elId(o);
		if (!isSet(o)) return 0;
		var left = o.offsetLeft;
		while (o = o.offsetParent) left += o.offsetLeft;
		return left;
	};

	// Determine bottom border position of the object
	//
	this.bottom = function(o)
	{
		if (isString(o)) o = elId(o);
		return this.top(o) + this.height(o);
	};

	// Determine right border position of the object
	//
	this.right = function(o)
	{
		if (isString(o)) o = elId(o);
		return this.left(o) + this.width(o);
	};

	// Determine width of the object
	//
	this.width = function(o)
	{
		if (isString(o)) o = elId(o);
		if (!isSet(o)) return 0;
		return o.offsetWidth;
	};

	// Determine height of the object
	//
	this.height = function(o)
	{
		if (isString(o)) o = elId(o);
		if (!isSet(o)) return 0;
		return o.offsetHeight;
	};

	// Vertically resize an element
	//
	// cy = (int) Height offset
	// negative = (bool) Rezize to the top instead of the bottom. This is achieved by scrolling the window accordingly.
	//
	this.resizeVert = function(o, cy, negative)
	{
		if (isString(o)) o = elId(o);
		if (!isSet(o)) return;
		var newCy = o.clientHeight + cy;
		var minCy = 0;
		var maxCy = 1000;
		if (o.tagName.toLowerCase() == "textarea") minCy = 60;
		if (newCy < minCy) newCy = minCy;
		if (newCy > maxCy) newCy = maxCy;
		var diffY = newCy - o.clientHeight;
		o.style.height = newCy + "px";

		// scroll page in the opposite offset direction to make the box resize at the top
		if (negative == true)
			window.scrollBy(0, diffY);
	};

	// Move an object to a position
	//
	// x = (number) Horizontal position
	// y = (number) Vertical position
	//
	this.moveTo = function(o, x, y)
	{
		if (isString(o)) o = elId(o);
		if (!isSet(o)) return;
		o.style.left = x + "px";
		o.style.top = y + "px";
	};

	// Move an object by a number of pixels in X and Y direction
	//
	// dx = (number) Horizontal offset
	// dy = (number) Vertical offset
	//
	this.moveBy = function(o, dx, dy)
	{
		if (isString(o)) o = elId(o);
		if (!isSet(o)) return;
		o.style.left = (parseInt(o.style.left) + dx) + "px";
		o.style.top = (parseInt(o.style.top) + dy) + "px";
	};

	// Resize an object to a size
	//
	// w = (number) Horizontal size
	// h = (number) Vertical size
	//
	this.resizeTo = function(o, w, h)
	{
		if (isString(o)) o = elId(o);
		if (!isSet(o)) return;
		o.style.width = w + "px";
		o.style.height = h + "px";
	};

	// Resize an object by a number of pixels in X and Y direction
	//
	// dx = (number) Horizontal offset
	// dy = (number) Vertical offset
	//
	this.resizeBy = function(o, dx, dy)
	{
		if (isString(o)) o = elId(o);
		if (!isSet(o)) return;
		o.style.width = (parseInt(o.style.width) + dx) + "px";
		o.style.height = (parseInt(o.style.height) + dy) + "px";
	};

	// ----- Element state functions -----

	// Toggle visibility of an object
	//
	// state = (bool) true/false: Force to show/hide it [optional]
	//
	// Returns the new enabled state as -1 or +1
	//
	this.toggleVisible = function(o, state)
	{
		if (isString(o)) o = elId(o);
		if (!isSet(o)) return -1;
		if (state == null)
			newstate = (o.style.display == "") ? "none" : "";
		else
			newstate = state ? "" : "none";
		o.style.display = newstate;
		return (newstate == "" ? 1 : -1);
	};

	this.show = function(o)
	{
		return this.toggleVisible(o, true);
	};

	this.hide = function(o)
	{
		return this.toggleVisible(o, false);
	};

	this.visible = function(o)
	{
		if (isString(o)) o = elId(o);
		if (!isSet(o)) return false;
		return o.style.display != "none";
	};

	// Toggle enabled state of an object
	//
	// state = (bool) true/false: Force to enable/disable it [optional]
	//
	// Returns the new enabled state as boolean
	//
	this.toggleEnabled = function(o, state)
	{
		if (isString(o)) o = elId(o);
		if (!isSet(o)) return false;
		if (state == null)
			newstate = !o.disabled;
		else
			newstate = !state;
		o.disabled = newstate;
		return !newstate;
	};

	this.enable = function(o)
	{
		return this.toggleEnabled(o, true);
	};

	this.disable = function(o)
	{
		return this.toggleEnabled(o, false);
	};

	this.enabled = function(o)
	{
		if (isString(o)) o = elId(o);
		if (!isSet(o)) return false;
		return !o.disabled;
	};

	// Disable an object in 50ms
	//
	this.disableThen = function(o)
	{
		if (isString(o)) o = elId(o);
		window.setTimeout(function() { if (o) { o.blur(); o.disabled = true; } }, 50);
	};

	// Toggle selection state of a button element, using the style class name "selected"
	//
	// Returns the new selected state as boolean
	//
	this.toggleSelected = function(o)
	{
		if (isString(o)) o = elId(o);
		if (this.hasClass(o, "selected"))
		{
			this.removeClass(o, "selected");
			return false;
		}
		this.addClass(o, "selected");
		return true;
	};

	// ----- Window scrolling functions -----

	// Get the current horizontal scroll position
	//
	this.scrollX = function()
	{
		if (isSet(window.pageXOffset))
			return window.pageXOffset;
		if (isSet(document.documentElement.scrollLeft))
			return document.documentElement.scrollLeft;
		if (isSet(document.body.scrollLeft))
			return document.body.scrollLeft;
		return -1;

		/*if (document.all)
			if (!document.documentElement.scrollLeft)
				return document.body.scrollLeft;
			else
				return document.documentElement.scrollLeft;
		else
			return window.pageXOffset;*/
	};

	// Get the current vertical scroll position
	//
	this.scrollY = function()
	{
		if (isSet(window.pageYOffset))
			return window.pageYOffset;
		if (isSet(document.documentElement.scrollTop))
			return document.documentElement.scrollTop;
		if (isSet(document.body.scrollTop))
			return document.body.scrollTop;
		return -1;
	};

	// Scroll the window down by the height of an item
	//
	this.scrollDown = function(o)
	{
		if (isString(o)) o = elId(o);
		window.scrollBy(0, this.height(o));
	};

	// Scroll the window up by the height of an item
	//
	this.scrollUp = function(o)
	{
		if (isString(o)) o = elId(o);
		window.scrollBy(0, -this.height(o));
	};

	var scrollTimeout = false;
	var scrollOffset = 0;

	// Set the anchor scrolling vertical offset
	//
	this.setAnchorScrollOffset = function(y)
	{
		scrollOffset = y;
	};

	// Scroll the window to the URL anchor
	//
	// The anchor must be defined on the page like <a id="myAnchor" />
	//
	this.scrollToAnchor = function()
	{
		// disable a running timeout if we have loaded faster than that
		try
		{
			clearTimeout(scrollTimeout);
		}
		catch (ex)
		{
		}

		var url = window.location.href;
		var id = "";
		var id_pos = url.lastIndexOf("#");
		if (id_pos > 0)
			id = url.substring(id_pos + 1, url.length);
		else
			// this is the place to match a custom URL...
		if (id != "")
		{
			var o = elId(id);   // fetch the anchor element by its id attribute
			if (!isSet(o)) return;
			var top = this.top(o) - 7 + scrollOffset;
			window.scrollTo(0, top);
		}
	};

	// Scroll the window to the position of an object
	//
	this.scrollToObject = function(o)
	{
		if (isString(o)) o = elId(o);
		if (!isSet(o)) return;
		var top = this.top(o) - 7 + scrollOffset;
		window.scrollTo(0, top);
	};

	// Automatically scroll the window to the URL anchor
	//
	// See scrollToAnchor function for details.
	// Automatically handles the document "load" event and tries to scroll
	// after a second already in case the page has not finished loading by then.
	//
	this.autoScrollToAnchor = function()
	{
		this.addEvent(window, "load", this.scrollToAnchor);
		scrollTimeout = setTimeout(this.scrollToAnchor, 1000);
	};

	// Toggle visibility of an object and scroll the page up and down
	//
	this.toggleVisScroll = function(o)
	{
		if (isString(o)) o = elId(o);
		if (!isSet(o)) return;

		if (o.style.display == "")
		{
			window.scrollBy(0, -this.height(o));
			newstate = "none";
			o.style.display = newstate;
		}
		else
		{
			newstate = "";
			o.style.display = newstate;
			window.scrollBy(0, this.height(o));
		}
		return (newstate == "" ? 1 : -1);
	};

	// ----- Element content functions -----

	// Set an element's text or HTML content
	//
	this.setContent = function(o, cont)
	{
		if (isString(o)) o = elId(o);
		if (!isSet(o)) return;
		try
		{
			o.innerHTML = cont;
		}
		catch (ex)
		{
			errorMessage("Exception caught while setting node content:\n" + formatException(ex), "DOM.setContent");
		}
	};

	// Set an image's source attribute depending on a condition
	//
	// This condition can be the return value of toggleVisId, e.g.
	//
	// cond = (bool) Condition
	// trueimg = (string) True case image source
	// falseimg = (string) False case image source
	//
	this.setImageCond = function(o, cond, trueimg, falseimg)
	{
		if (isString(o)) o = elId(o);
		o.src = cond ? trueimg : falseimg;
	};

	// ----- Stylesheet functions -----

	// Is a style class name present? Also works with multiple class names
	//
	this.hasClass = function(o, n)
	{
		if (isString(o)) o = elId(o);
		if (!isSet(o)) return false;
		if (o.className == undefined) return false;
		var cn = o.className.split(" ");
		var req = n.split(" ");
		req = req.filter(function(x) { return !cn.contains(x); });
		return req.length == 0;
	};

	// Get all classes of an element
	//
	this.getClasses = function(o)
	{
		if (isString(o)) o = elId(o);
		if (!isSet(o)) return "";
		if (o.className == undefined) return "";
		return o.className.split(" ");
	};

	// Add a style class name. Also works with multiple class names
	//
	this.addClass = function(o, n)
	{
		if (isString(o)) o = elId(o);
		if (!isSet(o)) return;
		if (o.className == undefined) return;
		var cn = o.className.split(" ");
		var req = n.split(" ");
		req.each(function(x) { if (!cn.contains(x)) cn.push(x); });
		o.className = cn.join(" ");
	};

	// Remove a style class name
	//
	this.removeClass = function(o, n)
	{
		if (isString(o)) o = elId(o);
		if (!isSet(o)) return;
		if (o.className == undefined) return;
		var cn = o.className.split(" ");
		var req = n.split(" ");
		cn = cn.filter(function(x) { return !req.contains(x); });
		o.className = cn.join(" ");
	};

	// Add a document-wide style definition in a new <style> tag in the <head> area
	//
	this.addPageStyle = function(css)
	{
		var style = document.createElement("style");
		style.setAttribute("type", "text/css");
		try
		{
			//style.innerHTML = css;
			style.appendChild(document.createTextNode(css));   // TODO: either way, this makes IE cry silently
		}
		catch (ex)
		{
			errorMessage(formatException(ex), "DOM.addPageStyle");
		}
		elTag("head")[0].appendChild(style);
	};

	// Return the effective (computed) style attribute for an element
	//
	this.getEffectiveStyle = function(o, n)
	{
		if (isString(o)) o = elId(o);
		if (!isSet(o)) return undefined;
		if (document.defaultView && document.defaultView.getComputedStyle)
			return document.defaultView.getComputedStyle(o, "").getPropertyValue(n);
		if (o.currentStyle)
			return o.currentStyle[n];
		errorMessage("Browser does not support retrieval of effective style", "DOM.getEffectiveStyle");
		return false;
	};

	// Get the current opacity of an element (0.0 to 1.0)
	//
	this.getOpacity = function(o)
	{
		if (isString(o)) o = elId(o);
		if (!isSet(o)) return undefined;
		if (o.filters && o.filters.alpha && isNumber(o.filters.alpha.opacity))   // IE
			return o.filters.alpha.opacity / 100;
		if (isSet(o.style.opacity) && o.style.opacity != "")   // others (not Opera)
			return o.style.opacity;
		return 1;
	};

	// Set an element's opacity
	//
	// This feature isn't available in Opera 8.5
	// In Internet Explorer, this may remove other filters from the element (untested)
	//
	// opacity = (number) 0.0 (transparent) to 1.0 (opaque)
	//
	this.setOpacity = function(o, opacity)
	{
		if (isString(o)) o = elId(o);
		if (!isSet(o)) return;
		if (isSet(o.style.filter))   // IE
			o.style.filter = (opacity == 1) ? "" : "Alpha(opacity=" + (opacity * 100) + ")";
		else if (isSet(o.style.opacity))   // others (not Opera)
			o.style.opacity = opacity;
	};

	this.createBlendLayer = function(color, opacity)
	{
		var layer = document.createElement("div");
		
			
			 
			if (!isIE6) layer.style.position = "fixed";
			else
			layer.style.position = "absolute";
			
			
			layer.style.top = 0;
			layer.style.left = 0;
			layer.style.width = "100%";
			layer.style.height = "100%";
			layer.style.zIndex = 100;
		
		layer.style.backgroundColor = color;
		this.setOpacity(layer, opacity);
		elTag("body")[0].appendChild(layer);
		return layer;
	};

	// ----- General event handling functions -----

	// Attach an event to an element
	//
	// The event handler will be wrapped in a compatibility layer offering a
	// DOM-compatible event object as the handler's parameter. If the handler
	// returns false, the event will be cancelled.
	//
	// o = Element
	// n = Event name (DOM syntax, i.e. without "on"). The special name "domload" is called
	//     right after the DOM tree has loaded (in Firefox/Mozilla) and is mapped to "load"
	//     for other browsers.
	// handler = Event handler function
	//
	// Returns a handle to allow detaching the event again.
	//
	this.addEvent = function(o, n, handler)
	{
		var dom_this = this;

		var wrappedHandler = function(e)
		{
			// unify event object for DOM/IE
			e = dom_this.findEvent(e);
			var res;
			try
			{
				res = handler(e);
			}
			catch (ex)
			{
				errorMessage(formatException(ex), "Event handler function");
			}
			if (typeof res == "undefined")
				return true;
			if (!res)
			{
				// cancel event bubbling
				if (e.cancelBubble)
					e.cancelBubble = true;   // IE
				if (e.stopPropagation)
					e.stopPropagation();   // DOM2

				// prevent default action (doesn't work in Opera)
				try { e.preventDefault(); } catch (ex) { }   // DOM2
				return false;   // IE
			}
			return true;   // IE
		};

		var compatName = n;
		if (n == "domload")
		{
			n = "DOMContentLoaded";
			compatName = "load";
		}

		if (isString(o)) o = elId(o);
		if (!isSet(o)) return;
		if (o.addEventListener)
			o.addEventListener(n, wrappedHandler, false);   // DOM
		else if (o.attachEvent)
			o.attachEvent("on" + compatName, wrappedHandler);   // IE
		else
			o["on" + compatName] = wrappedHandler;   // IE, too?

		return {"o": o, "n": n, "cn": compatName, "f": wrappedHandler };
	};

	// Remove an event from an element
	//
	// h = Return value from previous addEvent call
	//
	this.removeEvent = function(h)
	{
		if (h.o.addEventListener)
			h.o.removeEventListener(h.n, h.f, false);   // DOM
		else if (h.o.attachEvent)
			h.o.detachEvent("on" + h.cn, h.f);   // IE
		else
			h.o["on" + h.cn] = undefined;   // IE, too? - Attention: This can only remove all events (?)
	};

	// Execute a function after the page has loaded or immediately if this is already the case
	//
	this.whenLoaded = function(fn)
	{
		if (pageLoaded)
			fn();
		else
			this.addEvent(window, "load", fn);
	};

	// Has the page loaded?
	//
	this.isLoaded = function(fn)
	{
		return pageLoaded;
	};

	// Find the event object (internal use)
	//
	this.findEvent = function(e)
	{
		if (!isSet(e)) e = window.event;   // IE
		if (!e.target && e.srcElement)   // IE
		{
			e.target = e.srcElement;
//			if (e.button)
//				if (e.button & 1)
//					e.button = 0;
//				else if (e.button & 2)
//					e.button = 2;
//				else if (e.button & 4)
//					e.button = 1;   // Fehler "Mitglied nicht gefunden" im IE6
		}
		return e;
	};

	// ----- Keyboard event handling functions -----

	// Attach a keyboard event to an element
	//
	// See addEvent for notes about the handler function.
	//
	// TODO: doesn't work in IE and I have no idea why
	//
	// keyName = (string) Key combination name
	// handler = Event handler function
	//
	this.addKeyEvent = function(keyName, handler)
	{
		var dom_this = this;
		// TODO: Test this replacement: "DOM" -> "dom_this" in this function (2x)

		this.addEvent(window, "keydown", function(e) {
			try
			{
				if (dom_this.isKeyName(e, keyName))
					return handler();
			}
			catch (ex)
			{
				errorMessage("Exception caught while processing the key event handler for " + keyName + ":\n" + formatException(ex));
			}
		} );
	};

	// Find the key code from an event object (internal use)
	//
	this.findKeyCode = function(e)
	{
		if (e.data) return e.data;   // W3C
		if (e.which) return e.which;   // Netscape
		return e.keyCode;   // IE, Firefox, etc
	};

	// Is the Alt modifier key pressed?
	//
	this.altPressed = function(e)
	{
		return e.altKey;
	};

	// Is the Control modifier key pressed?
	//
	this.ctrlPressed = function(e)
	{
		return e.ctrlKey;
	};

	// Is the Shift modifier key pressed?
	//
	this.shiftPressed = function(e)
	{
		return e.shiftKey;
	};

	// Convert a key name into its key code
	//
	this.keyCodeFromName = function(n)
	{
		for (var c in this.keyNames)
			if (this.keyNames[c] == n) return c;
		return 0;
	};

	// Convert a key code into its key name
	//
	this.keyNameFromCode = function(c)
	{
		if (this.keyNames.containsKey(c)) return this.keyNames[c];
		return "";
	};

	// Was the specified key combination pressed in this event?
	//
	this.isKeyName = function(e, n)
	{
		var modifier = false;
		var modAlt = false;
		var modCtrl = false;
		var modShift = false;
		do
		{
			modifier = false;
			if (n.startsWith("Alt+"))
			{
				modAlt = true;
				n = n.substr(4);
				modifier = true;
			}
			if (n.startsWith("Ctrl+"))
			{
				modCtrl = true;
				n = n.substr(5);
				modifier = true;
			}
			if (n.startsWith("Shift+"))
			{
				modShift = true;
				n = n.substr(6);
				modifier = true;
			}
		}
		while (modifier);
		if (modAlt ^ this.altPressed(e)) return false;
		if (modCtrl ^ this.ctrlPressed(e)) return false;
		if (modShift ^ this.shiftPressed(e)) return false;
		if (this.findKeyCode(e) != this.keyCodeFromName(n)) return false;
		return true;
	};

	// Compose a key combination name from the event to display it to the user
	//
	this.getKeyName = function(e)
	{
		var n = "";
		if (this.ctrlPressed(e)) n += "Ctrl+";
		if (this.shiftPressed(e)) n += "Shift+";
		var keyCode = this.findKeyCode(e);
		var singleName = this.keyNameFromCode(keyCode);
		if (singleName)
			n += singleName;
		else
			n += "<" + keyCode + ">";
		return n;
	};

	// Array of key names
	//
	this.keyNames =
	{
		8: "Backspace",
		9: "Tab",
		13: "Enter",
		16: "Shift",
		17: "Ctrl",
		18: "Alt",
		19: "Pause",
		20: "CapsLock",
		27: "Escape",
		32: "Space",
		33: "PageUp",
		34: "PageDown",
		35: "End",
		36: "Home",
		37: "Left",
		38: "Up",
		39: "Right",
		40: "Down",
		45: "Insert",
		46: "Delete",
		48: "0",
		49: "1",
		50: "2",
		51: "3",
		52: "4",
		53: "5",
		54: "6",
		55: "7",
		56: "8",
		57: "9",
		65: "A",
		66: "B",
		67: "C",
		68: "D",
		69: "E",
		70: "F",
		71: "G",
		72: "H",
		73: "I",
		74: "J",
		75: "K",
		76: "L",
		77: "M",
		78: "N",
		79: "O",
		80: "P",
		81: "Q",
		82: "R",
		83: "S",
		84: "T",
		85: "U",
		86: "V",
		87: "W",
		88: "X",
		89: "Y",
		90: "Z",
		91: "WinLeft",
		92: "WinRight",
		93: "AppMenu",
		96: "Num0",
		97: "Num1",
		98: "Num2",
		99: "Num3",
		100: "Num4",
		101: "Num5",
		102: "Num6",
		103: "Num7",
		104: "Num8",
		105: "Num9",
		106: "Multiply",
		107: "Add",
		109: "Subtract",
		111: "Divide",
		112: "F1",
		113: "F2",
		114: "F3",
		115: "F4",
		116: "F5",
		117: "F6",
		118: "F7",
		119: "F8",
		120: "F9",
		121: "F10",
		122: "F11",
		123: "F12",
		144: "NumLock",
		145: "ScrollLock",
		170: "Search",
		172: "Web",
		180: "Mail"
	};

	// ----- Miscellaneous functions -----

	// Ascend the DOM tree starting from obj to find a node of name
	//
	// n = (string) Tag name to search
	//
	this.findParentByName = function(o, n)
	{
		if (isString(o)) o = elId(o);
		if (!isSet(o)) return undefined;
		while (o.nodeName.toLowerCase() != n)
		{
			if (!o.parentNode) return undefined;
			o = o.parentNode;
		}
		return o;
	};

	// Find all of a node's children that match a given tag name
	//
	// n = (string) Tag name to search
	// rec = (bool) Search recursively? (default: false)
	//
	this.findChildrenByName = function(o, n, rec)
	{
		if (!isSet(rec)) rec = false;
		var r = new Array();
		if (isString(o)) o = elId(o);
		if (!isSet(o)) return r;
		for (var i = 0; i < o.childNodes.length; i++)
		{
			if (o.childNodes[i].nodeName.toLowerCase() == n)
				r.push(o.childNodes[i]);
			if (rec && isSet(o.childNodes[i].childNodes))
			{
				var x = this.findChildrenByName(o.childNodes[i], n, rec);
				if (x.length > 0) r = r.concat(x);
			}
		}
		return r;
	};

	// Ascend the DOM tree starting from obj to find a node with a class
	//
	// n = (string) Class name to search
	//
	this.findParentByClass = function(o, n)
	{
		if (isString(o)) o = elId(o);
		if (!isSet(o)) return undefined;
		while (!this.hasClass(o, n))
		{
			if (!o.parentNode) return undefined;
			o = o.parentNode;
		}
		return o;
	};

	// Find all of a node's children that match a given class name
	//
	// n = (string) Class name to search
	// rec = (bool) Search recursively? (default: false)
	//
	this.findChildrenByClass = function(o, n, rec)
	{
		if (!isSet(rec)) rec = false;
		var r = new Array();
		if (isString(o)) o = elId(o);
		if (!isSet(o)) return r;
		for (var i = 0; i < o.childNodes.length; i++)
		{
			if (this.hasClass(o.childNodes[i], n))
				r.push(o.childNodes[i]);
			if (rec && isSet(o.childNodes[i].childNodes))
			{
				var x = this.findChildrenByClass(o.childNodes[i], n, rec);
				if (x.length > 0) r = r.concat(x);
			}
		}
		return r;
	};

	// Open a new compact popup window
	//
	// url = (string) URL to open
	// w = (int) Width in pixels
	// h = (int) Height in pixels
	//
	this.popupWindow = function(url, w, h)
	{
		window.open(url, "", "width=" + w + ", height=" + h + ", resizable=yes, scrollbars=yes");
	};

	// Initialisation code
	this.whenLoaded(function() { pageLoaded = true; });
}

var DOM = new DOMClass();


var runtimeConfigStore = function() { };

setErrorMessages(true);

// Turn on/off displaying of error messages through the errorMessage function
//
function setErrorMessages(show)
{
	setConfigValue('showErrorMessages', show ? true : false);
	setConfigValue('showErrorMessages.counter', 0);
}

// Report an error message
//
function errorMessage(msg, context)
{
	if (getConfigValue('showErrorMessages', false) && msg != "")
	{
		// Only display up to 3 error messages simultaneously
		if (getConfigValue('showErrorMessages.counter', 0) >= 3) return;

		if (!isSet(context)) context = "";
		var AFn = "";
		for (var i = 0; i < 9 + (context.length * 0.85); i++) AFn += String.fromCharCode(0xAF);
		setConfigValue('showErrorMessages.counter', getConfigValue('showErrorMessages.counter', 0) + 1);
		alert((context ? "Context: " + context + "\n" + AFn + "\n" : "") + msg);
		setConfigValue('showErrorMessages.counter', getConfigValue('showErrorMessages.counter', 0) - 1);
	}
}

// Format an exception object into a readable string
//
function formatException(ex)
{
	try
	{
		var nbsp = String.fromCharCode(160);
		var nbsp8 = nbsp + nbsp + nbsp + nbsp + nbsp + nbsp + nbsp + nbsp;
		var msg = "";
		msg += "Exception: " + ex.name + "\n";
		msg += "Message: " + ex.message + "\n";
		if (ex.fileName)
			msg += "Code: " + ex.fileName + ":" + ex.lineNumber + "\n";
		if (ex.stack)
			msg += "Stack:\n" + nbsp8 + ex.stack.trim().replace(/\r/g, "").replace(/^((?:.*?\n){25}).*$/, "$1").replace(/(^|\n)(.{150}).*?(?=\n|$)/g, "$1$2").replace(/\n/g, "\n" + nbsp8) + "\n";
		return msg;
	}
	catch (ex2)
	{
		if (ex2.stack)
			return "Error formatting exception: " + ex2 + "\n\nInner exception: " + ex;
		else
			return "Error formatting exception:\n" + inspectObject(ex2, false, true) + "\nInner exception:\n" + inspectObject(ex, false, true);
	}
}

function isSet(o)
{
	return typeof o != "undefined" && o != null;
}

function isString(o)
{
	return typeof o == "string";
}

function isObject(o)
{
	return typeof o == "object";
}

// Warning: Doesn't work with associative arrays as these are no real Array objects
//
function isArray(o)
{
	return typeof o == "object" && o instanceof Array;
}

function isFunction(o)
{
	return typeof o == "function";
}

function isNumber(o)
{
	return typeof o == "number";
}

function isBoolean(o)
{
	return typeof o == "boolean";
}

// TODO: isNumeric

function isEven(x)
{
	return isNumber(x) && x % 2 == 0;
}

function isOdd(x)
{
	return isNumber(x) && x % 2 == 1;
}

function isTrue(x)
{
	if (!isSet(x)) return false;
	if (isString(x)) return x.trim() != "" && x.trim() != "0" && x.trim().toLowerCase() != "false";
	if (isObject(x)) return x != null;
	if (isArray(x)) return x.length > 0;
	if (isNumber(x)) return x != 0;
	if (isBoolean(x)) return x;
	return false;
}

// Include another JavaScript source file
//
// Be sure to wait a little while until the browser has loaded the script file.
//
function includeSource(url)
{
	var script = document.createElement("script");
	script.setAttribute("type", "text/javascript");
	script.setAttribute("src", url);
	document.getElementsByTagName("head")[0].appendChild(script);
}
// TODO: Use a synchronous HTTPRequest on the js file and eval() the returned code
//       see webrequest.js
//
// Evaluate new JavaScript code, probably received through an HTTP request
//
function evalCode(code)
{
	try
	{
		eval(code);
	}
	catch (ex)
	{
		errorMessage("Exception caught while eval'ing code:\n" + formatException(ex), "evalCode");
	}
}

// Call a function delayed
//
// f = (function) Cannot be an anonymous inline function to use the clr parameter
// t = (int) delay time in milliseconds
// clr = (bool) cancel a previously scheduled call of this very function
//
function callDelayed(f, t, clr)
{
	if (!this.memory) this.memory = new Object();

	var id = this.memory.findValue(f);
	if (clr && id)
	{
		clearTimeout(id);
		this.memory[id] = undefined;
	}

	id = window.setTimeout(f, t);
	this.memory[id] = f;
}

// Get local offset from UTC in minutes
//
function getLocalTimezone()
{
	var localclock = new Date();
	return -localclock.getTimezoneOffset();
}

// Cut off the filename from a URL to get its domain and path only
//
// Keeps the trailing slash (/) at the path value.
//
function getURLDirectoryName(url)
{
	return url.replace(/([^/]*)$/, "");
}

// Cut off the domain and path from a URL to get its filename only
//
function getURLFileName(url)
{
	return url.replace(/^(.*\/)([^/]*)$/, "$2");
}

// Format a decimal number
//
// num = (number) Number to format
// dec = (int) Number of decimals to display
//
function formatNumber(num, dec)
{
	if (isNaN(num)) return num;
	if (!isSet(dec)) dec = 0;
	var factor = Math.pow(10, dec);
	num = Math.round(num * factor) / factor;
	var str = num + "";
	if (dec > 0)
	{
		if (str.indexOf(".") == -1) str += ".";
		while (str.length - str.indexOf(".") - 1 < dec) str += "0";
	}
	return str.replace(/\./, ",");
}

// Store a configuration value in the local cache
//
// This mechanism is used by other modules that can be configured.
//
function setConfigValue(name, value)
{
	runtimeConfigStore[name] = value;
}

// Retrieve a configuration value from the local cache
//
function getConfigValue(name, def)
{
	var value = runtimeConfigStore[name];
	if (typeof value == "undefined") value = def;
	return value;
}

// Test if the array contains a given value
//
Array.prototype.contains = function(o)
{
	for (var i = 0; i < this.length; i++)
		if (this[i] == o) return true;
	return false;
};

// Apply a function on each array element, without altering the array
//
Array.prototype.each = function(f)
{
	for (var i = 0; i < this.length; i++)
		f(this[i]);
};

// Apply a function on each array element and save its result back to the array element
//
Array.prototype.map = function(f)
{
	for (var i = 0; i < this.length; i++)
		this[i] = f(this[i]);
};

// Filter array elements by a custom function and return the filtered array
//
Array.prototype.filter = function(f)
{
	var out = new Array();
	for (var i = 0; i < this.length; i++)
		if (f(this[i], i)) out.push(this[i]);
	return out;
};

// Remove an array element
//
Array.prototype.remove = function(o)
{
	return this.filter(function(x) { return x != o });
};

// Remove an array element by its position
//
Array.prototype.removeAt = function(n)
{
	return this.filter(function(x, i) { return n != i });
};

// Test if the object contains a given key
//
Object.prototype.containsKey = function(o)
{
	for (var prop in this)
		if (prop == o) return true;
	return false;
};

// Find an object's property name that has the given value
//
Object.prototype.findValue = function(o)
{
	for (var prop in this)
		if (this[prop] == o) return prop;
	return false;
};

// Test if the object contains a given value in one of its properties
//
Object.prototype.containsValue = function(o)
{
	 //return this.findValue(o) ? true : false;
};

// Test if the string contains a given string
//
String.prototype.contains = function(str)
{
	return this.indexOf(str) != -1;
};

// Trim all whitespace from the beginning and end of the string
//
String.prototype.trim = function()
{
	return this.replace(/\s+$/, "").replace(/^\s+/, "");
	//return this.replace(/[ \t\r\n]*$/, "").replace(/^[ \t\r\n]*/, "");
};

// Test if the string begins with a given string
//
String.prototype.startsWith = function(str)
{
	if (!isString(str)) return false;
	return this.substr(0, str.length) == str;
};

// Test if the string ends with a given string
//
String.prototype.endsWith = function(str)
{
	if (!isString(str)) return false;
	return this.substr(this.length - str.length, str.length) == str;
};

// Pad a string to a minimum length
//
// minLength = (int) minimum length
// ch = (string) character/string to add. Default is " " (space)
// atBegin = (bool) pad at the beginning (left padding). Default is at the end (right padding)
//
String.prototype.pad = function(minLength, ch, atBegin)
{
	var out = this;
	if (!isSet(ch)) ch = " ";
	while (out.length < minLength)
		if (atBegin)
			out = ch + out;
		else
			out += ch;
	return out;
};

// Escape special characters for XML (and HTML...)
//
String.prototype.escapeXml = function()
{
	return this.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
};

// TODO: add escaping function for regexps?

// Repeat a string n times
//
// Usage like: "hello".repeat(5)
//
String.prototype.repeat = function(n)
{
	var a = "";
	while (n--) a += this;
	return a;
};

// Read a single cookie
//
function getCookie(n)
{
	var vars = document.cookie.split(";");
	for (var i = 0; i < vars.length; i++)
	{
		var parts = vars[i].trim().split("=");
		if (parts[0] == n)
			return decodeURIComponent(parts[1].replace(/\+/, " "));
	}
	return undefined;
}

// Set a cookie
//
// n = (string) cookie name
// v = (string) cookie data
// lifetime = (int) cookie life time, in days. 0 or unset for session cookies
// path = (string) path to set cookie for. current path by default
//
function setCookie(n, v, lifetime, path)
{
	var addStr = "";
	if (lifetime)
	{
		var time = new Date();
		time.setTime(time.getTime() + lifetime * 86400 * 1000);
		addStr += "; expires=" + time.toGMTString();
	}
	if (path)
	{
		addStr += "; path=" + path;
	}
	document.cookie = n + "=" + encodeURIComponent(v) + addStr;
}





function ShowPicture(file, w, h, shadow)
{
	if (!isSet(shadow)) shadow = true;

	var layer = DOM.createBlendLayer("black", 0);
	if (!isSet(layer)) return true;

	var x = DOM.width(layer) / 2 - w / 2;
	var y = DOM.height(layer) / 2 - h / 2;
	if (w <= 0 || h <= 0) return true;

	var picture = document.createElement("img");
	
	
	if (!isIE6) picture.style.position = "fixed";
	else
	picture.style.position = "absolute";
	
	picture.style.top = (y - 10) + "px";
	picture.style.left = x + "px";
	picture.style.width = w + "px";	
	//picture.style.height = h + "px";
	picture.style.zIndex = 110;
	if (!shadow)
		picture.style.border = "solid 1px white";
	picture.setAttribute("src", file);
	DOM.hide(picture);
	//DOM.setOpacity(picture, 0);
	elBody().appendChild(picture);

	var div = document.createElement("div");
	
	
	if (!isIE6) div.style.position = "fixed";
	else
	div.style.position = "absolute";
	
	div.style.top = (y + h - (shadow ? 25 : 0)) + "px";
	div.style.left = "0px";
	div.style.width = "100%";
	div.style.textAlign = "center";
	div.style.color = "white";
	div.style.fontSize = "1.3em";
	div.style.zIndex = 112;
	div.appendChild(document.createTextNode(getConfigValue("clicktoclosetext", "Click or press Esc to close")));
	DOM.hide(div);
	//DOM.setOpacity(div, 0);
	elBody().appendChild(div);

	var finishHandler = function(e)
	{
		DOM.show(picture);
		DOM.show(div);
	}
	
	var anim1 = new Animation(layer, "opacity", 0, 0.8);
	anim1.setDuration(100);
	anim1.setMovement("smooth", 1);
	anim1.setFinishedHandler(finishHandler);

	//var anim1a = new Animation([picture, div], "opacity", 0, 1);
	//anim1a.setDuration(100);
	//anim1a.setMovement("smooth", 1);

	var clickHandler = function(e)
	{
		if (!DOM.visible(div)) return;   // Nothing to do here

		anim1.halt();
		//anim1a.halt();

		DOM.hide(div);
		elBody().removeChild(picture);
		elBody().removeChild(div);

		var finishHandler2 = function(e)
		{
			elBody().removeChild(layer);
			//elBody().removeChild(picture);
			//elBody().removeChild(div);
		}
		var anim2 = new Animation(layer, "opacity", 0.6, 0);
		anim2.setDuration(100);
		anim2.setMovement("smooth", 1);
		anim2.setFinishedHandler(finishHandler2);
		anim2.play();

		//var anim2a = new Animation([picture, div], "opacity", 1, 0);
		//anim2a.setDuration(100);
		//anim2a.setMovement("smooth", 1);
		//anim2a.play();
	}
	DOM.addEvent(layer, "click", clickHandler);
	DOM.addEvent(picture, "click", clickHandler);
	DOM.addEvent(div, "click", clickHandler);
	DOM.addKeyEvent("Escape", clickHandler);

	anim1.play();
	//anim1a.play();

	//return false;
}

function MessageBox(msg)
{
	var dark = false;

	var layer = DOM.createBlendLayer(dark ? "black" : "white", 0);
	if (!isSet(layer))
	{
		alert(msg);
		return;
	}

	var x = DOM.width(layer) / 2 * 0.5;
	var y = DOM.height(layer) / 2 * 0.8;
	var w = DOM.width(layer) * 0.4;

	msg += "<br /><br /><small>" + getConfigValue("clicktoclosetext", "Click or press Esc to close") + "</small>";

	var div = document.createElement("div");
	//div.setAttribute("style", "position: fixed; top: " + y + "px; left: " + x + "px; width: " + w + "px; text-align: center; background: #F8EEEE; padding: 1em 1em; border: solid 1px #CC0000; color: black; white-space: pre;");
	div.style.position = "fixed";
	div.style.top = y + "px";
	div.style.left = x + "px";
	div.style.width = w + "px";
	div.style.textAlign = "center";
	div.style.background = "#F8EEEE";
	div.style.padding = "1em 1em";
	div.style.border = "solid 1px #CC0000";
	div.style.color = "black";	
	div.style.zIndex = 110;
	//div.style.whiteSpace = "pre";
	//div.appendChild(document.createTextNode(msg));
	div.appendChild(document.createTextNode(""));
	div.innerHTML = msg;

	//DOM.hide(div);
	DOM.setOpacity(div, 0);
	elBody().appendChild(div);

	var finishHandler = function(e)
	{
		DOM.show(div);
	}
	var anim1 = new Animation(layer, "opacity", 0, dark ? 0.6 : 0.7);
	anim1.setDuration(100);
	anim1.setMovement("smooth", 1);
	anim1.setFinishedHandler(finishHandler);

	var anim1a = new Animation(div, "opacity", 0, 1);
	anim1a.setDuration(100);
	anim1a.setMovement("smooth", 1);

	var clickHandler = function(e)
	{
		if (!DOM.visible(div)) return;   // Nothing to do here

		anim1.halt();
		anim1a.halt();

		//DOM.hide(div);
		//elBody().removeChild(div);

		var finishHandler2 = function(e)
		{
			elBody().removeChild(layer);
			elBody().removeChild(div);
		}
		var anim2 = new Animation(layer, "opacity", dark ? 0.6 : 0.7, 0);
		anim2.setDuration(100);
		anim2.setMovement("smooth", 1);
		anim2.setFinishedHandler(finishHandler2);
		anim2.play();

		var anim2a = new Animation(div, "opacity", 1, 0);
		anim2a.setDuration(100);
		anim2a.setMovement("smooth", 1);
		anim2a.play();
	}
	DOM.addEvent(layer, "click", clickHandler);
	DOM.addEvent(div, "click", clickHandler);
	DOM.addKeyEvent("Escape", clickHandler);

	anim1.play();
	anim1a.play();
}

function PreloadImages(url_str)
{
	var container = elId("preloadContainer");
	var urls = url_str.split(" ");
	for (var x = 0; x < urls.length; x++)
	{
		var img = document.createElement("img");
		img.setAttribute("src", urls[x]);
		img.setAttribute("width", "20");
		img.setAttribute("height", "20");
		container.appendChild(img);
	}
}



setConfigValue("infolabel.imgpath", "../../img/");
setConfigValue("clicktoclosetext", "Klicken oder Esc druecken zum Schliessen");

