/**
 * 
 * All content on this website (including text, images, source
 * code and any other original works), unless otherwise noted,
 * is licensed under a Creative Commons License.
 * 
 * http://creativecommons.org/licenses/by-nc-sa/2.5/
 * 
 * Copyright (C) 2004-2007 Open-Xchange, Inc.
 * Mail: info@open-xchange.com 
 * 
 * @author Viktor Pracht <viktor.pracht@open-xchange.com>
 * 
 */

var init = {};

function initAdd(name, ids) {
	var list = init[name];
	if (!list) list = init[name] = [];
	for (var i in ids) list.push(ids[i]);
}

function initSet(name, ids) {
	var list = init[name];
	if (!list) list = init[name] = {};
	for (var i in ids) list[i] = ids[i];
}
/**
 * 
 * All content on this website (including text, images, source
 * code and any other original works), unless otherwise noted,
 * is licensed under a Creative Commons License.
 * 
 * http://creativecommons.org/licenses/by-nc-sa/2.5/
 * 
 * Copyright (C) 2004-2007 Open-Xchange, Inc.
 * Mail: info@open-xchange.com 
 * 
 * @author Viktor Pracht <viktor.pracht@open-xchange.com>
 * 
 */

  ////////////////////////
 //   Event handling   //
////////////////////////

/**
 * Table of currently registered global event listeners.
 * @private
 */
var events = {};

/**
 * Encapsulates a method of an object in a function.
 * @param {Object} obj The instance of the object to call.
 * @param {String} methodname The name of the method to call on obj.
 * @return A function that can be used as the second parameter to {@link #register}.
 * @type Function
 * @see #register
 */
function encapsulateMethod(obj, methodname) {
	return function() { obj[methodname].apply(obj, arguments); };
}

/**
 * Registers a function as a listener for a global event.
 * Registering the same function for the same event multiple times has no effect.
 * Any given function is called exactly once for every triggered event.
 * The order of calls to different functions registered for the same event is
 * not guaranteed.
 * @param {String} name The name of the event. Event names are global.
 * @param {Function} callback A function to be called when the named event is
 * triggered.
 * @see #unregister
 */
function register(name, callback) {
	if (typeof(callback) != "function") {
		alert("Register " + name + ":" +callback + "\n  is not a function.");
		return;
	}
	var list = (name in events) ? events[name] : events[name] = new Array();
	for (var i in list) if (list[i] == callback) return;
	list.push(callback);
}

/**
 * Unregisters a previously registered listener from a global event.
 * Unregistering a listener which was not previously registered for the specified
 * event has no effect.
 * @param {String} name The name of the event. Event names are global.
 * @param {Function} callback The same function object as was used in a call to
 * {@link #register}. Return values from {@link #encapsulateMethod} must be stored
 * and reused instead of calling #encapsulateMethod again.
 */
function unregister(name, callback) {
	var list = events[name];
	if (!list) return;
	for (var i = 0; i < list.length; i++) {
		if (list[i] == callback) {
			list.splice(i, 1);
			return;
		}
	}
}
/*	
	if (!list) return;
	var cb = list.pop();
	if (cb == callback) return;
	for (var i = list.length - 1; i >= 0; i--) {
		if (list[i] == callback) {
			list[i] = cb;
			return;
		}
	}
	if(cb)
		list.push(cb);
}
*/

/**
 * Triggers a global event and calls all registered listeners.
 * @param {String} name The name of the event. Event names are global.
 * @param params Any further parameters are passed to the called listeners.
 * @see #register
 */
function triggerEvent() {
	var list = events[arguments[0]];
	var args = null;
	args = new Array(arguments.length - 1);
	for (var i = 1; i < arguments.length; i++)
		args[i - 1] = arguments[i];
	if (list) 
	{
		for (var cb in list)
		{ 
			list[cb].apply(null, args);
		}
	}
}

function Events() {
	this.events = {};
	this.posted = {};
}

Events.prototype = {
	/**
	 * Registers a function as a listener for an event.
	 * Registering the same function for the same event multiple times has no
	 * effect. Any given function is called exactly once for every triggered
	 * event. The order of calls to different functions registered for the same
	 * event is not guaranteed.
	 * @param {String} name The name of the event. Event names are specific to
	 * an Events instance.
	 * @param {Function} callback A function to be called when the named event
	 * is triggered. Which parameters are passed to the callback is defined by
	 * the event.
	 * @see #unregister
	 */
	register: function(name, callback) {
		var list = (name in this.events) ? this.events[name]
		                                 : this.events[name] = new Array();
		for (var i in list) if (list[i] == callback) return;
		list.push(callback);
	},

	/**
	 * Unregisters a previously registered listener from an event.
	 * Unregistering a listener which was not previously registered for the
	 * specified event has no effect.
	 * @param {String} name The name of the event. Event names are specific to
	 * an Events instance.
	 * @param {Function} callback The same function object as was used in a call
	 * to {@link #register}. Return values from {@link #encapsulateMethod} must
	 * be stored and reused instead of calling #encapsulateMethod again.
	 */
	unregister: function(name, callback) {
		var list = this.events[name];
		if (!list) return;
		var cb = list.pop();
		if (cb == callback) return;
		for (var i = list.length - 1; i >= 0; i--) {
			if (list[i] == callback) {
				list[i] = cb;
				return;
			}
		}
		if(cb)
			list.push(cb);
	},

	/**
	 * Triggers an event and calls all registered listeners.
	 * @param {String} name The name of the event. Event names are specific to
	 * an Events instance.
	 * @param params Any further parameters are passed to the called listeners.
	 * @see #register
	 */
	trigger: function() {
		var list = this.events[arguments[0]];
		if (list) {
			var args = new Array(arguments.length - 1);
			for (var i = 1; i < arguments.length; i++)
				args[i - 1] = arguments[i];
			for (var cb in list) list[cb].apply(null, args);
		}
	},
	
	/**
	 * Postpones the triggering of an event until the currently executing
	 * JavaScript code exits and the browser enters its event loop.
	 * If an event is already posted, any further posts for the same event have
	 * no effect.
	 * @param {String} name The name of the event. Event names are specific to
	 * an Events instance.
	 * @param params Any further parameters are passed to the called listeners.
	 * If a parameter is a fuction, then it is evaluated once immediately before
	 * calling the first listener and the return value is passed to
	 * the listeners. In the case of multiple calls with the same event name
	 * without an opportunity to execute the listeners, the parameter values
	 * from the last call are used.
	 * @see #trigger
	 */
	post: function(name) {
		if (!this.posted[name]) {
			var Self = this;
			setTimeout(function() {
				var args = Self.posted[name];
				delete Self.posted[name];
				var list = Self.events[name];
				if (list) {
					var params = new Array(args.length - 1);
					for (var i = 1; i < args.length; i++) {
						if (args[i].constructor == Function)
							params[i - 1] = args[i]();
						else
							params[i - 1] = args[i];
					}
					for (var cb in list) list[cb].apply(null, params);
				}
			}, 0);
		}
		this.posted[name] = arguments;
	}
}
/**
 * 
 * All content on this website (including text, images, source
 * code and any other original works), unless otherwise noted,
 * is licensed under a Creative Commons License.
 * 
 * http://creativecommons.org/licenses/by-nc-sa/2.5/
 * 
 * Copyright (C) 2004-2007 Open-Xchange, Inc.
 * Mail: info@open-xchange.com 
 * 
 * @author Viktor Pracht <viktor.pracht@open-xchange.com>
 * 
 */

var _, gettext, ngettext;
var setLanguage, getLanguage;

/**
 * Formats a string by replacing printf-style format specifiers in the string
 * with dynamic parameters. Flags, width, precision and length modifiers are
 * not supported. All type conversions are performed by the standard toString()
 * JavaScript method.
 * @param {String} string The format string.
 * @param params Either an array with parameters or multiple separate
 * parameters.
 */
function format(string, params) {
	var param_array = params;
	if (typeof(params) != "object") {
		param_array = new Array(arguments.length - 1);
		for (var i = 1; i < arguments.length; i++)
			param_array[i - 1] = arguments[i];
	}
	var index = 0;
	return String(string).replace(/%(([0-9]+)\$)?[A-Za-z]/g, function(match, pos, n) {
		if (pos) index = n - 1;
		return param_array[index++];
	}).replace(/%%/, "%");
}

/**
 * Formats and translates an error returned by the server.
 * @param {Object} result the JSON object as passed to a JSON callback function.
 * @type String
 * @returns the formatted and translated error message.
 */
function formatError(result) {
	//#. %1$s is the error code.
	//#. %2$s is the formatted error message.
	//#, c-format
	return format(_("Error code: %2$s (%1$s,%3$s)"), result.code,format(_(result.error), result.error_params),result.error_id);
}
var current2;
var languages;
var originals = {};
	
(function() {
	languages = {};
	var counter = 0;

	_ = gettext = function(text) {
		text = String(text);
		if (!current2) return text.substring(text.indexOf("|") + 1);
		if (current2.dictionary)
			return current2.dictionary[text] || text.substring(text.indexOf("|") + 1);
	};

	ngettext = function(singular, plural, n) {
		var text = n != 1 ? plural : singular;
		var def = text.substring(text.indexOf("|") + 1);
		if (!current2) return def;
		var translation = current2.dictionary[singular + "\0" + plural];
		if (!translation) return def;
		return translation[Number(current2.plural(n))] || def;
	};

	setLanguage = function(name2) {
		if (languages[name2]) {
			current2 = languages[name2];
			for (var i in init.i18n) {
				var attrs = init.i18n[i].split(",");
				var node = $(i);
				if(node) {
					for (var j = 0; j < attrs.length; j++) {
						var attr = attrs[j];
						var id = attr + "," + i;
						var text = attr ? node.getAttributeNode(attr)
						                : node.firstChild;
						if (!text) alert(format('Invalid i18n for id="%s"', i));
						var original = originals[id];
						if (!original) original = originals[id] = text.nodeValue;
						text.nodeValue = _(original);
					}
				}
			}
			triggerEvent("LanguageChanged");
		} else {
			// check the main window, maybe we already loaded the i18n-js-file
			if (corewindow !== window && corewindow.getLanguage(name2) != null)
				languages[name2] = corewindow.getLanguage(name2);

			(new JSON()).get("lang/" + name2 + ".js", null, function(lang3) {
				languages[name2] = lang3;
				setLanguage(name2);
			});
		}
	};
	
	getLanguage = function(name) {
		if (languages && languages[name])
			return languages[name];
		else
			return null;
	};	

})();

/**
 * Encapsulation of a single translated text node which is created at runtime.
 * @param {Function} callback A function which is called as a method of
 * the created object and returns the current translated text.
 */
function I18nNode(callback) {
	/**
	 * The DOM text node which is updated automatically.
	 */
	this.node = document.createTextNode(callback());

	this.callback = callback;
	this.index = ++I18nNode.counter;
	this.enable();
}

I18nNode.prototype = {
	/**
	 * Updates the node contents. Is called whenever the current language
	 * changes and should be also called when the displayed value changes.
	 */
	update: function() { this.node.data = this.callback(); },
	
	/**
	 * Disables automatic updates for this object.
	 * Should be called when the text node is removed from the DOM tree.
	 */
	disable: function() { delete I18nNode.nodes[this.index]; },
	
	/**
	 * Reenables previously disabled updates.
	 */
 	enable: function() { I18nNode.nodes[this.index] = this; }
};

I18nNode.nodes = {};
I18nNode.counter = 0;

register("LanguageChanged", function() {
	for (var i in I18nNode.nodes) I18nNode.nodes[i].update();
});

/**
 * Creates an automatically updated node from a static text. The node can not
 * be removed.
 * @param {String} text The text to be translated. It must be marked
 * with the / *i18n* / comment.
 * @type Object
 * @return The new DOM text node.
 */
function addTranslated(text) {
	return (new I18nNode(function() { return _(text); })).node;
}

/**
 * Format UTC into human readable date and time formats
 * @param {String} stringtoformat String to format
 * @param {String} formattype a "one word string" to request the returned format
 * 				<b>possible values:</b>
 * 				date 	 	- only the date,
 * 				time 		- only the time
 * @return {Date} return Date Object if valid else null
 */
function parseDateString(stringtoformat,formattype,format_language) {
	var datereturn;
	if(!format_language) {
		format_language=configGetKey("language");
	}
	if (!formattype) {
		formattype = "date";
	}
	if(formattype=="time") {
		var timematch=/^\s*((0?\d|1[0-2])(:?([0-5]\d))?\s*([AaPp][Mm])?|([01]?\d|2[0-3])(:?([0-5]\d))?)\s*$/.exec(stringtoformat);
		if (!timematch) 
			return null;
		var h = Number(timematch[2] || timematch[6]);
		var m = Number(timematch[4] || timematch[8] || 0);
		if (timematch[5]) {
			h = h % 12;
			if(timematch[5].toLowerCase() == "pm") {
				h=h+12;
			}
		}
		if(m) return new Date(Date.UTC(1970, 0, 1,h,m,0));	
		else return new Date(Date.UTC(1970, 0, 1,h,0,0));	
	}
	if(formattype=="date") {
		var datematch = /^\s*(\d{1,2})([^0-9](\d{1,2})([^0-9](\d+))?)?\s*$/.exec(stringtoformat);
		if(!datematch) 
			return null;
		var d;
		var m;
		var y;
		switch (format_language) {	
			case "en_US":
				var d=datematch[3];
				var m=datematch[1];
				var y=datematch[5];
				break;
			default:
				var d=datematch[1];
				var m=datematch[3];
				var y=datematch[5];
		}
		return new Date(Date.UTC(y, (m-1), d,0,0,0));
	}
}


 var months= new Array("January",/*i18n*/
	"February",/*i18n*/
	"March",/*i18n*/
	"April",/*i18n*/
	"May",/*i18n*/
	"June",/*i18n*/
	"July", /*i18n*/
	"August", /*i18n*/
	"September",/*i18n*/
	"October",/*i18n*/
	"November",/*i18n*/
	"December");/*i18n*/
	var weekdays= new Array("Sunday",
	"Monday",/*i18n*/
	"Tuesday",/*i18n*/
	"Wednesday",/*i18n*/
	"Thursday",/*i18n*/
	"Friday",/*i18n*/
	"Saturday");/*i18n*/
function isToday(utc) {
	utc=new Date(utc);
	var now=new Date();
	var today=new Date(Date.UTC(now.getFullYear(),now.getMonth(),now.getDate(),0,0,0));
	var diff=utc.getTime() - today.getTime();
	if(diff >= 0 && diff < (1000 * 60 * 60 * 24)) {
		return true;
	}
	return false;
}
/**
 * Format UTC into human readable date and time formats
 * @param {Date} utc Date 
 * @param {String} formattype a "one word string" to request the returned format
 * 				<b>possible values:</b>
 * 				date 	 	- only the date,
 * 				time 		- only the time
 * 				datetime 	. time and date
 * 				dateday 	. date with day (i18n translated)
 *				datestring 	. string of a date object (i18n translated)
 * @return {String} return formated string
 */
function formatDate(utc,dateform,format_language) {
	utc=new Date(utc);
	if(!format_language) {
		format_language=configGetKey("language");
	}
	var daystring=utc.getUTCDay();
	var day=utc.getUTCDate();
	var month=utc.getUTCMonth()+1;
	var year=utc.getUTCFullYear();
	var hour=utc.getUTCHours();
	var minutes=utc.getUTCMinutes();
	switch(format_language) {
		case "en_US":
			if(dateform=="suffix") {
				var pm=false;
				if(hour>11) { pm=true; }
				if(pm) return ("PM");
				else return ("AM");
			}
			if(dateform=="onlyhour") {
				if(hour>11) { hour=hour-12;	}
				if (hour == 0) hour = 12;
				if(hour<10) hour="0"+hour;
				
				return (hour+"");
			}
			if(dateform=="hour") {
				var pm=false;
				if(hour>11) { pm=true;hour=hour-12;	}
				if (hour == 0) hour = 12;
				if(pm) return (hour+"pm");
				else return (hour+"am");
			}
			if(dateform=="time") {
				var pm=false;
				if(hour>11) { pm=true;hour=hour-12;	}
				if(hour<10) hour="0"+hour;
				if (hour == 0) hour = 12;
				if(minutes<10) minutes="0"+minutes;
				if(pm) return (hour+":"+minutes+ " PM");
				else return (hour+":"+minutes+ " AM");
			}
			if(dateform=="date") {
				if(day<10) day="0"+day;
				if(month<10) month="0"+month;
				return month+"-"+day+"-"+year;
			}
			if(dateform=="datetime") {
				if(hour>11) { pm=true;hour=hour-12;	}
				if(hour<10) hour="0"+hour;
				if (hour == 0) hour = 12;
				if(minutes<10) minutes="0"+minutes;
				if(day<10) day="0"+day;
				if(month<10) month="0"+month;
				if(pm) return month+"-"+day+"-"+year+" "+ (hour+":"+minutes+ " PM");
				else return month+"-"+day+"-"+year+" "+ (hour+":"+minutes+ " AM");
			}
			if(dateform=="dateday") {
				if(day<10) day="0"+day;
				if(month<10) month="0"+month;
				return _(weekdays[daystring]) + ", " + month + "-"  + day + "-" + year;
			}
			if(dateform=="datestring") {
				if(day<1) day=day+"st" ;
				if(day<2) day=day+"nd" ;
				if(day<3) day=day+"rd" ;
				if(day>3) day=day+"th" ;
				month=_(months[month-1]);
				return month+" "+day+", "+year;
			}
			break;	
		default:
			if(dateform=="suffix") {
				return "";
			}
			if(dateform=="onlyhour") {
				if(hour<10) hour="0"+hour;
				return (hour);
			}
			if(dateform=="hour") {
				return hour;
			}
			if(dateform=="time") {
				if(hour<10) hour="0"+hour;
				if(minutes<10) minutes="0"+minutes;
				return (hour+":"+minutes); 
			}
			if(dateform=="date") {
				if(day<10) day="0"+day;
				if(month<10) month="0"+month;
				return day+"."+month+"."+year;
			}
			if(dateform=="datetime") {
				if(hour<10) hour="0"+hour;
				if(minutes<10) minutes="0"+minutes;
				if(day<10) day="0"+day;
				if(month<10) month="0"+month;
				if(pm) return day+"."+month+"."+year+" "+ (hour+":"+minutes);
				else return day+"."+month+"."+year+" "+ (hour+":"+minutes);
			}
			if(dateform=="dateday") {
				if(day<10) day="0"+day;
				if(month<10) month="0"+month;
				return  _(weekdays[daystring]) + ", " + day + "."  + month + "." + year;
			}
			if(dateform=="datestring") {
				month=_(months[month-1]);
				return day+". "+month+" "+year;
			}
			break;
	}
}

function formatNumbers(value,format_language) {
	var val;
	if(!format_language) {
		format_language=configGetKey("language");
	}
	switch(format_language) {
		case "en_US":
			return value;
			break;
		default:
			val = String(value).replace(/\./,"\,");
			return val;
			break;
	}
}

function round(val) {
	val = formatNumbers(Math.round(parseFloat(String(val).replace(/\,/,"\.")) * 100) / 100);
	return val;
}

function getInterval(t, until) {
	if (t >= 7*24*60*60*1000 && t % 7*24*60*60*1000 == 0) {
		var n = Math.round(t / (7*24*60*60*1000));
		return format(until ? ngettext("in|1 week", "in|%d weeks", n)
		                    : ngettext("1 week", "%d weeks", n),
		              n);
	} else if (t >= 24*60*60*1000) {
		var n = Math.round(t / (24*60*60*1000));
		return format(until ? ngettext("in|1 day", "in|%d days", n)
		                    : ngettext("1 day", "%d days", n),
		              n);
	} else if (t >= 60*60*1000) {
		var h = Math.floor(t / (60*60*1000));
		var m = Math.round((t / (60*1000)) % 60);
		return format(until ? ngettext("in|1 hour and %2$s", "in|%1$d hours and %2$s", h)
		                    : ngettext("1 hour and %2$s", "%1$d hours and %2$s", h),
		              h,
		              format(until ? ngettext("in|1 minute", "in|%d minutes", m)
			                       : ngettext("1 minute", "%d minutes", m),
			                 m));
	} else {
		var m = Math.round(t / (60*1000));
		return format(until ? ngettext("in|1 minute", "in|%d minutes", m)
		                    : ngettext("1 minute", "%d minutes", m),
		              m);
	}
}

/*
// SimpleDate in JavaScript
// TODO: Language selection - now done by inserting manually into the lang files

SimpleDate.prototype.dateValue = null;

function SimpleDate(date) {
	if (date == null){
		this.dateValue = new Date();
	} else if (date instanceof Date){
        this.dateValue = date;
    } else  if (date instanceof Number){
        this.dateValue = new Date(date);
    } else {
		var longValue = parseInt(date, 10);
        this.dateValue = new Date(longValue);
	}
}

SimpleDate.prototype.clone = function () {
	return new SimpleDate(this.getLongValue());
}


SimpleDate.prototype.getLongValue = function (leaveSecondes) {
	var tDate = new Date(this.valueOf());

	if (!leaveSecondes)
		tDate.setUTCSeconds(0, 0);

	return tDate.valueOf();
}

SimpleDate.prototype.setLongValue = function (longValue) {
	this.dateValue = new Date(longValue);
}

SimpleDate.prototype.valueOf = function (date) {
	return this.dateValue.valueOf();
}

SimpleDate.prototype.setDate = function (date) {
	if (date instanceof Date)
		this.dateValue = date;
	else
		this.dateValue = new Date(date);
}

SimpleDate.prototype.setHours = function (hour) {
	this.dateValue.setUTCHours(hour);
}

SimpleDate.prototype.getHours = function () {
	return this.dateValue.getUTCHours();
}

SimpleDate.prototype.setMinutes = function (minutes) {
	this.dateValue.setUTCMinutes(minutes);
}

SimpleDate.prototype.getMinutes = function () {
	return this.dateValue.getUTCMinutes();
}

SimpleDate.prototype.setSeconds = function (seconds, milliseconds) {
	if (milliseconds)
		this.dateValue.setUTCSeconds(seconds, milliseconds);
	else
		this.dateValue.setUTCSeconds(seconds, 0);
}

SimpleDate.prototype.getSeconds = function () {
	return this.dateValue.getUTCSeconds();
}

SimpleDate.prototype.setDayInMonth = function (numDate) {
	this.dateValue.setUTCDate(numDate);
}

SimpleDate.prototype.getDayOfWeek = function () {
	return this.dateValue.getUTCDay();
}

SimpleDate.prototype.getDayInMonth = function () {
	return this.dateValue.getUTCDate();
}

SimpleDate.prototype.setYear = function (numYear) {
	this.dateValue.setUTCFullYear(numYear);
}

SimpleDate.prototype.getYear = function () {
	return this.dateValue.getUTCFullYear();
}

SimpleDate.prototype.setMonth = function (month) {
	this.dateValue.setUTCMonth(month);
}

SimpleDate.prototype.getMonth = function () {
	return this.dateValue.getUTCMonth();
}

SimpleDate.prototype.toString = function () {
	return this.getLongValue()+"";
}

SimpleDate.prototype.toFormattedString = function (stringFormat) {
	if (this.dateValue == null) return "";
	var buffer = new Array();
	try {
		for (var i = 0; i < stringFormat.length; i++) {
			var chr1 = stringFormat.charAt(i);
			if (chr1 == "~") {
				buffer[buffer.length] = 
					this.getNextToken(stringFormat.charAt(++i), this.dateValue);
			} else {
				buffer[buffer.length] = chr1;
			}
		}
	} catch(e) {
		return null;
	}
	return buffer.join("");
}

SimpleDate.prototype.getNextToken = function (chr, date) {
	var ret = chr;
	switch (chr){
		case "y":
			//year two digits (04)
			ret = new String(date.getUTCFullYear()).substr(2);
			break;
		case "Y":
			//year four digits (2004)
			ret = date.getUTCFullYear();
			break;
		case "d":
			//day in month (1 - 31)
			ret = date.getUTCDate();
			ret = ret < 10 ? "0" + ret : ret;
			break;
		case "D":
			//day in week (Full String)
			ret = this.dayArray[date.getUTCDay()];
			break;
		case "w":
			//day of week (0-6)
			ret = date.getUTCDay();
			break;
		case "W":
			//day of week (1-7)
			ret = date.getUTCDay() + 1;
			break;
		case "k":
			//month of year (1-12)
			ret = date.getUTCMonth() + 1;
			ret = ret < 10 ? "0" + ret : ret;
			break;
		case "M":
			//month of year (full string)
			ret = this.monthArray[date.getUTCMonth()];
			break;
		case "a":
			//am/pm marker
			ret = date.getUTCHours() < 12 ? "AM" : "PM";
			break;
		case "h":
			//hour (12 hours)
			ret = date.getUTCHours();
			ret = ret > 12 ? ret - 12 : ret;
			ret = ret < 10 ? "0" + ret : ret;
			break;
		case "H":
			//hour (24 hours)
			ret = date.getUTCHours();
			ret = ret < 10 ? "0" + ret : ret;
			break;
		case "m":
			//minutes
			ret = date.getUTCMinutes();
			ret = ret < 10 ? "0" + ret : ret;
			break;
		case "s":
			//secondes
			ret = date.getUTCSeconds();
			ret = ret < 10 ? "0" + ret : ret;
			break;
		case "S":
			//milisecondes
			ret = date.getUTCMilliseconds();
			break;
		case "L":
			//long value
			ret = date.valueOf();
			break;
	}
	return ret;
}*/
var debug = false;  // IMPORTANT!: This var is for debugging and should never set to true.

var bUWAEnabled = true;

/**
 * 
 * All content on this website (including text, images, source
 * code and any other original works), unless otherwise noted,
 * is licensed under a Creative Commons License.
 * 
 * http://creativecommons.org/licenses/by-nc-sa/2.5/
 * 
 * Copyright (C) 2004-2007 Open-Xchange, Inc.
 * Mail: info@open-xchange.com 
 * 
 * @author Viktor Pracht <viktor.pracht@open-xchange.com>
 * 
 */

var AjaxRoot = "/ajax"
function oldGecko() {
	if (navigator.appName == "Netscape" && navigator.userAgent.indexOf("rv:") >= 0) {
		var version = Number(navigator.userAgent.match(/rv:(\d+\.\d+)/)[1]);
		if (version <= 1.8)
			return true;
	}
	return false;
}
////////////////////////////////
// BEGIN Temporarily entries
var preloadingnewwindows=true;
 
/**
 * @bShared boolean which is true if shared folder functionality is on
 * TODO: Should be replaced with server config parameter later
 */
 
var bShared = true;
var bPublic = true;

// END Temporarily entries 
////////////////////////////////
 
/**
 * @fileoverview Main JavaScript framework.
 */

// Das Kommentar da oben *bleibt* auch oben.

  ///////////////////////////
 //   Automatic Refresh   //
///////////////////////////

function refreshWindow () {
	triggerEvent("OX_Refresh");
}

register("OX_Refresh", function() { storageCache.update(); });

var autorefresh_var;
register("OX_Configuration_Loaded_Complete",function () {
	if(configGetKey("gui.global.autorefresh") != 0) {
		autorefresh_var=window.setInterval(refreshWindow, (configGetKey("gui.global.autorefresh")*60000));	
	}
});
register("OX_Configuration_Changed",function(param) {
	if(param == "configuration/settings") {
		if(autorefresh_var) {
			window.clearInterval(autorefresh_var);
		}
		if(configGetKey("gui.global.autorefresh") != 0) {
			autorefresh_var=window.setInterval(refreshWindow, (configGetKey("gui.global.autorefresh")*60000));	
		}
	}
});

  /////////////////////////////////
 //   Asynchronous processing   //
/////////////////////////////////

/**
 * Creates an object which calls the specified callback when multiple parallel
 * processes complete.
 * @param {Functio} callback The callback which is called as a method of the
 * returned object when all parallel processes complete.
 */
function Join(callback) {
	//alert(callback.constructor.nativeCode);
	this.callback = callback;
	this.count = 0;
}

Join.prototype = {
	/**
	 * Adds a parallel process by specifying a callback function which is called
	 * when that process ends.
	 * @param {Function} callback A callback function which should be called
	 * when the process ends.
	 * @type Function
	 * @return A function which should be specified as callback instead of
	 * the function specified as parameter.
	 */
	add: function(callback) {
		this.count++;
		return this.alt(callback);
	},
	
	/**
	 * Adds an alternative callback function to an existing parallel process.
	 * @param {Function} callback A calback function which should be called
	 * when the process ends.
	 * @type Function
	 * @return A function which should be specified as callback instead of
	 * the function specified as parameter.
	 */
	alt: function(callback) {
		var Self = this;		
		return function() {
			if (callback) callback.apply(this, arguments);
			if (!--Self.count) Self.callback();		
		}
	}
}

  ////////////////////
 //   DOM events   //
////////////////////

/**
 * Stops the processing of a DOM event
 * @param {Event} e The currently processed event.
 */
var stopEvent = (function() {

	function stopNormal(e) {
		e.preventDefault();
		e.stopPropagation();
	}
	
	function stopIE(e) {
		e.returnValue = false;
		e.cancelBubble = true;
	}
	
	return function(e) {
		stopEvent = e.preventDefault ? stopNormal : stopIE;
		stopEvent(e);
	}
})();

/**
 * Prevents the default handler of a DOM event from executing.
 * @param {Event} e The currently processed event.
 */
var cancelDefault = (function() {
	function cancelNormal(e) { e.preventDefault(); }
	function cancelIE(e) { e.returnValue = false; }
	return function(e) {
		cancelDefault = e.preventDefault ? cancelNormal : cancelIE;
		cancelDefault(e);
	}
})();

/**
 * Stops the bubbling of a DOM event.
 * @param {Event} e The currently processed event.
 */
var cancelBubbling = (function() {
	function cancelNormal(e) { e.stopPropagation(); }
	function cancelIE(e) { e.cancelBubble = true; }
	return function(e) {
		cancelBubbling = e.stopPropagation ? cancelNormal : cancelIE;
		cancelBubbling(e);
	}
})();

/**
 * @private
 */
var _IE_Events = {};

/**
 * Adds an event handler for a DOM event.
 * To work around memory leaks in Internet Explorer, this function decouples
 * the callback function from the hooked DOM object. For bookkeeping purposes,
 * the callback function gets a property named &quot;id&quot; with a unique
 * value.
 * @param {Object} node A DOM object which provides an event.
 * @param {String} event The name of the event. E. g. &quot;onclick&quot;.
 * @param {Function} callback The event handler. It is called with
 * the DOM Event as parameter.
 */
var addDOMEvent = (function() {

	var id = 0;
	
	function makeHandler(id, win) {
		var f = function() {
			var list = _IE_Events[id];
			var e = !win ? window.event : win;
			if (!e.currentTarget) e.currentTarget = this;
			for (var i = list.length - 1; i >= 0; i--)
				if (list[i](e) === false) return false;
			return true;
		};
		f.id = id;
		return f;
	}
	
	function addIE(node, event, callback, win) {
		event = "on" + event;
		var oldHandler = node[event];
		var list;
		if (debug && oldHandler && !oldHandler.id) {
			alert(format("Mixing addDOMEvent and DOM 0 events is not allowed!\nid=\"%s\" event=\"%s\"",
				node.id, event));	
		}
		if (oldHandler && oldHandler.id) {
			list = _IE_Events[oldHandler.id];
		} else {
			node[event] = makeHandler(++id, win);
			list = _IE_Events[id] = [];
		}
		list.push(callback);
	}
	
	function addNormal(node, event, callback) {
		node.addEventListener(event, callback, false);
	}
	
	return function(node, event, callback, win) {
		addDOMEvent = node.addEventListener ? addNormal : addIE;
		addDOMEvent(node, event, callback, win);
	};
})();

/**
 * Removes a previously added DOM event handler.
 * @param {Object} node A DOM object which provides an event.
 * @param {String} event The name of the event. E. g. &quot;onclick&quot;.
 * @param {Function} callback The previously added event handler.
 * @see addDOMEvent
 */
var removeDOMEvent = (function() {

	function removeIE(node, event, callback) {
		event = "on" + event;
		var handler = node[event];
		if (!handler || !handler.id)
			return;
		var list = _IE_Events[handler.id];
		for (var i in list) if (list[i] == callback) {
			list.splice(i, 1);
			if (!list.length) {
				delete _IE_Events[handler.id];
				node[event] = "";
			}
		}
	}
	
	function removeNormal(node, event, callback) {
		node.removeEventListener(event, callback, false);
	}
	
	return function(node, event, callback) {
		removeDOMEvent = node.addEventListener ? removeNormal: removeIE;
		removeDOMEvent(node, event, callback);
	}
})();

  //////////////
 //   JSON   //
//////////////

/**
 * A queue of JSON requests.
 * This class maintains a queue of asynchronous JSON events. The requests are
 * processed one after another. While one request is pending, the remaining
 * requests can be cancelled with {@link #cancel}.
 * @constructor
 */
function JSON() {
	/**
	 * @private
	 */
	this.first = null;
	
	/**
	 * @private
	 */
	this.last = null;
	
	/**
	 * @private
	 */
	this.processing = false;
}

JSON.serialize = function(data) {
	if (typeof(data) == "string")
		return "\"" + data.replace(/[\x00-\x1f\\"]/g, function(c) {
			var n = Number(c.charCodeAt(0)).toString(16);
			return "\\u00" + (n.length < 2 ? "0" + n : n);
		}) + "\"";
	if (typeof(data) == "function") return "function";
	if (!data || typeof(data) !== "object") return String(data);
	var strings = new Array(data.length);
	if (data.constructor == Array) {
		for (var i in data) strings[i] = JSON.serialize(data[i]);
		return "[" + strings.join() + "]";
	}
	var j = 0;
	for (var i in data) strings[j++] = "\"" + i + "\":" + JSON.serialize(data[i]);
	return "{" + strings.join() + "}";
}

JSON.count = 0;

JSON.prototype = {
	/**
	 * Asynchronously requests a JSON object from the server.
	 * This method retrieves a JSON object from the server by issuing an HTTP
	 * GET request to the specified URI and calling the specified callback when
	 * the retrieval is complete. If there is already another request from this
	 * queue object pending, the new request is put at the end of a queue to be
	 * executed after all previous requests have completed.
	 * @param {String} uri The URI for the HTTP GET request.
	 * @param {Function} cb A callback function which is called with the
	 * received JSON object or raw data as parameter. If there was any error
	 * then this function is not called.
	 * @param {Function} errorHandler An optional callback function wihch is
	 * called when the server returns an error. The function takes two
	 * parameters: result and status. If the HTTP status code was 200, then
	 * result is the JSON object and status is not set. If the HTTP status was
	 * not 200, then result is the status string and status is the HTTP status
	 * code. The function should return true when it handles the error.
	 * Otherwise, the default error handler specified by JSON.errorHandler will
	 * be called after this function returns. If this parameter is not specified,
	 * the default error handler is called directly.
	 * @param {Boolean} raw Specifies whether the response data should be
	 * passed to the callback as-is or first parsed as a JSON object. Defaults
	 * to the latter.
	 * @type Object
	 * @return An object which can be used to cancel the request with the
	 * {@link #cancel} method.
	 * @see #cancel
	 */
	get: function(uri, meta, cb, errorHandler, raw) {
		var request = {
			method: "GET",
			uri: uri,
			data: "",
			cb: cb,
			errorHandler: errorHandler,
			raw: raw,
			next: null
		};
		this.add(request);
		return request;
	},
	
	/**
	 * Asynchronously posts url-encoded data and retrieves a JSON object from
	 * the server.
	 * This method posts an object to the server by issuing an HTTP
	 * POST request to the specified URI and calling the specified callback when
	 * the reply arrives. If there is already another request from this
	 * queue object pending, the new request is put at the end of a queue to be
	 * executed after all previous requests have completed.
	 * @param {String} uri The URI for the HTTP POST request.
	 * @param {Object} data An object which is serialized using the
	 * application/x-www-form-urlencoded encoding and sent as the body of the
	 * request.
	 * @param {Function} cb A callback function which is called with the
	 * received JSON object or raw data as parameter. If there was any error
	 * then this function is not called.
	 * @param {Function} errorHandler An optional callback function wihch is
	 * called when the server returns an error. The function takes two
	 * parameters: result and status. If the HTTP status code was 200, then
	 * result is the JSON object and status is not set. If the HTTP status was
	 * not 200, then result is the status string and status is the HTTP status
	 * code. The function should return true when it handles the error.
	 * Otherwise, the default error handler specified by JSON.errorHandler will
	 * be called after this function returns. If this parameter is not specified,
	 * the default error handler is called directly.
	 * @param {Boolean} raw Specifies whether the response data should be
	 * passed to the callback as-is or first parsed as a JSON object. Defaults
	 * to the latter.
	 * @see #get
	 * @see #cancel
	 */
	post: function(uri, data, meta, cb, errorHandler, raw) {
		var encoded = new Array(), n = 0;
		for (var i in data)
			encoded[n++] = i + "=" + encodeURIComponent(data[i]);
		var request = {
			method: "POST",
			uri: uri,
			data: encoded.join("&"),
			contenttype: "application/x-www-form-urlencoded",
			cb: cb,
			errorHandler: errorHandler,
			raw: raw,
			next: null
		};
		this.add(request);
		return request;
	},
	
	/**
	 * Asynchronously sends a JSON object and retrieves a JSON object from
	 * the server.
	 * This method sends a JSON object to the server by issuing an HTTP
	 * PUT request to the specified URI and calling the specified callback when
	 * the reply arrives. If there is already another request from this
	 * queue object pending, the new request is put at the end of a queue to be
	 * executed after all previous requests have completed.
	 * @param {String} uri The URI for the HTTP POST request.
	 * @param {Object} data An object which is serialized using JSON syntax and
	 * sent as the body of the request.
	 * @param {Function} cb A callback function which is called with the
	 * received JSON object or raw data as parameter. If there was any error
	 * then this function is not called.
	 * @param {Function} errorHandler An optional callback function wihch is
	 * called when the server returns an error. The function takes two
	 * parameters: result and status. If the HTTP status code was 200, then
	 * result is the JSON object and status is not set. If the HTTP status was
	 * not 200, then result is the status string and status is the HTTP status
	 * code. The function should return true when it handles the error.
	 * Otherwise, the default error handler specified by JSON.errorHandler will
	 * be called after this function returns. If this parameter is not specified,
	 * the default error handler is called directly.
	 * @param {Boolean} raw Specifies whether the response data should be
	 * passed to the callback as-is or first parsed as a JSON object. Defaults
	 * to the latter.
	 * @see #get
	 * @see #cancel
	 */
	put: function(uri, data, meta, cb, errorHandler, raw) {
		var request = {
			method: "PUT",
			uri: uri,
			contenttype: "text/javascript; charset=UTF-8",
			data: JSON.serialize(data),
			cb: cb,
			errorHandler: errorHandler,
			raw: raw,
			next: null
		};
		this.add(request);
		return request;
	},
	
	/**
	 * Cancels a previously enqueued request.
	 * If the request is already pending, its property
	 * <code>cancelled</code> is set to true, but the callbacks will be called
	 * anyway. Otherwise, the request is removed from the queue.
	 * @param {Object} request An object previously returned by one of
	 * {@link #get}, {@link #post} or {@link #put}.
	 * @type Boolean
	 * @return True if the request was already sent to the server.
	 */
	cancel: function(request) {
		if (request == this.first) {
			request.cancelled = true;
			return false;
		}
		for (var r = this.first; r; r = r.next)
			if (request == r.next) {
				r.next = request.next;
				return true;
			}
		return false;
	},

	/**
	 * @private
	 */
	remove: function() {
		if (this.first) {
			if (this.last == this.first) this.last = null;
			this.first = this.first.next;
		}
	},

	/**
	 * @private
	 */
	add: function(request) {
		if (!this.first)
			this.last = this.first = request;
		else
			this.last = this.last.next = request;
		if (!this.processing) this.process();
	},

	/**
	 * @private
	 */
	process: function() {
		JSON.count++;
		if (!(this.processing = this.first != null)) {
			if (!--JSON.count) triggerEvent("Loading", false);
			return;
		}
		var xmlhttp = this.getXmlHttp();
		var Self = this;
		xmlhttp.onreadystatechange = callback;
		xmlhttp.open(this.first.method, this.first.uri, true);
		if (this.first.contenttype)
			xmlhttp.setRequestHeader("Content-Type", this.first.contenttype);
		xmlhttp.send(this.first.data);
		if (JSON.count == 1) triggerEvent("Loading", true);
		function callback() {
			if (xmlhttp.readyState != 4) return;
			JSON.count--;
			xmlhttp.onreadystatechange = emptyFunction; // fixes IE memory leak
			var cb = Self.first.cb;
			var originalErrorHandler = Self.first.errorHandler;
			var errorHandler = originalErrorHandler ? function(result, status) {
				if (!originalErrorHandler(result, status))
					JSON.errorHandler(result, status);
			} : JSON.errorHandler;
			var raw = Self.first.raw;
			Self.remove();
			var result = {};
			if (xmlhttp.status != 200) {
				errorHandler(xmlhttp.statusText, xmlhttp.status);
				Self.process();
				return;
			}
			if (raw)
				result = xmlhttp.responseText;
			else {
				var s = "return " + xmlhttp.responseText;
				try {
					result = Function(s)();
				} catch (e) {
					//#. %s is the JavaScript error message.
					//#, c-format
					alert(format(_("Syntax error in server reply:\n%s"), e.message, s));
					Self.process();
					return;
				}
				if (result && typeof(result) == "object" && result.error) {
					errorHandler(result);
					Self.process();
					return;
				}
			}
			//try {
				cb(result);
			/*} catch(e) {
				Self.process();
				throw e;
			}*/
			Self.process();
		};
	},
	
	/**
	 * @private
	 */
	getXmlHttp: function() {
		alert(_("Your browser does not support AJAX."));
	}
};

JSON.errorHandler = function(result, status) {
	if (status)
		//#. HTTP Errors from the server
		//#. %1$s is the numeric HTTP status code
		//#. %2$s is the corresponding HTTP status text
		alert(format(_("Error: %1$s - %2$s"), status, result));
	else
		alert(formatError(result));
};

(function() {
	var xmlhttp = null;
	try {
		xmlhttp = new XMLHttpRequest();
		if (xmlhttp) {
			xmlhttp = null;
			JSON.prototype.getXmlHttp = function() { return new XMLHttpRequest(); };
		}
	} catch (e) {
		try {
			xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
			if (xmlhttp) {
				xmlhttp = null;
				JSON.prototype.getXmlHttp = function() {
					return new ActiveXObject("Msxml2.XMLHTTP");
				};
			}
		} catch (e) {
			try {
				xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
				if (xmlhttp) {
					xmlhttp = null;
					JSON.prototype.getXmlHttp = function() {
						return new ActiveXObject("Microsoft.XMLHTTP");
					};
				}
			} catch (e) {
				JSON.prototype.getXmlHttp();
			}
		}
	}
})();

  //////////////
 //  Login   //
//////////////
var bClickedLogin = false;
/**
 * @private
 */
var myjoin = new Join(function () {
	loadMessage("Rebuild Tree...", /*i18n*/
			"30");
	triggerEvent("RebuildTree");
	initAll2(login);
})

var loginready=myjoin.add();
var htmlload = new Join(myjoin.add());				
var htmljsload = new Join(myjoin.add(function() {
	fillInitObject();
}));
//var externalJoin = new Join(myjoin.add());

var jsload = new Join(myjoin.add(function() {
	preloadJSFiles();
}));
var cacheload = new Join(myjoin.add());
var fileloaded = function() {};
var wholeelement=new Object();
var rootebene=this;
var htmljsarray=new Array();
var jsarray=new Array();

function preloadJSFiles() {
	for(var i=0;i<jsarray.length;i++) {
		var node=wholeelement["js"][jsarray[i]]
		var d=newnode("script",null,{type : "text/javascript", text: node },[]);
		body.parentNode.getElementsByTagName("head")[0].appendChild(d);
	}
}
var searchedsizeids=new Object();
function fillInitObject() {
	function searchSizeNode(id) {
		if(searchedsizeids[id]) {return searchedsizeids[id]; }
		var searchobjects=new Array();
		searchobjects.push(init.size);
		for(var zaehler=0;zaehler<searchobjects.length;zaehler++) {
			var myel=searchobjects[zaehler];
			myel.length;
			for (var i1=0;i1<myel.length;i1++) {
				if(myel[i1]) {
					if(myel[i1].id == id) {
						searchedsizeids[id]=myel[i1].children;
						return searchedsizeids[id]
					} else {
						if(myel[i1].children.length) {
							searchobjects.push(myel[i1].children);
						}
					}
				}
			}
		}
		return null;
	}
	for(var i3=0;i3<htmljsarray.length;i3++) {
		var node=wholeelement["htmljs"][htmljsarray[i3]]	
		eval(node.node);
		if(!preload) { alert("Failure no preload var available") }
		for(var i in preload) {
			switch (i) {
				case "size" :
					if(preload[i].length>0) {
						var element;
						if(node.parent) {
							//Hier children object
							element=searchSizeNode(node.parent);						
						} else {
							element=init.size;
						}
						if(!element) {
							alert("Error in Fill size: parent id -" +node.parent);
						}
						for(var i2 in preload[i]) {
							element.push(preload[i][i2]);
						}
					}
					break;
				default	:
					for(var i2 in preload[i]) {
						init[i][i2]=preload[i][i2];
					}
					break;
			}	
		}
		delete preload;
	}
}
function loadFile(page,cb,params,sizetreeid) {
	var xmlhttp = JSON.prototype.getXmlHttp();
	function callback() {
		if (xmlhttp.readyState != 4) return;
		xmlhttp.onreadystatechange = emptyFunction; // fixes IE memory leak
		var s = xmlhttp.responseText;
		try { cb(s,params,sizetreeid); } 
		catch(e) { throw e; }
	}
	xmlhttp.onreadystatechange = callback;
	xmlhttp.open("GET",page, true);
	xmlhttp.send("");
}
var oIfrExt;
var externalJSFileLoaded;
function loadExternalScript(callback){
	externalJSFileLoaded = function (){callback();}
	oIfrExt = newnode("iframe",{display:"none"});
//		addDOMEvent(oIfrExt,'load',function (){alert("loaded");});//loaded
	body.appendChild(oIfrExt);
//		oIfrExt.contentWindow.test = function () {temp();};		
	oIfrExt.src = "netvibes_iframe.html";
}
function loadContents() {
	function storeHTMLFile(node) {
		function getIDs(obj) {
			function recursive(obj) {
				for(var i=obj.firstChild; i!=null; i=i.nextSibling) {
					if(i.nodeType==1) { 
						idValue=i.id;
						if(idValue) { 
							if(allnodes[i.id]) {
								alert("Internal Error: ID \"" + i.id + "\" used multiple times!");
								allnodes[i.id] = "Duplicate ID";
							} else {
								allnodes[i.id]=i; 
							}
						}
						recursive(i);
					}
				}
			}
			if(obj.id) { allnodes[obj.id]=obj; } 
			recursive(obj);
		}
		var el=document.createElement("div");
		el.innerHTML=node;	
		getIDs(el);
	}
	function storeHTMLJSFile(node,param,sizetreeid) {
		if(!wholeelement["htmljs"]) wholeelement["htmljs"]=new Object();
		wholeelement["htmljs"][param]= new Object();
		if(sizetreeid) { wholeelement["htmljs"][param].parent=sizetreeid; }
		wholeelement["htmljs"][param].node=node;
	}
	function storeJSFile(node,param) {
		if(!wholeelement["js"]) wholeelement["js"]=new Object();
		wholeelement["js"][param]=node;
	}
	function loadFileForCache(file) {
		loadFile(file,cacheload.add(),file);
	}
	function loadHTMLFile(page) {
		loadFile(page,htmlload.add(storeHTMLFile),null);
	}
	function loadHTMLJSFile(page,params,sizetreeid) {
		htmljsarray[htmljsarray.length]=params;
		loadFile(page,htmljsload.add(storeHTMLJSFile),params,sizetreeid);
	}
	function loadJSFile(page) {
		if(debug) { debugJSLoad(page)}  
		else {
			jsarray[jsarray.length]=page;
			loadFile(page,jsload.add(storeJSFile),page);
		}
	}
	
	
	function debugJSLoad(page) {
		fileloaded=jsload.add();
		var d=newnode("script",null,{type : "text/javascript"  , src : page });	
		body.parentNode.getElementsByTagName("head")[0].appendChild(d);
	}	


	loadMessage("Load Contents...", /*i18n*/
			"20");
	/* Prefill Joins */
	var tmparray = new Array();
	tmparray.push(htmlload.add());
	tmparray.push(htmljsload.add());
	tmparray.push(jsload.add());
	tmparray.push(htmlload.add());
	tmparray.push(cacheload.add());
//	tmparray.push(externalJoin.add());


	/*Load always needed contents */
	loadHTMLFile("mainpage.html");
	loadHTMLJSFile("mainpage.js","core");
	loadJSFile("concat_core.js");
	/* remove this */
	loadFileForCache("concat_all.js")

	
	/* Load portal contents */
	if(configGetKey("modules.portal.module")) {
		loadJSFile("js/portal/portalDnD.js");				
		loadJSFile("js/portal/portalClass.js");
		loadJSFile("js/portal/portal.js");
		loadJSFile("js/portal/portalitemClass.js");
	}

	/*Load mail contents*/
	if(configGetKey("modules.mail.module")) {
		loadHTMLFile("mail_core.html");
		loadHTMLJSFile("mail_core.js","mail_core","contentarea");
		loadJSFile("js/mail/mail.js");
		loadJSFile("js/mail/mailcore.js");
		if(configGetKey("fastgui.preload.mail")) {
			loadFileForCache("concat_mailnew.js");
			loadFileForCache("concat_maildetail.js");
			
			loadFileForCache("newMail.html");
			loadFileForCache("3rdparty/tinymce/jscripts/tiny_mce/tiny_mce.js");
			loadFileForCache("3rdparty/tinymce/jscripts/tiny_mce/themes/advanced/editor_template.js");
			
		}
	}
	/* Load mini CalendarContents */
	loadJSFile("js/calendar/calendarcommon.js");
	loadJSFile("js/calendar/calendarmini.js");
	loadJSFile("js/calendar/minicalendarClass.js");	
	loadHTMLFile("calendarmini.html");
	loadHTMLJSFile("calendarmini.js","cal_mini","below_folders");	 
	

	/*Load mail contents*/
	if(configGetKey("modules.calendar.module")) {
		loadHTMLFile("calendar_core.html");
		loadHTMLJSFile("calendar_core.js","cal_core","contentarea");
		loadJSFile("concat_calendar.js");
		if(configGetKey("fastgui.preload.calendar")) {
			loadFileForCache("concat_calendarnew.js");
			loadFileForCache("css/calendar/newappointment.css");
			loadFileForCache("newAppointment.html");
		}
	}
	if(configGetKey("modules.contacts.module")) {
		loadHTMLFile("contacts_core.html");
		loadHTMLJSFile("contacts_core.js","cont_core","contentarea");
		loadJSFile("js/contacts/contacts.js")
		if(configGetKey("fastgui.preload.contacts")) {
			loadFileForCache("concat_contactsnew.js");
			loadFileForCache("css/contacts/newcontact.css");
			loadFileForCache("newContact.html");
		}
		if(configGetKey("fastgui.preload.distributionlist")) {
			loadFileForCache("concat_distributionnew.js");
			loadFileForCache("newDistributionList.html");
			loadFileForCache("css/contacts/newcontact.css");
		}

	}
	if(configGetKey("modules.infostore.module")) {
		loadHTMLFile("infostore_core.html");
		loadHTMLJSFile("infostore_core.js","info_core","contentarea");
		loadJSFile("js/infostore/infolist.js");	
		loadJSFile("js/infostore/infosplit.js");
		loadJSFile("js/infostore/infostore.js");		
		loadJSFile("js/infostore/infostoredetail.js");				
		if(configGetKey("fastgui.preload.infostore")) {
			loadFileForCache("concat_infostorenew.js");
			loadFileForCache("newInfoItemMain.html");
			loadFileForCache("css/tasks/newtask.css");
		}

	}
	if(configGetKey("modules.mail.module") ||
	   configGetKey("modules.infostore.module") || 
   	   configGetKey("modules.calendar.module") || 
   	   configGetKey("modules.contacts.module") || 
   	   configGetKey("modules.tasks.module")) {
		loadHTMLFile("mailcheck.html");
		loadHTMLJSFile("mailcheck.js","mail_check","below_folders");
		loadJSFile("js/mail/mailcheck.js");	
	}

	if(configGetKey("modules.tasks.module")) {
		loadHTMLFile("tasks_core.html");
		loadHTMLJSFile("tasks_core.js","tasks_core","contentarea");
		loadJSFile("js/tasks/tasks.js")
		if(configGetKey("fastgui.preload.tasks")) {
			loadFileForCache("concat_tasksnew.js");
			loadFileForCache("newTask.html");
			loadFileForCache("css/tasks/newtask.css");
		}
	}
	

	
	
	/* Portal Classes */
	
	loadJSFile("js/sidepanel/folderprops.js")
	loadJSFile("js/sidepanel/configtree.js")
	loadJSFile("js/timezone.js");
	loadJSFile("concat_config.js")
	loadJSFile("js/reminder.js")
	loadJSFile("js/rightshandling.js")
	loadJSFile("js/AcceptDeny.js")
	loadJSFile("js/Alerts.js")
	loadJSFile("js/calendar/Conflicts.js")
	loadJSFile("js/ObjectCache.js")
//	if(bUWAEnabled)
//		loadExternalScript();
	for(var i=0;i<tmparray.length;i++) {
		tmparray[i]();
	}
}



function dologin(result) {
	session = result.session;
	loginready();
	var tmpJoin=new Join(loadContents);
	(new JSON()).get(AjaxRoot + "/config/modules?session=" + session, null,tmpJoin.add(function(obj) {
		if(!config) { config = new Object(); }
		if (obj) {
			config.modules = obj.data;
		}
	}));	
	/*(new JSON()).get(AjaxRoot + "/config/fastgui?session=" + session, null,tmpJoin.add(function(obj) {
		if(!config) { config = new Object(); }
		if (obj) {
			config.fastgui = obj.data;
		}
	}));*/	
	if(!config) { config = new Object(); }
	config.fastgui=new Object();
	config.fastgui.preload=new Object();
	config.fastgui.preload.mail=true;
	config.fastgui.preload.calendar=true;
	config.fastgui.preload.tasks=true;
	config.fastgui.preload.contacts=true;
	config.fastgui.preload.distributionlist=true;
	config.fastgui.preload.infostore=true;
}  

function login() {
	document.getElementById("loading_data_complete").style.display="none";
	document.getElementById("loading_data").style.display="";
	/*avoid double login*/
	if(bClickedLogin)
		return;
	bClickedLogin=true;
	/*avoid double login end*/	
	var form = document.getElementById("login");	
	new JSON().post(
		AjaxRoot + "/login?action=login", 
		{ name: form.username.value, password: form.password.value },
		null,
		function(result) { dologin(result); },
		function(result, status) {
			triggerEvent("OX_Login_Failed");
			document.getElementById("loading_data_complete").style.display="";
			document.getElementById("loading_data").style.display="none";
			if (!status) {
				if (result.code == "LGI-0006")
					alert(_("Login failed. Please check your user name and password and try again."));
				else
					alert(formatError(result));
			} else
				//#. HTTP Errors from the server
				//#. %1$s is the numeric HTTP status code
				//#. %2$s is the corresponding HTTP status text
				alert(format(_("Error: %1$s - %2$s"), status, result));
			bClickedLogin = false;
			return true;
		},
		null
	);
	return false;
}

function loggedIn() {
	//session = session_id;
	register("OX_Configuration_Loaded_Complete", function() {
		$("loginbutton").onblur = emptyFunction;
		$("loading_data_complete").style.display="";
		$("loading_data").style.display="none";
		$("loginScreen").style.display = "none";
		$("login").username.value="";
		$("login").password.value="";
		loadingComplete(true);
		setTimeout(function() {
			triggerEvent("OX_Switch_View",currentpath2.join("/"));
			 
		},0);
		appendNode("everything");
		
		register("Logout", logout);
	});
	
	triggerEvent("OX_Login");
}

/**
 * Popup windows call this to disable popup error messages.
 * @param Boolean main True if the function is called by the main window.
 * False or omitted for popup windows.
 */
function loadingComplete(main) {
	var loggingOut = false;
	JSON.errorHandler = function(result, status) {
		if(typeof savePending != "undefined" && savePending) savePending = false;
		if (status) {
			//#. HTTP Errors from the server
			triggerEvent("OX_New_Error", 2,
				//#. %1$s is the numeric HTTP status code
				//#. %2$s is the corresponding HTTP status text
				format(_("Error: %1$s - %2$s"), status, result));
		} else if (result.code.match(/^SES-02..$/)) {
			if (!loggingOut) {
				if (main) {
					loggingOut = true;
					alert(_("Your session has expired. Please log in again."));
					window.onbeforeunload = null;
					setTimeout( function() { window.location.replace(location.pathname); },0);
				} else
					alert(_("Your session has expired. You have to close this window."));
			}
		} else {
			newServerError(result,4,window.opener);
		}
	};
}
  ///////////////
 //  Logout   //
///////////////

/**
 * @private
 */
function logout() {
	if(configContainsKey("gui.global.save")) {
		var id=configGetKey("gui.global.save");
	}
	else {
		id=0;
	}
	switch (id) {
		case 0:
			LogoutPopup.commitno();
			break;
		case 2:
			LogoutPopup.openWindow();
			break;
		case 1:
			LogoutPopup.commityes();
			break;					
	}
}

  ///////////////////////
 //   Configuration   //
///////////////////////

register("OX_Login", function() {
	(new JSON()).get(AjaxRoot + "/config/?session=" + session, null, function(obj) {
		if (obj) {
			config = obj.data;
		} else {
			config = {};
		}
		triggerEvent('OX_Configuration_Load_Foldertree');
		triggerEvent('OX_Configuration_Loaded');
		triggerEvent('OX_Configuration_Loaded_Complete');
	});	
});


register("OX_Configuration_Loaded_Complete", function() {
	internalCache.getUsers([configGetKey("identifier")],
	function(cbObj){
		$("loggedinUser").firstChild.data = 
			format(_(" - %s"),cbObj[configGetKey("identifier")].display_name);
	});
});

  //////////////////////
 //   Current time   //
//////////////////////

/**
 * @type Number
 * @return the current time in milliseconds since 1970-01-01 00:00,
 * in the timezone of the logged in user.
 */
var now;

register("OX_Configuration_Loaded_Complete", function() {
	var offset = configGetKey("currentTime") - (new Date()).getTime();
	now = function() { return (new Date()).getTime() + offset; }
});

  ///////////////////////////
 //   Secondary windows   //
///////////////////////////

/**
 * Opens a new window with a specified URI.
 * Windows must have a deterministic title for automated tests to find them.
 * this functions automatically assigns them.
 * @param {String} uri The uri to open in the window.
 * @param {String} options The optionsl third parameter to window.open().
 * @type Object
 * @param Object a reference to a window object (optional)
 * @return The new window object.
 */
var newWindow = (function() {
	var windows = {};
	setInterval(function() {
		for (var i in windows) if (windows[i].closed) delete windows[i];
	}, 10000);
	var counter = 1;
	function serialize(id) {
		var parts = [];
		for (var i in id) parts.push(i + "=" + id[i]);
		return parts.sort().join("&");
	}
	return function newWindow(uri, options, id, nwin) {
		var nwin = nwin || window;
		if (id) {
			var sid = serialize(id);
			var win = windows[sid];
			if (win && win.closed) {
				delete windows[sid];
				win = null;
			}
			if (win)
				setTimeout(function() { win.focus(); }, 0);
			else
				win = windows[sid] = nwin.open(uri, "OX" + counter++, options);
		} else
			win = nwin.open(uri, "OX" + counter++, options);
		return win;
	}
})();

  /////////////////////////////
 //   Module Registration   //
/////////////////////////////

/**
 * Name of the currently displayed module.
 */
var activemodule = "portal";

var modules = new Array();
var moduleshash=new Object();
var modulesview =[];

function registerModule(name, text, priority) {
	if(!moduleshash[name]) {
		var obj={name: name, text: text, disable: false, priority: priority};
		modules.push(obj);
		modules.sort(function(a,b) { return a.priority-b.priority; });
		moduleshash[name]=obj;		
	} else {
		for(var i=0;i<modules.length;i++) {
			if(modules[i].name == name) {
				var obj={name: name, text: text, disable: false, priority: priority};
				modules[i]=obj;
				moduleshash[name]=obj;				
			}
		}
	}
}
function registerModuleStatic(name, text,priority) {
	if(!moduleshash[name]) {
		var obj={name: name, text: text, disable: true, priority: priority};
		modules.push(obj);
		modules.sort(function(a,b) { return a.priority-b.priority; });
		moduleshash[name]=obj;		
	} else {
		for(var i=0;i<modules.length;i++) {
			if(modules[i].name == name) {
				var obj={name: name, text: text, disable: true, priority: priority};
				modules[i]=obj;
				moduleshash[name]=obj;				
			}
		}
	}	
}
function registerModuleView(name, text, limit,adj) {
	modulesview.push({name: name,text: text, limit: limit,adj:adj});
}
registerModuleStatic("contacts", "Contacts",40); /*i18n*/
registerModuleStatic("calendar", "Calendar",30); /*i18n*/
registerModuleStatic("mail", "E-Mail",20); 	   /*i18n*/
registerModuleStatic("portal", "Start Page",10); 	   /*i18n*/
registerModuleStatic("infostore", "InfoStore",60); /*i18n*/
registerModuleStatic("tasks", "Tasks",50); /*i18n*/

var views = {name: "root", level: -1, children: {}};

var currentview = views;
var currentfullpath;
var currentpath2 = [];
var currentpath = [];

function registerView(name, show, enter, leave, hide, change) {
	var names = name.split("/");
	var view = views;
	for (var i = 0; i < names.length; i++) {
		var viewname = names[i];
		var nextview = view.children[viewname];
		if (!nextview)
			nextview = view.children[viewname] =
				{name: view.name + "/" + names[i], parent: view, level: i, children: {}};
		view = nextview;
	}
	if (!view.show) view.show=new Array();
	if (!view.enter) view.enter=new Array();
	if (!view.change) view.change=new Array();		
	if (!view.leave) view.leave=new Array();
	if (!view.hide) view.hide=new Array();
	view.show.push(show);
	view.enter.push(enter);
	view.change.push(change);
	view.leave.push(leave);
	view.hide.push(hide);
	return view;
}

function changeView(name,param) {
	var names = name.split("/");
	currentpath2 = name.split("/");
	for (var i = 0; i < names.length && i < currentpath.length && names[i] == currentpath[i]; i++);
	var kview = currentview;
	for (var j = currentview.level; j >= i; j--) {
		if(currentview.leave) {
			for (var n=0;n<currentview.leave.length;n++) {
				if (currentview.leave[n]) {
					currentview.leave[n](param);
				}
			}
		}
		currentview = currentview.parent;
	}
	for (var k = kview.level; k >= i; k--) {
		if(kview.hide) {
			for (var n=0;n<kview.hide.length;n++) {
				if (kview.hide[n]) {
					kview.hide[n](param);
				}
			}
		} 
		kview = kview.parent;
	}
	activemodule=names[0];
	currentfullpath = name;
	var tmpview=views;
	var issame=true;
	for (var z=0 ;z <= k ; z++) {
		tmpview = tmpview.children[currentpath[z]];
		if(tmpview.change)
		{
			for (var n=0;n<tmpview.change.length;n++) {
				if (tmpview.change[n]) {
					tmpview.change[n](param);
				} 
			}
		}
	}		
	
	for (k++; k < names.length; k++) {
		kview = kview.children[names[k]];
		if (!kview)	{
			
			while(names.length>k) {
				names.pop();
			}
			break;
		}
		else
		{
			if(kview.show) {
				for (var n=0;n<kview.show.length;n++) {
					if (kview.show[n]) {
						kview.show[n](param);
					} 
				}
			}
		}
	}
	resizeHandler();
	for (j++; j < names.length; j++) {
		currentview = currentview.children[names[j]];
		if(currentview) {
			oldcurrentview=currentview;
			if(currentview.enter) {
				for (var n=0;n<currentview.enter.length;n++) {
					if (currentview.enter[n]) {
						currentview.enter[n](param);
					} 
				}
			}
		}
	}

	currentpath = names;
	return currentview;
}

  /////////////////////
 //   Linked list   //
/////////////////////

/**
 * Creates a linked list.
 * @class Double-linked list.
 * Not all methods of a typical linked list are implemented. The links are
 * stored in the properties <code>next</code> and <code>prev</code> of each
 * item.
 */
function LinkedList() {
	/**
	 * The first item. Null if the list is empty.
	 */
	this.first = null;

	/**
	 * The last item. Null if the list is empty.
	 */
	this.last = null;
}

LinkedList.prototype = {
	/**
	 * Adds an item to the end of the list.
	 * @param {Object} item The item to add. Two properties named
	 * <code>prev</code> and <code>next</code> will be added to the item.
	 * @type Boolean
	 * @return True if the list was empty before the call.
	 */
	addLast: function(item) {
		item.next = null;
		item.prev = this.last;
		if (this.last)
			this.last.next = item;
		else
			this.first = item;
		this.last = item;
		return this.first === item;
	},
	
	/**
	 * Removes and returns the first item from the list.
	 * @return The removed item or null if the list was already empty.
	 */
	removeFirst: function() {
		var item = this.first;
		if (item) {
			this.first = item.next;
			if (this.first)
				this.first.prev = null;
			else
				this.last = null;
		}
		return item;
	},
	
	/**
	 * Removes and returns the last item from the list.
	 * @return The removed item or null if the list was already empty.
	 */
	removeLast: function() {
		var item = this.last;
		if (item) {
			this.last = item.prev;
			if (this.last)
				this.last.next = null;
			else
				this.first = null;
		}
		return item;
	},
	
	/**
	 * Removes an arbitrary item from the list.
	 * @param {Object} item The item to be removed from the list.
	 */
	remove: function(item) {
		if (item.next) item.next.prev = item.prev;
		if (item.prev) item.prev.next = item.next;
		if (item === this.first) this.first = item.next;
		if (item === this.last) this.last = item.prev;
	},
	
	/**
	 * Cuts off all nodes previous to the specified node.
	 * Specifying null clears the list.
	 * @param {Object} newfirst The new first node of the list.
	 */
	setFirst: function(newfirst) {
		if (newfirst) newfirst.prev = null; else this.last = null;
		this.first = newfirst;
	},
	
	/**
	 * Inserts a node before another node.
	 * @param {Object} newitem The item to be inserted.
	 * @param {Object} nextitem The item before which the new item should be
	 * inserted.
	 */
	insertBefore: function(newitem, nextitem) {
		if (nextitem === this.first) {
			this.first = newitem;
			newitem.prev = null;
		} else {
			nextitem.prev.next = newitem;
			newitem.prev = nextitem.prev;
		}
		newitem.next = nextitem;
		nextitem.prev = newitem;
	}
};

  ///////////////////////////////////////
 //   Global mouse cursor functions   //
///////////////////////////////////////

var setMouseCursor, removeMouseCursor;

(function() {
	var cursors = new LinkedList();
	cursors.addLast({cursor: ""});

	/**
	 * Sets the mouse cursor.
	 * @param {String} cursor The CSS specification of the cursor.
	 * @type Object
	 * @return An object which is later used to remove the mouse cursor.
	 * @see #removeMouseCursor
	 */
	setMouseCursor = function(cursor) {
		var item = {cursor: cursor};
		cursors.addLast(item);
		body.style.cursor = cursor;
		return item;
	}

	/**
	 * Removes a previously set mouse cursor.
	 * @param {Object} item An object returned by a previous call to
	 * {@link #setMouseCursor}.
	 */
	removeMouseCursor = function(item) {
		if (item === cursors.last) {
			cursors.remove(item);
			body.style.cursor = cursors.last.cursor;
		} else
			cursors.remove(item);
	}
})();

  /////////////////////////////////////////
 //   "Loading" Icon and mouse cursor  //
/////////////////////////////////////////

register("Loading", (function() {
	var cursor = null;
	return function(loading) {
		var img = $("loading");
		if(img){
			img.src = loading ? "img/toolbar/ox_animated.gif" : "img/toolbar/tb_loading.gif";
			img.alt = loading ? _("Loading...") : _("No activity");
			img.title = img.alt;
		}
		if (!document.all) { // Changing the cursor triggers mouseup in IE
			if (loading) {
				if (!cursor) cursor = setMouseCursor("wait");
			} else if (cursor) {
				removeMouseCursor(cursor);
				cursor = null;
			}
		}
	};
})());

  /////////////////////////////////
 //   Most-Recently-Used list   //
/////////////////////////////////

/**
 * Creates an MRU list.
 * @class Most-recently-used list (MRU list).
 * An MRU list limits the number of stored items by discarding
 * the least-recently referenced item, when storing a new item would otherwise
 * exceed the limit.
 * @param {Int} size The maximum size of the list.
 */
function MRUList(size) {
	if (!size) return new SimpleStorage();
	this.free = size;
	this.cache = {};
	this.list = new LinkedList();
}

MRUList.prototype = {
	/**
	 * Retrieves an item from the list.loadconfig();
	 * @param key The key of the item to retrieve.
	 * @return The retrieved item or <code>undefined</code> if the item was not
	 * found.
	 */
	get: function(key) {
		var item = this.cache[key];
		if (!item) return undefined;
		this.list.remove(item);
		this.list.addLast(item);
		return item.data;
	},

	/**
	 * Stores an item in the list.
	 * @param key The key under which to store the item.
	 * @param value The item to store in the list.
	 */
	set: function(key, value) {
		var item = this.cache[key];
		if (item)
			item.data = value;
		else {
			item = {key: key, data: value};
			this.list.addLast(item);
			this.cache[key] = item;
			if (this.free)
				this.free--;
			else
				delete this.cache[this.list.removeFirst().key];
		}
	},

	/**
	 * Removes an item from the list.
	 * @param key The key of the item to remove.
	 */
	remove: function(key) {
		var item = this.cache[key];
		if (!item) return;
		this.list.remove(item);
		delete this.cache[key];
		this.free++;
	}
};

  /////////////////
 //   Storage   //
/////////////////

function SimpleStorage() {
	this.data = {};
}

SimpleStorage.prototype = {
	get: function(key) { return this.data[key]; },
	set: function(key, value) { this.data[key] = value; },
	remove: function(key) { delete this.data[key]; }
}

/**
 * @param {Number} timestamp The timestamp of the current storage content.
 * @param {Array} ids An array with object IDs of all items.
 * @param {String} uri The URI from which items will be fetched on-demand.
 * @param {Int} prefetch Number of additional items to fetch before the first
 * and after the last explicitly requested item. Defaults to 0.
 * @param {Int} cacheSize Maximum number of items to cache. Unlimited
 * if zero or not specified.
 * @param {Function} serialize Optional function to convert object IDs to
 * strings which can be used as keys in hash tables. Defaults to identity.
 * @param {Function} makeID Optional function to convert an item to
 * an object ID. Defaults to returning the first array entry.
 * @param {Object} extraData Optional object containing extra data indexed by
 * object ID. Extra data handling is disabled if this parameter is not specified.
 * @param {Function} updateItem Optional function to update a dynamically
 * retrieved item with extra data. It takes an extra data item and an item as
 * parameters and returns the updated item, which may be the same instance as
 * the second parameter. Must be specified whenever extraData is specified.
 */
function Storage(timestamp, ids, uri, prefetch, cacheSize, serialize, makeID, extraData, updateItem) {
	/**
	 * Timestamp of the storage content.
	 */
	this.timestamp = timestamp;
	
	/**
	 * Array with object IDs.
	 */
	this.ids = ids;
	
	/**
	 * A mapping from serialized object IDs to their indices in the storage.
	 */
	this.indices = {};
	
	/**
	 * URI used to fetch data.
	 */
	this.uri = uri;
	
	/**
	 * @private
	 */
	this.prefetch = prefetch;

	/**
	 * @private
	 */
	this.cacheSize = cacheSize;
	
	/**
	 * @private
	 */
	this.serialize = serialize || identity;
	
	/**
	 * @private
	 */
	this.makeID = makeID || function(x) { return x[0]; };
	
	/**
	 * @private
	 */
	this.extraData = extraData;

	/**
	 * @private
	 */
	this.updateItem = updateItem;
	
	/**
	 * A callback function to call instead of the built-in update in
	 * {@link StorageCache.update}.
	 */
	this.serverUpdates = null;

	/**
	 * @private
	 */
	this.data = new MRUList(cacheSize);

	/**
	 * Events triggered by this storage.
	 * <dl><dt>Changed</dt><dd>The contents were changed using one of
	 * the methods of this object.<br>Parameters:
	 * <ul><li>from - Lower inclusive limit of the changed range.</li>
	 * <li>to - Upper exlcusive limit of the changed range.</li></ul></dd></dl>
	 */
	this.events = new Events();

	/**
	 * @private
	 */
	this.from = 0x7fffffff;

	/**
	 * @private
	 */
	this.to = 0;

	/**
	 * @private
	 */
	this.json = new JSON();
	
	this.requests = new LinkedList();
	
	this.processing = false;
	
	this.uid = Storage.uid++;

	for (var i = 0; i < ids.length; i++)
		this.indices[this.serialize(ids[i])] = i;
}

Storage.uid = 0;

Storage.prototype = {
	/**
	 * Iterates over items of the storage.
	 * Data is fetched from the server on demand.
	 * @param {Array} ids An array with object IDs of iterated items.
	 * @param {Function} clear A callback which is called with the index of
	 * a missing item if the data for that item needs to be fetched from
	 * the server.
	 * @param {Function} set A callback function which is called with the index
	 * and the data of each item specified in <code>ids</code>.
	 * @param {Function} final_cb A callback function which is called after
	 * the iteration completes.
	 * @param {Int} prefetch Number of items to speculatively fetch before
	 * the first and after the last explicitly requested item. Defaults to 0.
	 * @type Object
	 * @return An object which can be used to cancel the pending request, or
	 * <code>undefined</code> if the request could be processed synchronously.
	 */
	newIterate: function(ids, clear, set, final_cb, prefetch) {
		var newids = {};
		var min = Infinity, max = -1;
		for (var i = 0; i < ids.length; i++) {
			var id = this.serialize(ids[i]);
			var ix = this.indices[id];
			min = Math.min(min, ix);
			max = Math.max(max, ix);
			var d = this.data.get(id);
			if (d)
				set(ix, d);
			else {
				newids[id] = ix;
				clear(ix);
			}
		}
		if (isEmpty(newids)) {
			if (final_cb) final_cb();
			return;
		}
		for (var i = Math.max(0, min - prefetch); i < min; i++) {
			var id = this.serialize(this.ids[i]);
			var d = this.data.get(id);
			if (!d) newids[id] = i;
		}
		var last = Math.min(this.ids.length - 1, max + prefetch);
		for (var i = max + 1; i <= last; i++) {
			var id = this.serialize(this.ids[i]);
			var d = this.data.get(id);
			if (!d) newids[id] = i;
		}
		var newrequest = {ids: newids, set: set, final_cb: final_cb};
		if (this.requests.addLast(newrequest) && !this.processing)
			this.process();
		return newrequest;
	},
	
	/**
	 * @private
	 */
	process: function() {
		var idset = {};
		for (var r = this.requests.first; r; r = r.next)
			for (var id in r.ids)
				if (!(id in idset)) idset[id] = this.ids[r.ids[id]];
		var oldrequests = this.requests;
		this.requests = new LinkedList();
		this.processing = true;
		var ids = [];
		for (var id in idset) ids.push(idset[id]);
		var Self = this;
		this.json.put(this.uri, ids, null, function(obj) {
			var items = obj.data;
			var id, item;
			var makeIdItem = Self.extraData ? function(i) {
				id = Self.serialize(Self.makeID(items[i]));
				item = Self.updateItem(Self.extraData[id], items[i]);
			} : function (i) {
				item = items[i];
				id = Self.serialize(Self.makeID(item));
			}
			for (var i in items) {
				makeIdItem(i);
				Self.data.set(id, item);
				for (var r = oldrequests.first; r; r = r.next)
					if (id in r.ids) {
						r.set(r.ids[id], item);
						delete(r.ids[id]);
					}
				for (var r = Self.requests.first; r; r = r.next)
					if (id in r.ids) {
						r.set(r.ids[id], item);
						delete(r.ids[id]);
					}
			}
			for (var r = oldrequests.first; r; r = r.next)
				if (r.final_cb) r.final_cb();
			for (var r = Self.requests.first; r; r = r.next) {
				if (isEmpty(r.ids)) {
					if (r.final_cb) r.final_cb();
					Self.requests.remove(r);
				}
			}
			if (Self.requests.first)
				Self.process();
			else
				Self.processing = false;
		});
	},
	purge : function (removeids) { 
		for (var i= 0; i< removeids.length; i++) {
			var serid=this.serialize(removeids[i]);
			this.data.remove(serid);
		}
	},
	/**
	 * Cancels a previously started iteration request.
	 * @param {Object} request An iteration request previously returned by
	 * a call to {@link #newIterate}.
	 */
	cancel: function(request) { this.requests.remove(request); },

	/**
	 * Updates a set of entries. Only entries in the local cache are updated.
	 * @param {Array} indices An array with object IDs of items to update.
	 * @param {Function} callback A function called with the index and
	 * the current data element as parameters and which should return
	 * the updated data element.
 	 */
	localUpdate: function(ids, callback) {
		var Self = this;
		var min = Infinity;
		var max = -1;
		for (var i in ids) {
			var id = this.serialize(ids[i]);
			var index = this.indices[id];
			var data = this.data.get(id);
			if (!data) continue;
			var newdata = callback(index, data);
			if (!newdata) continue;
			this.data.set(id, newdata);
			min = Math.min(min, index);
			max = Math.max(max, index);
		}
		if (min <= max) this.postChanged(min, max + 1);
	},
	
	/**
	 * @param {Number} timestamp The timestamp of the updated data.
	 * @param {Array} ids New array of object IDs. Each array element is itself
	 * an array with the columns required to represent the object ID.
	 * @param {Array} updates Array with updated items. In each item, the field
	 * or element specified by {@link #idField} must contain the object ID of
	 * the item.
	 * @param {Object} extraData Optional updated extra data. If not specified,
	 * the extra data is not modified. Extra data handling is not applied to
	 * the content of updates.
	 */
	update: function(timestamp, ids, updates, extraData) {
		this.timestamp = timestamp;
		if (extraData) this.extraData = extraData;
		var modified = {}, data = new MRUList(this.cacheSize);
		for (var i in updates)
			modified[this.serialize(this.makeID(updates[i]))] = updates[i];
		var maxLength = Math.max(this.ids.length, ids.length);
		this.ids.length = ids.length;
		this.indices = {};
		for (var i = 0; i < ids.length; i++) {
			var id = this.serialize(this.ids[i] = this.makeID(ids[i]));
			var item = modified[id] || this.data.get(id);
			this.indices[id] = i;
			if (item) data.set(id, item);
		}
		this.data = data;
		this.postChanged(0, maxLength);
	},
	
	/**
	 * Appends items at the end of the list.
	 * @param {Array} items An array with the items to append.
	 * @param {Object} extraData An optional object with additional extra data.
	 */
	append: function(items, extraData) {
		var from = this.ids.length;
		var len = items.length;
		for (var i in items) {
			var id = this.makeID(items[i]);
			var sid = this.serialize(id);
			this.indices[sid] = this.ids.length;
			this.ids.push(id);
			this.data.set(sid, items[i]);
		}
		if (extraData)
			for (var i in extraData) this.extraData[i] = extraData[i];
		this.postChanged(from, this.ids.length);
	},
	
	/**
	 * Removes a range of items from the list.
	 * @param from Lower inclusive limit of the range.
	 * @param to Upper exclusive limit of the range. Defaults to from + 1.
	 */
	remove: function(from, to) {
		if (to == undefined) to = from + 1;
		for (var i = from; i < to; i++) {
			var id = this.serialize(this.ids[i]);
			this.data.remove(id);
			delete this.indices[id]
			if (this.extraData) delete this.extraData[id];
		}
		var oldlen = this.ids.length;
		this.ids.splice(from, to - from);
		for (var i = to; i < this.ids.length; i++)
			this.indices[this.serialize(this.ids[i])] = i;
		this.postChanged(from, oldlen);
	},
	
	/**
	 * Removes a set of IDs and the corresponding items from the storage.
	 * @param {Array} ids An array with object ID of items to be removed.
	 */
	removeIDs: function(ids) {
		var indices = new Array(ids.length);
		for (var i = 0, j = 0; i < ids.length; i++) {
			var ix = this.indices[this.serialize(ids[i])];
			if (ix !== undefined) indices[j++] = ix;
		}
		indices.length = j;
		this.removeIndices(indices);
	},
	
	/**
	 * Removes a set of items specified by an array of indices.
	 * The array is modified!
	 * @param {Array} indices An array with indices of items to be removed.
	 */
	removeIndices: function(indices) {
		if (!indices.length) return;
		indices.sort(function(a, b) { return a - b; });
		indices.push(Infinity);
		var n = 0, next = indices[0], dest = next;
		this.postChanged(next, this.ids.length);
		for (var src = dest; src < this.ids.length; src++) {
			if (src < next) {
				var id = this.ids[src];
				this.ids[dest] = id;
				this.indices[this.serialize(id)] = dest++;
			} else {
				var id = this.serialize(this.ids[src]);
				this.data.remove(id);
				delete this.indices[id];
				if (this.extraData) delete this.extraData[id];
				next = indices[++n];
			}
		}
		this.ids.length = dest;
	},
	
	getIndex: function(id) { return this.indices[this.serialize(id)]; },
	
	getSID: function(index) {
		var id = this.ids[index];
		if (id) return this.serialize(id);
	},
	
	/**
	 * @private
	 */
	postChanged: function(from, to) {
		this.from = Math.min(this.from, from);
		this.to = Math.max(this.to, to);
		var Self = this;
		this.events.post("Changed",
			function() {
				var from = Self.from;
				Self.from = 0x7fffffff;
				return from;
			},
			function() {
				var to = Self.to;
				Self.to = 0;
				return to;
			}
		);
	}
};

  ///////////////////
 //   Selection   //
///////////////////

/**
 * Selection as a set of object IDs.
 */
function Selection() {
	/**
	 * Number of selected items.
	 */
	this.count = 0;

	/**
	 * A map from serialized object IDs to object IDs.
	 * @private
	 */
	this.data = {};
	
	/**
	 * Index of the selection anchor.
	 * The anchor is used for range selects with the Shift key.
	 * @private
	 */
	this.anchor = 0;
	
	/**
	 * Events triggered by this Selection.
	 * <dl><dt>Selected</dt><dd>The selection has changed. Parameters:
	 * <ul><li>Number of selected elements.</li></ul></dd></dl>
	 */
	this.events = new Events();
	
	var Self = this;
	this.changed_cb = function() {
		var oldcount = Self.count;
		for (var id in Self.data) {
			if (!(id in Self.storage.indices)) {
				delete Self.data[id];
				Self.count--;
			}
		}
		if (Self.count != oldcount)
			Self.events.post("Selected", Self.count);
	};
}

Selection.prototype = {
	/**
	 * Returns the selection status of a single item.
	 * @param {Int} index The index of the queried item.
	 * @return Boolean
	 */
	get: function(index) {
		if (!this.storage) console.error("Selection.get without storage");
//		return this.serialize(this.storage.ids[index]) in this.data;
		var id = this.storage.ids[index];
		if (!id) return false;
		return this.serialize(id) in this.data;
	},
	
	/**
	 * Returns the selection status of a single item. returns false if ths index
	 * is out of range.
	 * @param {Int} index The index of the queried item.
	 * @return Boolean
	 */
	get2: function(index) {
		if (!this.storage) console.error("Selection.get without storage");
		var id = this.storage.ids[index];
		if (!id) return false;
		return this.serialize(id) in this.data;
	},
	
	/**
	 * Toggles the selection status of a single item.
	 * @param {Int} index The index of the toggled item.
	 * @return the new selection status of the toggled item.
	 * @private
	 */
	toggle: function(index) {
		var id = this.storage.ids[index];
		var sid = this.serialize(id);
		var oldval = sid in this.data;
		if (oldval) delete this.data[sid]; else this.data[sid] = id;
		this.count += oldval ? -1 : 1;
		this.events.post("Selected", this.count);
		return !oldval;
	},
	
	/**
	 * Deselects the specified serialized object IDs.
	 * @param {Object} sids An object with serialized object IDs to deselect
	 * as keys.
	 */
	deselectSIDs: function(sids) {
		for (var sid in sids) {
			this.count -= (sid in this.data) ? 1 : 0;
			delete this.data[sid];
		}
		this.events.post("Selected", this.count);
	},

	/**
	 * Clears the entire selection.
	 * @private
	 */
	reset: function() {
		this.data = {};
		this.count = 0;
		this.events.post("Selected", this.count);
	},
	
	/**
	 * Selects multiple items.
	 * @param {Int} from Lower inclusive limit of the selected range.
	 * @param {Int} to Upper exclusive limit of the selected range.
	 * @private
	 */
	select: function(from, to) {
		for (var i = from; i < to; i++) {
			var id = this.storage.ids[i];
			var sid = this.serialize(id);
			if (!(sid in this.data)) {
				this.count++;
				this.data[sid] = id;
			}
		}
		this.events.post("Selected", this.count);
	},

	/**
	 * Returns an array with object IDs of selected items.
	 * @type Array
	 * @return an array with object IDs of selected items.
	 */
	getSelected: function() {
		var ids = [];
		for (var id in this.data) ids.push(this.data[id]);
		return ids;
	},
	
	/**
	 * Handles a mouse click.
	 * @param {Number} index Index of the clicked item.
	 * @param {Boolean} ctrl Whether the control key was held down at the time
	 * of the click.
	 * @param {Boolean} shift Whether the shift key was held down at the time
	 * of the click.
	 */
	click: function(index, ctrl, shift) {
		if (!this.storage) console.error("Selection.click without storage");
		if (index < 0 || index >= this.storage.ids.length) {
			this.reset();
			return;
		}
		if (!ctrl) this.reset();
		if (shift)
			this.select(Math.min(this.anchor, index),
				Math.max(this.anchor, index) + 1);
		else {
			this.toggle(index);
			this.anchor = index;
		}
	},
	
	/**
	 * Sets the storage which is used to convert indices to object IDs.
	 * @param {Storage} storage The storage, or null.
	 */
	setStorage: function(storage) {
		if (this.storage)
			this.storage.events.unregister("Changed", this.changed_cb);
		this.storage = storage;
		if (storage) {
			this.serialize = storage.serialize
			storage.events.register("Changed", this.changed_cb);
			this.changed_cb();
//			this.click(0);
		} else {
			this.serialize = null;
//			this.reset();
		}
	},
	
	/**
	 * @deprecated
	 */
	getID: function() {
		for (var i in this.data) return this.data[i];
	}
};

  ///////////////////////
 //   Storage cache   //
///////////////////////

/**
 * A cache of Storage containers.
 * TODO
 */
function StorageCache(maxCount) {
	/**
	 * @private
	 */
	this.cache = new MRUList(maxCount);
	
	/**
	 * @private
	 */
	this.json = new JSON();
	
	/**
	 * @private
	 */
	this.storage = null;
	
	/**
	 * The current storage.
	 * The last retrieved storage becomes the current storage.
	 * If it was retrieved using {@link #get} and not {@link #setCurrent} then
	 * it can be updated by calling {@link #update}.
	 */
	this.current = null;
}

// TODO: read from user 
StorageCache.prefetch = 20;
StorageCache.cacheSize = 1000;

(function() {
	function makeURI(uri, params) {
		var paramArray = [];
		for (var i in params) paramArray.push(i);
		paramArray.sort();
		var uriArray = [uri, "?"];
		for (var i = 0; i < paramArray.length; i++) {
			uriArray.push(paramArray[i] + "=" +
				encodeURIComponent(params[paramArray[i]]));
			uriArray.push("&");
		}
		uriArray.pop();
		return uriArray.join("");
	}

	StorageCache.prototype = {
		/**
		 * Asynchronously switch to a new storage.
		 * @param {String} uri the base URI of the storage,
		 * e.&nbsp;g. &quot;/ajax/tasks&quot;.
		 * @param {Object} params URI parameters as fields of an object.
		 * <code>params.session</code> is added automatically.
		 * <code>params.columns</code> must contain a comma-separated list of
		 * column(s) required for the object ID and defaults to &quot;1,20&quot;.
		 * @param {Function} callback A function which is called with the new
		 * storage as parameter.
		 * @param {String} columns A comma-separated list of additional columns to
		 * store after the ones specified in <code>params.columns</code>.
		 * Defaults to no additional columns.
		 * @param {Boolean} cached Specifies whether this object should be cached
		 * and/or retrieved from the cache. Defaults to <code>true</code>.
		 * @param {Function} serialize A function for converting complex object IDs
		 * (e.&nbsp;g. for the calendar) to strings. Defaults to concatenating
		 * the first two array elements with a dot.
		 * @param {Function} makeID A function for converting items to
		 * object IDs. Defaults to returning the first two array elements as
		 * an object.
		 * @param {Object} putBody An object which is serialized and used in
		 * the body of an HTTP PUT request. If not specified, an HTTP GET
		 * request is performed instead.
		 * @param {Function} makeExtra A function for converting object IDs from
		 * the initial request to extra data items as required by the updateItem
		 * function. Extra data handling is disabled if this parameter is
		 * not specified.
		 * @param {Function} updateItem A function for copying extra data from
		 * the initial request into items which are retrieved on demand. this is
		 * useful when some columns are available in the initial request, but
		 * not from the &quot;list&quot; action, e.&nbsp;g. indentation level in
		 * the threaded mail view. This function takes the extra data and
		 * an item as parameters and returns the updated item, which may be
		 * the same instance as the second parameter. Must be specified whenever
		 * makeExtra is specified.
		 */
		get: function(uri, params, callback, columns, cached, serialize, makeID, putBody, makeExtra, updateItem) {
			this.putBody = putBody;
			this.uri = uri;
			if (!params.columns) params.columns = "1,20";
			params.session = session;
			this.params = {};
			for (var i in params) this.params[i] = params[i];
			var completeURI = makeURI(uri, params);
			this.columns = columns;
			params.columns = params.columns + "," + columns;
			var key  = makeURI(uri, params);
			if (cached == undefined) cached = true;
			this.serialize = serialize || function(x) { return x.folder + "." + x.id; };
			this.makeID = makeID || function(x) { return {id: x[0], folder: x[1]}; };
			this.storage = cached ? this.cache.get(key) : null;
			if (!this.storage) {
				var Self = this;
				function cb_json(obj) {
					if (obj.error) return;
					var ids = new Array(obj.data.length);
					if (makeExtra) {
						Self.extraData = {};
						for (var i = 0; i < obj.data.length; i++) {
							var id = ids[i] = Self.makeID(obj.data[i]);
							Self.extraData[Self.serialize(id)] = makeExtra(obj.data[i]);
						}
					} else {
						Self.extraData = null;
						for (var i = 0; i < obj.data.length; i++)
							ids[i] = Self.makeID(obj.data[i]);
					}
					Self.current = Self.storage = new Storage(obj.timestamp, ids,
						makeURI(uri, {action: "list", session: session,
						              columns: columns ? params.columns : this.params.columns}),
						StorageCache.prefetch, StorageCache.cacheSize,
						Self.serialize, Self.makeID, Self.extraData, updateItem);
					if (cached) Self.cache.set(key, Self.storage);
					callback(Self.storage);
				}
				if(this.putBody) {
					this.json.put(completeURI, this.putBody, null, cb_json);
				} else {
					this.json.get(completeURI, null, cb_json);
				}
			} else {
				this.current = this.storage;
				callback(this.storage);
			}
		},
		
		/**
		 * Updates the current storage.
		 * @param {Function} callback Optional callback which is called after
		 * the update completes. Not called if there is no current storage.
		 */
		update: function(callback) {
			if (!this.storage) return;
			if (this.storage.serverUpdates) {
				this.storage.serverUpdates();
				return;
			}
			var storage = this.storage;
			var ids, updateObj;
			var join = new Join(cb_forUpdate);
			var all_cb = join.add(function(obj) { ids = obj.data; });
			var update_cb = join.add (function(obj) { updateObj = obj; });
			
			if(this.putBody) {
				this.json.put(makeURI(this.uri, this.params), this.putBody, null, all_cb);
			} else {
				this.json.get(makeURI(this.uri, this.params), null, all_cb);
			}
			
			var p = {};
			for (var i in this.params) p[i] = this.params[i];
			p.action = "updates";
			p.columns = this.columns ? p.columns + "," + this.columns : p.columns;
			p.ignore = "deleted";
			p.timestamp = this.storage.timestamp;
			var Self = this;
			if(this.putBody) {
				this.json.put(makeURI(this.uri, p), this.putBody, null, update_cb);
			} else {
				this.json.get(makeURI(this.uri, p), null, update_cb);
			}

			function cb_forUpdate() {
				if (Self.makeExtra) {
					var extraData = {};
					for (var i in ids)
						extraData[Self.serialize(Self.makeID(ids[i]))] = Self.makeExtra(ids[i]);
				}
				storage.update(updateObj.timestamp, ids, updateObj.data, extraData);
				if (callback) callback();
			}
		},
		
		setCurrent: function(storage) {
			this.storage = null;
			this.current = storage;
		}
	};
})();


  //////////////////
 //   Quickinfo  //
//////////////////

/**
 * Events object for the help panel event.<b> 
 * The event is called "help" and has a single parameter:
 * the string to display (already translated).
 */
var helpevents = new Events();

register("OX_Show_Help_Panel",function(active) {
	(active ? addDOMEvent : removeDOMEvent)(body, "mouseover", handler);
	var currentnode = null;
	function handler(e) {
		var node = e.target || e.srcElement;
		var text;
		while (node) {
			if (node.quickinfo) {
				text = node.quickinfo;
				break;
			}
			if (node.id) {
				text = init.help[node.id];
				if (text) break;
			}
			node = node.parentNode;
		}
		if (node == currentnode) return;
		helpevents.post("help", node ? _(text) : "");
		currentnode = node;
	}
});

  //////////////////////
 //   Tab function   //
//////////////////////

/**
 * Register the Tabs
 * @param {Array} the tablists
 * @param {Array} the panellist
 * @param {Array} the ventlist
 */
function setTabLists(tabArray, panelArray, eventArray)
{
	tabsList = tabArray;
	panelsList = panelArray;
	eventList = eventArray;
}

/**
 * change Content und Highlight Tab
 * @param {String} id from Div
 * @param {String} id from Tab
 */
function changeTab(tab, panel,disable)
{
	if(!$(tab)) return;
	if(disable) return
	for(tabElementNr in tabsList)
	{
		tabElement = tabsList[tabElementNr];
		panelElement = panelsList[tabElementNr];
		if(eventList != null)
			eventElement = eventList[tabElementNr];
		if(tabElement != tab)
		{
			if($(tabElement))
			{
				$(tabElement).style.display = 'none';				
			}
			if($(panelElement))
			{
				classNameNew = (tabElementNr == 0)? 'tabPanelFirst' : 'tabPanel';
				$(panelElement).className = classNameNew;
			}						
		} else {
			if($(tabElement))
				$(tabElement).style.display = 'block';
				if(eventList != null)
					triggerEvent(eventElement[0], eventElement[1]);			
				
			if($(panelElement))
			{
				classNameNew = (tabElementNr == 0)? 'tabPanelFirstHi' : 'tabPanelHi';
				$(panelElement).className = classNameNew;
			}
		}
	}
	triggerEvent("OX_ChangeTabs_GUI",tab);
}

  //////////////////////////
 //   Set Tag Function   //
//////////////////////////

/**
 * changes the tag (color_label) of an object
 * @param {Number} the tag to set
 * @param {Array} ids of tasks, appointment or contact to change
 * @param {String} timestamp of last sync
 * @param {Function} callback when update is finished
 */
function setTag(tag, ids, timestamp,callback){
		
	var tmpObject = {};
	
	if(ids.length == 1){
		tmpObject.color_label = tag;
		var servletUrl = activemodule;
		if(activemodule == "mail_detail") servletUrl = "mail";
		var param=AjaxRoot + "/"+servletUrl+"?action=update&session=" + session 
				+ "&id="+encodeURIComponent(ids[0].id)
				+ "&folder="+encodeURIComponent(ids[0].folder)
				+ "&timestamp="+timestamp;
		if(ids[0].recurrence_position && ids[0].recurrence_position > 0) {
			tmpObject.recurrence_position = ids[0].recurrence_position;
		}
		json.put(param, 
			tmpObject, 
			null, 
			function(cb) {	
				timestamp = cb.timestamp;
				if (callback) callback(timestamp);
			}
		);
	} else if(ids.length > 1){
		var multipleObject = [];
		for(var i = 0; i < ids.length; i++){
			multipleObject[i] = { action : "update", module : activemodule, timestamp : timestamp, id : ids[i].id, folder : ids[i].folder, data : { color_label : tag } };
			if(ids[i].recurrence_position && ids[i].recurrence_position > 0) {
				multipleObject[i] = ids[i].recurrence_position;
			}
		}
		json.put(AjaxRoot + "/multiple?session=" + session + "&continue=true",
			multipleObject,
			null,
			function(cb){
				timestamp = cb.timestamp;
				if (callback) callback(timestamp);
			});
	}
}


  //////////////////////////
 //   Global variables   //
//////////////////////////

/**
 * The session ID as a string.
 * Until a successful login, the value is null. After a login succeeds, the
 * value must be added as an URI parameter to every server request.
 */
var session = null;

/**
 * User configuration.
 */
var config;

/**
 * Width of resizable splits in pixels.
 */
var SplitWidth = 3;

/**
 * Array for the Tabs
 */
var tabsList = new Array();
var panelsList = new Array();
register("Loaded", function() {
	if(IE6) {
		changeClassAttributes(".tabPanelBackground","borderBottom","none");
		changeClassAttributes(".tabPanelBackground","background","url(img/tabs/tabPanelBorderBg.gif) #e8e8e8 bottom repeat-x");
	}
});

/**
 * Global storage cache.
 */
var storageCache = new StorageCache(10);

/**
 * Currently focused element.
 */
var focusedElement = null;

register("Loaded", function() {
	addDOMEvent(body, "focus", function(e) {
		focusedElement = e.target || e.srcElement;
	});
});

/**
 * Sets the focus to the specified node.
 */
function setFocus(node) {
	focusedElement = node;
	if (node && node.focus) setTimeout(function() { try{node.focus();} catch (e){} });
}

/**
 * Params in the Url
 */
var url = {};
(function() {
	if (location.href.indexOf('#') == -1) return;
	var qs = location.href.substring(location.href.indexOf('#')+1);
	var nv = qs.split('&');

	for(i = 0; i < nv.length; i++)
	{
	   eq = nv[i].indexOf('=');
	   url[nv[i].substring(0,eq).toLowerCase()] = 
	   	decodeURIComponent(nv[i].substring(eq + 1));
	}
})();

/**
 * Empty function.
 * Does nothing. Useful for disabling DOM events.
 */
function emptyFunction() {}

/**
 * Identity function.
 * Returns its first parameter.
 */
function identity(x) { return x; }

/**
 * Returns whether an object is empty.
 * @type Boolean
 * @return True if the object does not have enumerable properties,
 * false otherwise.
 */
function isEmpty(x) {
	for (var i in x) return false;
	return true;
}

/**
 * Removes elements from an array based on a predicate function.
 * @param {Array} array The array to filter.
 * @param {Function} predicate A function which takes an array element and
 * its index as parameters and returns false if that element needs
 * to be removed, true otherwise.
 * @param {Number} start The index of the element where the filtering should
 * start.
 * @type Array
 * @return The modified array.
 */
function filterArray(array, predicate, start) {
	var len = array.length;
	for (var s = start || 0; s < len && predicate(array[s], s); s++);
	for (var d = s++; s < len; s++)
		if (predicate(array[s])) array[d++] = array[s];
	if (d < len) array.length = d;
	return array;
}

  //////////////////////////////////
 //   Global browser detection   //
//////////////////////////////////

/**
 * Value of the property Event.button which indicates the left mouse button.
 * @final
 */
var LeftButton = document.implementation.hasFeature("MouseEvents", "2.0") ? 0 : 1;

/**
 * Value of the property Event.button which indicates the right mouse button.
 * @final
 */
var RightButton = LeftButton + document.implementation.hasFeature("MouseEvents", "2.0") ?2:1;

/**
 * Name of the CSS &quot;float&quot; property.
 * @final
 */
var flt = (function() {
	var div = document.createElement("div");
	div.innerHTML = "<div stlye='float:left'></div>";
	return div.firstChild.style.cssFloat === undefined ? "styleFloat" : "cssFloat";
})();

/**
 * The DOM node of the HTML body.
 */
var body;

/**
 * Resizes a resizable panel.
 * @param {String} id The ID of the resized element.
 * @param {String} size The new size of the first child as a CSS length
 * specification.
 */
var resizeSplit;

/**
 * Returns a DOM node with the specified ID. Only nodes from static HTML are
 * present.
 * @param {String} id The ID of the node.
 * @type Object
 * @return the DOM node with the specified ID.
 */
var $ =function(arg) {return document.getElementById(arg);}
,replace$;

/**
 * Skips non-element nodes.
 * When navigating in the DOM tree, usually only element nodes are of interest.
 * To skip all other nodes, this function is called on a node and returns
 * the first element node in the list of siblings, starting with the specified
 * node, or the specified node if it is already an element node. Typical usage
 * involves calling this function on the <code>firstChild</code> of a parent
 * node to retrieve the first child element, then calling it on
 * the <code>nextSibling</code> of the result of the last call to retrieve
 * further child elements.
 * @param {Object} node A DOM node which is used as a starting point in
 * the search for element nodes.
 * @return Object The first element node in the sibling chain starting with
 * {@link #node}.
 */
function getElement(node) {
	if (!node) return node;
	while (node && node.nodeType != 1) node = node.nextSibling;
	return node;
}

/**
 * Utility function for creation of DOM trees from JavaScript.
 * @param {String} tag Tag name, e. g. "div".
 * @param {Object} style An object with stylesheet properties for the new node.
 * The property name &quot;flt&quot; gets converted to the correct property name
 * for float.
 * @param {Object} props An object with other properties of the new node.
 * @param {Array} chlidren An array with children of the new node.
 * @return The new DOM node.
 */
function newnode(tag, style, props, children, doc) {
	if (!doc)
		doc = document;
	var retval = doc.createElement(tag);
	if (style) for (var i in style)
		retval.style[i == "flt" ? flt : i] = style[i];
	if (props) for (var i in props) retval[i] = props[i];
	if (children) for (var i in children) retval.appendChild(children[i]);
	return retval;
}

  ///////////////////
 //   Animation   //
///////////////////

/**
 * Plays an animation.
 * Animations should be implemented by callback functions which take a number
 * between 0 and a specified end value as parameter. This function will call
 * such an animation callback as often as possible in a specified time interval.
 * @param {Number} duration Total duration of the animation, in milliseconds.
 * @param {Number} end The final parameter value for the callback.
 * @param {Function} callback A callback function which is called repeatedly
 * with numbers in the range from 0 to end, inclusive. The first call is
 * performed immediately, with the parameter 0. The last call always has
 * the parameter exactly equal to end.
 * @param {Function} final_cb An optional callback which is called after
 * the last iteration.
 * @type Function
 * @return A function which can be called to cancel the animation. The animation
 * callback is called immediately with the final value but the final callback is
 * not called.
 */
function animate(duration, end, callback, final_cb) {
	var start = (new Date()).getTime();
	var f = end / duration;
	callback(0);
	var timer = setTimeout(anim, 0);
	function anim() {
		timer = null;
		var dt = (new Date()).getTime() - start;
		if (dt < duration) {
			callback(dt * f);
			timer = setTimeout(anim, 0);
		} else {
			callback(end);
			if (final_cb) final_cb();
		}
	}
	return function(disableFinal) {
		if (timer) clearTimeout(timer);
		callback(end);
		if(!disableFinal) {
			if (final_cb) final_cb();
		}
	};
}

/**
 * Creates a function for conversion of a linear motion to a polynomial motion
 * of a higher order. The motion ends at the same value, but with a final speed
 * of zero.
 * @param {Number} power The power of the parameter in the transforming
 * equation.
 * @param {Number} end The final value of the transformed parameter (the same
 * value as passed to the animate function).
 * @type Function
 * @return A function which takes the callback parameter in an animate callback
 * and returns the modified value to be used instead.
 */
function nonLinear(power, end) {
	var endPow = Math.pow(end, power - 1);
	return function(x) { return end - Math.pow(end - x, power) / endPow; };
} 

  //////////////////////
 //   Benchmarking   //
//////////////////////

var benchmark, stopbenchmark;

(function() {
	var times = [];
	benchmark = function(name) {
		times.push({time: new Date().getTime(), name: name});
	};
	stopbenchmark = function() {
		times.push({time: new Date().getTime()});
		var s = "Benchmark\n";
		for (var i = 1; i < times.length; i++)
			s += i + ": " + ((times[i].time - times[i - 1].time) / 1000) + "s (" + times[i - 1].name + ")\n";
		delete times[name];
		window.console ? console.log(s) : alert(s);
		times = [];
	};
})();

  ////////////////////////
 //   Initialization   //
////////////////////////

var resizeHandler;
var resizeEvents = new Events();
var pxPerEm;
var rootebene=this;
var evals=new Array();
var allnodes = {};
loadMessage =function() {};
function initAll(login) {	
	body = document.getElementsByTagName("body")[0];
	if(login) {
		loadMessage = function(message, status) {
			document.getElementById("loadmessage_text").firstChild.data = format(_("%s Please wait..."), _(message));
			document.getElementById("loadmessage_bar").style.width=status+"%";
			document.getElementById("loadmessage_bar").firstChild.data=status+"%"
		};

		loadMessage("Autologin...","10"); /*i18n*/
		var match = /(\w+)([-_](\w+))?/.exec(navigator.language || navigator.userLanguage);
		var lang = "en_US";
		Found: if (match) {
			if (match[2]) {
				lang = match[1].toLowerCase() + "_" + match[3].toUpperCase();
				if (corewindow && lang in corewindow.all_languages) break Found;
				lang = "en_US";
			}
			var lng = match[1].toLowerCase();
			if (corewindow && lng in corewindow.all_languages) break Found;
			lng += "_";
			var len = lng.length;
			for (var i in corewindow.all_languages) {
				if (i.substring(0, len) == lng) {
					lang = i;
					break Found;
				}
			}
		}
		setLanguage(lang);
		(new JSON()).get(AjaxRoot + "/login?action=autologin", null,
			function(result) { dologin(result); },
			function(result, status) {
				// Initial translation

				// enable login form
				document.getElementById("loading_data").style.display="none";
				document.getElementById("loading_data_complete").style.display="block";
				if (document.getElementById("login")) document.getElementById("login").username.focus();
				triggerEvent("LoginPageLoaded");
				return true;
			}
		);
		register("OX_Configuration_Loaded_Complete",function () {
			setLanguage(configGetKey("language"));
		});
	} else {
		initAll2(login);
		setLanguage(corewindow.configGetKey("language")); 
	}
}
function initAll2(login) {
	body = document.getElementsByTagName("body")[0];
	
	function getComputedStyle(node) {
		return window.getComputedStyle ? window.getComputedStyle(node, "")
		                               : node.currentStyle;
	}

	loadMessage("Rebuild Tree...", /*i18n*/
	"40");
	
	
	var nodes = document.getElementsByTagName("*");
	var nodes_len = nodes.length;
	
	for (var i = 0; i < nodes_len; i++) {
		var node = nodes[i];
		var id = node.id;
		if (id) {
			if (allnodes[id]) {
				alert("Internal Error: ID \"" + id + "\" used multiple times!");
				allnodes[id] = "Duplicate ID";
			} else
				allnodes[id] = node;
		}
	}

	$ = function(id) { return allnodes[id] };
	replace$ = function(node) {
		if(node && node.id) {
			allnodes[node.id]=node;
		}
	}
	function cp(o) {
		var retval = {};
		for (var i in o)
			retval[i] = typeof(o[i]) == "object" ? cp(o[i]) : o[i];
		return retval;
	}

	function copy(o) {
		if (typeof(o) != "object") return o;
		return cp(o);
	}
	
	function arraycopy(a) {
		var retval = [];
		for (var i in a) retval[i] = copy(a[i]);
		return retval;
	}
	
	function copycontents(from, to) {
		for (var i in to) delete to[i];
		for (var i in from) to[i] = from[i];
	}
	
	/**
	 * Returns a zero Coord.
	 * A coordinate (type Coord) is {px: Number, em: Number,
	 * ids: {Sintrg: Number}, dep: {String: Number}}.
	 * @type Coord
	 * @return A new instance of the zero coordinate.
	 */
	function zero() { return {px: 0, em: 0, ids: {}, dep: {}}; }

	/**
	 * Converts a DOM node ID to a dependent coordinate of the same size.
	 * @param {String} id The ID of a DOM node.
	 * @type Coord
	 * @return A coordinate which computes to the size of the node.
	 */
	function dependency(id) {
		var retval = zero();
		retval.ids[id] = 1;
		return retval;
	}

	/**
	 * Converts a textual size specification into a coordinate.
	 * A specification consists of one or more CSS length values.
	 * Currently supported units are px, em and %.
	 * A coordinate (type Coord) is
	 * {px: Number, em: Number, ids: {String: Number}, dep: {String: Number}}.
	 * @param {String} id The node ID displayed in error messages.
	 * @param {String} text The size specification.
	 * @param {Coord} relativeTo A coordinate relative to which % is
	 * interpreted.
	 * @type Coord
	 * @return The size as a coordinate.
	 */
	function parse(id, text, relativeTo) {
		var retval = zero();
		var regex = /([+-]?[0-9]*(\.[0-9]*)?)(px|em|%)/g;
		var match
		while (match = regex.exec(text)) {
			if (match[3] == "%")
				retval = add(retval, scale(relativeTo, Number(match[1]) / 100));
			else
				retval[match[3]] += Number(match[1]);
		}
		return retval;
	}
	
	/**
	 * Adds two coordinates.
	 * @param {Coord} a First coorfdinate
	 * @param {Coord} b Second coordinate
	 * @type Coord
	 * @return The sum of a and b.
	 */
	function add(a, b) {
		var retval = {px: a.px + b.px, em: a.em + b.em, ids: copy(a.ids),
			dep: copy(a.dep)};
		for (var i in b.ids) retval.ids[i] = (retval.ids[i] || 0) + b.ids[i];
		for (var i in b.dep) retval.dep[i] = (retval.dep[i] || 0) + b.dep[i];
		return retval;
	}
	
	/**
	 * Subtracts one coordinate from another.
	 * @param {Coord} a The minuend.
	 * @param {Coord} b The subtrahend.
	 * @type Coord
	 * @return The difference between a and b.
	 */
	function sub(a, b) {
		var retval = {px: a.px - b.px, em: a.em - b.em, ids: copy(a.ids),
			dep: copy(a.dep)};
		for (var i in b.ids) retval.ids[i] = (retval.ids[i] || 0) - b.ids[i];
		for (var i in b.dep) retval.dep[i] = (retval.dep[i] || 0) - b.dep[i];
		return retval;
	}
	
	/**
	 * Scales a coordinate by a constant factor.
	 * @param {Coord} a The coordinate.
	 * @param {Coord} s The scaling factor.
	 * @type Coord
	 * @return The scaled coordinate.
	 */
	function scale(a, s) {
		var retval = {px: a.px * s, em: a.em * s, ids: {}, dep: {}};
		for (var i in a.ids) retval.ids[i] = a.ids[i] * s;
		for (var i in a.dep) retval.dep[i] = a.dep[i] * s;
		return retval;
	}
	
	// Some browsers don't understand "position: absolute; height: auto;".
	IE6 = false;

	function initResize() {
		
		/**
		 * Size of every panel as {String: {size: [Coord], panel: Panel}}.
		 * The size array contains the Coord values for top, right, bottom,
		 * left, width and height, in that order.
		 * Values which must not be set are deleted/undefined.
		 * Panel is {id: String, size: String, children: panels,
		 * padding: String, margin: String}.
		 */
		var sizes = {};
		
		/**
		 * Computes the actual value of a coordinate.
		 * @param {Coord} coord The coordinate to compute.
		 * @param {Number} ix The index of the coordinate in its size array.
		 * @type {em: Number, px: Number}
		 * @return The coordinate with all dependencies resolved.
		 */
		function compute(coord, ix) {
			var wh = (ix & 1) + 4;
			var retval = {px: coord.px, em: coord.em};
			for (var i in coord.dep)
				retval = add(retval, scale(compute(sizes[i].size[wh], wh), coord.dep[i]));
			wh = ix & 1 ? "offsetWidth" : "offsetHeight";
			for (var i in coord.ids) retval.px += $(i)[wh] * coord.ids[i];
			return retval;
		}
		
		/**
		 * Computes the size of a panel and inserts it into {@link sizes}.
		 * HC SVNT DRACONES
		 * @param {String} auto ox:align of the parent if the parent doesn't
		 * have an ox:size
		 * @param {Array} size Available space as [Coord]. This object is
		 * modified by subtracting the space occupied by the panel.
		 * @param {Panel} panel the panel to layout.
		 * Panel is {id: String, size: String, children: panels,
		 * padding: String, border: String, margin: String}.
		 * @type Coord
		 * @return The size of the panel in the direction of {@link #auto}.
		 */
		function getPanelSize(auto, size, panel) {
			if (auto && panel.align != auto)
				alert(format('At id="%s": invalid ox:align="%s" inside ox:align="%s" without ox:size.',
					panel.id, panel.align, auto));

			var z = zero();
			var margin = extract(size, panel.margin, "margin");
			var border = extract(size, panel.border, "border");
			var padding = extract(size, panel.padding, "padding");
			var nodesize = adjust_wh(adjust_wh(adjust(size, margin), border), padding);
			var childsize = [padding[0], padding[1], padding[2], padding[3],
			                 nodesize[4], nodesize[5]];
			sizes[panel.id] = {size: nodesize, panel: panel};
			var ix = {top: 0, right: 1, bottom: 2, left: 3}[panel.align];
			var wh = (ix & 1) + 4;

			var retval = add(add(margin[wh], border[wh]), padding[wh]);
			if (panel.align == "stretch")
				recursion(auto);
			else {
				delete nodesize[(ix + 2) & 3];
				if (panel.size) {
					update(parse(panel.id, panel.size, size[wh]));
					recursion(null);
				} else if (panel.children.length)
					update(recursion(panel.align));
				else {
					retval = margin[wh];
					update(dependency(panel.id));
					delete nodesize[wh];
				}
			}
			return retval;
			
			function extract(size, border, type) {
				var retval = [z, z, z, z, z, z];
				if (!border) return retval;
				var deltas = border.split(" ");
				if (deltas.length != 4)
					alert(format('At id="%s": invalid ox:%s="%s"',
					             panel.id, type, border));
				for (var i = 0, wh = 4; i < 4; i++, wh ^= 1)
					retval[wh] = add(retval[wh],
						retval[i] = parse(panel.id, deltas[i], size[wh]));
				return retval;
			}
			
			function adjust(size, deltas) {
				if (!deltas) return arraycopy(size);
				var retval = [, , , , sub(size[4], deltas[4]), sub(size[5], deltas[5])];
				for (var i = 0; i < 4; i++) retval[i] = add(size[i], deltas[i]);
				return retval;
			}
			
			function adjust_wh(size, deltas) {
				var retval = arraycopy(size);
				if (deltas) {
					retval[4] = sub(retval[4], deltas[4]);
					retval[5] = sub(retval[5], deltas[5]);
				}
				return retval;
			}
		
			function recursion(auto) {
				var retval = zero();
				for (var i = 0; i < panel.children.length; i++) {
					var child = panel.children[i];
					if (child)
						retval = add(retval, getPanelSize(auto, childsize, child));
				}
				return retval;
			}

			function update(psize) {
				childsize[wh] = nodesize[wh] = copy(psize);
				if (panel.resize) {
					(psize = zero()).dep[panel.id] = 1;
					childsize[wh] = psize;
				}
				retval = add(retval, psize);
				size[ix] = add(size[ix], retval);
				size[wh] = sub(size[wh], retval);
			}
		}


		// Compute sizes
		var size = [zero(), zero(), zero(), zero(),
		            dependency("body"), dependency("body")];
		for (var i = 0; i < init.size.length; i++) {
			var child = init.size[i];
			if (child) getPanelSize(null, size, child);
		}

		// Remove unnecessary size specificatinos.
		var del1 = IE6 ? 2 : 4; // delete bottom for IE and height for the rest
		var check1 = IE6 ? 4 : 2;
		var del2 = IE6 ? 1 : 5; // same for right and width
		var check2 = IE6 ? 5 : 1;
		for (var i in sizes) {
			var size = sizes[i].size;
			if (size[0] && size[check1]) delete size[del1];
			if (size[3] && size[check2]) delete size[del2];
		}

		// Extract dynamic dependencies
		var deps = {}; // {String: {String: true}}
		for (var i in sizes) {
			var size = sizes[i].size;
			var dep = deps[i] = {};
			for (var j = 0; j < 6; j++)
				if (size[j]) for (var id in size[j].ids) dep[id] = true;
		}
/*
		// Remove indirect dependencies: i>j and j>k => not i>k
		for (var i in deps) {
			var dep = deps[i];
			var del = {};
			for (var j in dep) for (var k in deps[j]) del[k] = true;
			for (var j in del) delete dep[j];
		}
*/
		// Remove dependencies on body
		for (var j in deps)	delete deps[j].body;

		// Create size panel lists
		var size_panels = []; // [{String: [Coord]}]
		do {
			// Add independent panels to the lists
			var level = {};
			var hasNodes = false;
			for (var i in deps) {
				var dep = deps[i];
				var independent = true;
				for (var j in dep) { independent = false; break; }
				if (independent) copycontents(sizes[i].size, level[i] = []);
				hasNodes |= independent;
			}
			if (!hasNodes) break;
			size_panels.push(level);
			// Remove independent panels
			for (var i in level) delete deps[i];
			// Remove dependencies on removed panels
			for (var i in level)
				for (var j in deps)	delete deps[j][i];
		} while (true);

		// Check for circular dependencies
		var s = ["Circular dependencies detected:"];
		for (var i in deps) {
			var d = [];
			for (var j in deps[i]) d.push(j);
			s.push(format('"%s" depends on "%s"', i, d.join('", "')));
		}
		if (s.length > 1) alert(s.join("\n"));

		// Extract resize dependencies
		var sdeps = {}; // {String: {String: true}}
		for (var i in sizes) {
			var size = sizes[i].size;
			for (var j = 0; j < 6; j++) {
				if (!size[j]) continue;
				for (var k in size[j].dep) {
					var sd = sdeps[k];
					if (!sd) sd = sdeps[k] = {};
					sd[i] = true;
				}
			}
		}

		// Compute the transitive closure over resize dependencies
		var computed = {}; // {String: true}
		for (var i in sdeps) if (!computed[i]) transClosure(i);
		function transClosure(i) {
			computed[i] = true;
			var list = sdeps[i];
			var newentries = {};
			for (var j in list) {
				if (!computed[j]) transClosure(j);
				for (var k in sdeps[j]) newentries[k] = true;
			}
			for (var j in newentries) list[j] = true;
		}

		// Create resize panel lists from size panel lists
		var resize_panels = {}; // {String: [{String: [Coord]}]}
		var len = size_panels.length;
		for (var i in sdeps) {
			var panels = resize_panels[i] = new Array(len);
			for (var j = 0; j < len; j++) panels[j] = {};
			panels[0][i] = sizes[i].size;
			for (var j in sdeps[i]) {
				for (var k = 0; k < len; k++) {
					if (j in size_panels[k]) {
						copycontents(size_panels[k][j], panels[k][j] = []);
						break;
					}
				}
			}
		}

		loadMessage("Static resizing...", /*i18n*/
				"40");

		// Set static sizes and remove them from size panel lists
		var pxFields = ["top", "right", "bottom", "left", "height", "width"];
		var emFields = ["marginTop", "marginRight", "marginBottom", "marginLeft"];
		for (var i in size_panels) {
			var panels = size_panels[i];
			for (var j in panels) {
				var panel = panels[j];
				var style = $(j).style;
				style.position = "absolute";
				for (var k in panel) {
					var stat = true;
					for (var l in panel[k].ids) { stat = false; break; }
					var size = compute(panel[k], k);
					if (stat && (emFields[k] || !size.px || !size.em)) {
						if (size.px) {
							style[pxFields[k]] = size.px + "px";
							if (emFields[k])
								style[emFields[k]] = (size.em || 0) + "em";
						} else {
							style[pxFields[k]] = (size.em || 0) + "em";
							if (emFields[k])
								style[emFields[k]] = 0;
						}
						delete panel[k];
/*					} else if (IE_Expressions) {
						function getExpr(coord, ix) {
							var retval = [coord.px, "+pxPerEm*", coord.em];
							for (var l in coord.ids)
								retval.concat(["+$(", l, ").offset",
									(ix & 1 ? "Width*" : "Height*"),
									coord.ids[l]]);
							for (var l in coord.dep)
								retval.concat(["+$(", l, ").offset",
									(ix & 1 ? "Width*" : "Height*"),
									coord.dep[l]]);
							return retval.join("");
						}
						style.setExpression(pxFields[k], getExpr(panel[k], k));
						delete panel[k];*/
					}
				}
			}
		}
		loadMessage("Dynamic resizing...", /*i18n*/
				"70");

		// Remove static panels.
		for (var i in size_panels) {
			var panels = size_panels[i];
			Panels: for (var j in panels) {
				var panel = panels[j];
				for (var k in panel) continue Panels;
				delete panels[j];
			}
		}

		/**
		 * Computes changes which are necessary for a single coordinate due to
		 * the resizing of the window.
		 * @param {Array} changes An array to which the computed changes are
		 * appended.
		 * @param {Object} style A DOM style object of the node whose changes
		 * are computed.
		 * @param {Object} size An object of the form {px: Number, em: Number}
		 * which contains the new computed coordinate.
		 * @param {Number} ix The index of the coordinate in arrays like [Coord]
		 * or {@link pxFields} and {@link emFields}.
		 */
		function resizeChanges(changes, style, size, ix) {
			var value = Math.max(0, size.px + size.em * pxPerEm) + "px";
			var field = pxFields[ix];
			if (style[field] != value)
				changes.push({style: style, field: field, value: value});
		}

		/**
		 * Computes changes which are necessary for a single coordinate due to
		 * the resizing of an element.
		 * @param {Array} changes An array to which the computed changes are
		 * appended. Each change has the form
		 * {style: Object, field: String, Value: String}.
		 * @param {Object} style A DOM style object of the node whose changes
		 * are computed.
		 * @param {Object} size An object of the form {px: Number, em: Number}
		 * which contains the new computed coordinate.
		 * @param {Number} ix The index of the coordinate in arrays like [Coord]
		 * or {@link pxFields} and {@link emFields}.
		 */
		function resizeSplitChanges(changes, style, size, ix) {
			function change(field, value) {
				if (style[field] != value)
					changes.push({style: style, field: field, value: value});
			}
			if (emFields[ix] || !size.em) {
				change(pxFields[ix], size.px + "px");
				if (emFields[ix]) change(emFields[ix], size.em + "em");
			} else if (size.px)
				resizeChanges(changes, style, size, ix);
			else
				change(pxFields[ix], size.em + "em");
		}
		
		/**
		 * Resizes all panels.
		 * @param {Array} panel_lists An array with a list of panels for each
		 * dynamic dependency level. Each list of panels is {String: [Coord]}.
		 * @param {Number} n The current dynamic dependency level. It is
		 * an index into the panel_lists array.
		 * @param {Function} changesF A function which computes necessary
		 * changes for a coordinate and appends them to an array, which is
		 * passed as the first parameter to it. The changes are computed from
		 * a DOM style object, a computed coordinate of the form
		 * {px: Number, em: Number} and the index of the coordinate, which are
		 * passed as the second to fourth parameters, respectively.
		 * @param {Object} pending And object {timeout: Number} containing the
		 * currently pending resize. If there are further dynamic dependency
		 * levels after the current, their resizing is scheduled via
		 * setTimeout() and the returned handle is placed in this object.
		 * @see resizeChanges
		 * @see resizeSplitChanges
		 */
		function resize(panel_lists, n, changesF, pending) {
			var panels = panel_lists[n];
			// Compute required changes.
			var changes = [];
			for (var i in panels) {
				var node = $(i);
				var hidden = (node.style.display == "none");
				while (!hidden && node.parentNode) {
					hidden = node.style && node.style.display == "none";
					node = node.parentNode;
				}
				if (hidden) continue;
				var panel = panels[i];
				var style = $(i).style;
				for (var j in panel) {
					if (!panel[j]) 
						debugger;
					changesF(changes, style, compute(panel[j], j), j);
				}
			}
			// Update the styles.
			if (changes.length) {
				for (var j in changes) {
					var change = changes[j];
					change.style[change.field] = change.value;
				}
			}
			// Schedule next dependency level or trigger final event.
			if (++n < panel_lists.length) {
				pending.timeout = setTimeout(function() {
					resize(panel_lists, n, changesF, pending);
				}, 0);
			} else {
				delete pending.timeout;
				resizeEvents.post("Resized");
			}
		}
		
		var resizeSplitPending = {};
		resizeSplit = function(id, size) {
			if (!sizes[id])
				alert(format('Invalid resizeSplit() call with id="%s"', id));
			var panel = sizes[id].panel;
			var ix = {top: 0, right: 1, bottom: 2, left: 3}[panel.align];
			var wh = (ix & 1) + 4;
			copycontents(parse(id, size), sizes[id].size[wh]);
			if ("timeout" in resizeSplitPending)
				clearTimeout(resizeSplitPending.timeout);
			pxPerEm = scalediv.offsetHeight / 1000;
			resize(resize_panels[id], 0, resizeSplitChanges, resizeSplitPending);
		}

		var scalediv = newnode("div",
			{position: "absolute", visibility: "hidden", width: 0, height: "1000em"});
		body.appendChild(scalediv);
		var resizePending = {};
		resizeHandler = function() {
			if ("timeout" in resizePending) clearTimeout(resizePending.timeout);
			pxPerEm = scalediv.offsetHeight / 1000;
			if (IE6)
				resizePending.timeout = setTimeout(function() {
					resize(size_panels, 0, resizeSplitChanges, resizePending);
				}, 0)
			else
				resize(size_panels, 0, resizeSplitChanges, resizePending);
		}
		window.onresize = resizeHandler;
		function final_resize() {
			resizeEvents.unregister("Resized", final_resize);
			loadMessage("Initialization ...", /*i18n*/
					"90");
			triggerEvent("Preload");
			triggerEvent("Loaded");
			if(login) { loggedIn()} else {
				$("loading_data").style.display="none";
				$("loading_data_complete").style.display="block";
			}
		}
		
		resizeEvents.register("Resized", final_resize);
		resizeHandler();
	}

	// Logging
	if (!window.console) {
		var appended = false;
		var caption = newnode("div", {color: "white", backgroundColor: "#576586"}, 0, [
			document.createTextNode("Debug Log"),
			newnode("span", {flt: "right"}, {onclick: function() {
					while (caption.nextSibling)
						logger.removeChild(caption.nextSibling);
					body.removeChild(logger);
					appended = false;
				}}, [newnode("img", 0, {src: "img/x.png"})])]);
		var logger = newnode("div", {zIndex: 9999, position: "absolute",
			width: "20em", height: "10em", overflow: "auto", right: 0,
			bottom: 0, border: "2px dashed red"}, 0, [caption]);
		window.console = {log: function(text, params) {
			if (!appended) {
				body.appendChild(logger);
				appended = true;
			}
			var lines = format.apply(null,arguments).split("\n");
			for (var i = 0; i < lines.length; i++)
				logger.appendChild(newnode("div", 0, 0,
					[document.createTextNode(lines[i])]));
		}};
	}
	
	function makeSplitCallback(split, align, live) {
		var parent = split.parentNode;
		var previous = split.previousSibling;
		while (previous.nodeType != 1) previous = previous.previousSibling;
		return function(e) {
			function getPixels(value) {
				if (!value) return value;
				var match = /^([0-9.]+)(em|px)$/.exec(value);
				if (!match) alert(format("Invalid ox:min or ox:max at id=\"%2\".", split.id));
				var num = parseFloat(match[1]);
				switch (match[2]) {
					case "px": return num;
					case "em": return pxPerEm * num;
				}
			}
			var min = getPixels(init.min[previous.id]) || 0;
			var max = getPixels(init.max[previous.id]) || Infinity;
			var displayOffset;
			var sizeF = {
				left: function() {
					displayOffset = previous.offsetLeft;
					var offset = previous.offsetWidth - e.clientX;
					var max2 = Math.min(max, parent.clientWidth - split.offsetWidth);
					return function(x, y) {
						return Math.min(max2, Math.max(min, offset + x));
					};
				},
				right: function() {
					displayOffset = previous.offsetLeft + previous.offsetWidth - parent.offsetWidth;
					var offset = previous.offsetWidth + e.clientX;
					var max2 = Math.min(max, parent.clientWidth - split.offsetWidth);
					return function(x, y) {
						return Math.min(max2, Math.max(min, offset - x));
					};
				},
				top: function() {
					displayOffset = previous.offsetTop;
					var offset = previous.offsetHeight - e.clientY;
					var max2 = Math.min(max, parent.clientHeight - split.offsetHeight);
					return function(x, y) {
						return Math.min(max2, Math.max(min, offset + y));
					};
				},
				bottom: function() {
					displayOffset = previous.offsetTop + previous.offsetHeight - parent.offsetHeight;
					var offset = previous.offsetHeight + e.clientY;
					var max2 = Math.min(max, parent.clientHeight - split.offsetHeight);
					return function(x, y) {
						return Math.min(max2, Math.max(min, offset - y));
					};
				}
			}[align]();
			var size = sizeF(e.clientX, e.clientY);
			function m(e) {
				stopEvent(e);
				size = sizeF(e.clientX, e.clientY);
				if (live) {
					var s = function() { return size; };
					resizeSplit(previous.id, size + "px");
					resizeEvents.post("SplitResized", s, parent);
				} else
					movingSplit.style[align] = (displayOffset + size) + "px";
			};
			function u() {
				showIFrames();
				removeDOMEvent(body, "mousemove", m);
				removeDOMEvent(body, "mouseup", u);
				parent.style.cursor = "";
				if (!live) {
					split.parentNode.removeChild(movingSplit);
					movingSplit = null;
					var s = function() { return size; };
					resizeSplit(previous.id, size + "px");
					resizeEvents.post("SplitResized", s, parent);
				}
			};
			hideIFrames();
			parent.style.cursor = split.style.cursor;
			addDOMEvent(body, "mousemove", m);
			addDOMEvent(body, "mouseup", u);
			if (!live) {
				var movingSplit = split.cloneNode(true);
				movingSplit.style[{top: "marginTop", right: "marginRight",
					bottom: "marginBottom", left: "marginLeft"}[align]] = 0;
				movingSplit.style[align] = (displayOffset + size) + "px";
				movingSplit.className = movingSplit.className + " moving";
				split.parentNode.appendChild(movingSplit);
			}
			cancelDefault(e);
		}
	}
	
	// Manually resizable splits
	for (var i in init.split) {
		var split = $(i);
		addDOMEvent(split, "mousedown", makeSplitCallback(split, init.split[i], false));
	}

	var scalediv2 = newnode("div",
		{position: "absolute", visibility: "hidden", width: 0, top: 0, bottom: 0});
	body.appendChild(scalediv2);
	setTimeout(function() {		
		IE6 = scalediv2.offsetHeight < body.clientHeight;
		
		// IE6 workaround
		if (IE6) {
			for (var i in init.IE6workaround) {
				var workaround = init.IE6workaround[i];
				var dir = workaround.dir;
				var node = $(i);
				for (dir = dir & (dir - 1); dir; dir = dir & (dir - 1)) {
					node = getElement(node.firstChild);
					node.style.padding = workaround.padding;
				}
			}
			try {
				document.execCommand('BackgroundImageCache', false, true);
			} catch(e) {}
		}
		// End of IE6 workaround
		
		body.removeChild(scalediv2);
		scalediv2 = null;
		initResize();
	}, 0);
	
	// Document title
	var originalTitle = document.title;
	register("LanguageChanged", function() {
		document.title = _(originalTitle);
	});

	// Direct linking
	if (login) register("OX_Configuration_Loaded_Complete", function() {
		var module = location.hash.match(/[#&]m=([^#&]+)/);
		var folder = location.hash.match(/[#&]f=([^#&]+)/);
		var id = location.hash.match(/[#&]i=([^#&]+)/);
		if (module && folder && id)
			triggerEvent("OX_Direct_Linking", module[1],
			             {folder: folder[1], id: id[1]});
	});
	
	// Automatic logout handling
	var loggingOut = false;
	if (login) JSON.errorHandler = function(result, status) {
		if (status) {
			//#. HTTP Errors from the server
			triggerEvent("OX_New_Error", 2,
				//#. %1$s is the numeric HTTP status code
				//#. %2$s is the corresponding HTTP status text
				format(_("Error: %1$s - %2$s"), status, result));
		} else if (result.code.match(/^SES-02..$/)) {
			if (!loggingOut) {
				loggingOut = true;
				window.onbeforeunload = null;
				alert(_("Your session has expired. Please log in again."));
				setTimeout( function() { window.location.replace(location.pathname); },0);
			}
		} else
			newServerError(result,4);
	}
	
	// TODO: move to separate event handlers.
	addDOMEvent(body, "mousedown", function(e) {
		triggerEvent('OX_GLOBAL_CLICK',e);
	});
}

function unloadMessageMainLogin(){
    return _("Do you really want to discard your changes and close the window?");
}

var hideIFrames, showIFrames;

(function() {
	var count = 0;
	
	hideIFrames = function() {
		if (count++) return;
		for (var i in init.hide) {
			var div = $(i + "-hide").style;
			var iframe = $(i);
			div.width = iframe.offsetWidth + "px";
			div.height = iframe.offsetHeight + "px";
			div.display = "block";
		}
	}
	
	showIFrames = function() {
		if (--count) return;
		for (var i in init.hide) $(i + "-hide").style.display = "none";
	}
})();

/**
 * Utility function for seperating file names from the path
 * @param {String} path with file name<br />
 * 		Example:<br />
 * 		/path/to/file.ext
 * @return The new file name
 */
function separateFilenameFromPath(sValue) {
	var aTMP = sValue.split("/");
	if(aTMP.length==1)
	{
		aTMP2 = sValue.split("\\");
		return aTMP2[(aTMP2.length-1)];	
	}	
	return aTMP[(aTMP.length-1)];	
}

function removeClass(sClassName,sClassToDel)
{
	var sDeseletedClassName = '';
	if(!sClassName || sClassName.length == 0)
		return sDeseletedClassName;
	var aSplited = sClassName.split(' ');
	for(var nInd=0;nInd < aSplited.length;nInd++)
	{
		if(aSplited[nInd].length>0 && aSplited[nInd] != sClassToDel)
		{
			sDeseletedClassName += ' '+aSplited[nInd];
		}
	}			
	return sDeseletedClassName;
}
/**
*	Replace node.innerHTML="" with this
*/
function removeChildNodes(node) {
	if(node) {
		var nodes=node.childNodes;
		for(zaehler=0;zaehler<nodes.length;) {
			node.removeChild(nodes[zaehler]);
		}
	}
}

/* 
 * convert Bytes to KB, MB or GB
 * @param {Number} bytes Number of bytes
 * @return {String} The size as a human-readable, already translated string.
 * */
function bytesToString(bytes)
{
	var units = [
		//#. Byte unit
		"bytes", /*i18n*/
		//#. Kilobyte unit (1024)
		"KB", /*i18n*/
		//#. Megabyte unit (1024^2)
		"MB", /*i18n*/
		//#. Gigabyte unit (1024^3)
		"GB", /*i18n*/
		//#. Terabyte unit (1024^4)
		"TB", /*i18n*/
		//#. Petabyte unit (1024^5)
		"PB", /*i18n*/
		//#. Exabyte unit (1024^6)
		"EB", /*i18n*/
		//#. Zettabyte unit (1024^7)
		"ZB", /*i18n*/
		//#. Yottabyte unit (1024^8)
		"YB" /*i18n*/
	];

	for (var i = 0; i < units.length; i++) {
		if (bytes < 1000)
			//#. Byte size like "500 MB". Space or no space?
			//#. %1$s is the number
			//#. %2$s is the unit
			return format(_("%1$s %2$s"),
			              formatNumbers(Math.round(bytes * 100) / 100),
			              units[i]);
		bytes /= 1024;
	}
}

function clone(element) {
	if(typeof(element) != "object") return element;
	return subclone(element);
}
function subclone(element) {
	if(!element) {
		return null;
	}
	if(element.constructor == Array) {
		var retval = [];
		for (var i=0;i<element.length;i++)
			retval[i] = (typeof(element[i]) == "object") ? subclone(element[i]) : element[i];
		return retval;
	}
	else {
		var retval = {};
		for (var i in element)
			retval[i] = (typeof(element[i]) == "object") ? subclone(element[i]) : element[i];
		return retval;
	}
}
function trimStr(withBlanks)
{
	var TrimdStr = '';
	for(var nIndx=0;nIndx<withBlanks.length;nIndx++)
	{
		var sChar = withBlanks.substr(nIndx,1);
		if(sChar != ' ')
		{
			 TrimdStr = withBlanks.substr(nIndx);
			 break;
		}
	}
	for(var nIndx=TrimdStr.length-1;nIndx >= 0;nIndx--)
	{
		var sChar = TrimdStr.substr(nIndx,1);
		if(sChar != ' ')
		{
			 return TrimdStr.substr(0,nIndx+1);
		}
	}
	return '';
}
//DEFAULT
var defaultviews=new Object();

function addDefaultView(myview,viewname) {
	if(!defaultviews) {
		defaultviews=new Object();
	}
	defaultviews[myview]=viewname;
}
function removeDefaultView(myview) {
	if(!defaultviews) {
		defaultviews=new Object();
	}
	delete defaultviews[myview];
}	
function getDefaultViewName(myview) {
	if(defaultviews) {
		if(defaultviews[myview]) {
			return defaultviews[myview];
		}
	}
	return null;
}
function getDefaultSubviews(myview) {
	var myret = new Object();
	for (i in defaultviews) {
		var splitview = i.split("/");
		var searchview = myview.split("/");
		for(i2=0;i2<searchview.length;i2++) {
			if(!splitview[i2]) {
				break;
			}
			if(splitview[i2]!=searchview[i2]) {
				break;
			}
			if(searchview.length==(i2+1)) {
				myret[i]=defaultviews[i];
			}
		} 
	}
	return clone(myret);
}

function isDefaultableView(key) {
	if(defaultviews) {
		if(defaultviews[key]) {
			return true;
		}
	}
	return false;	
}


function revertUrlEncodedString(str)
{
	var sEncStr = decodeURIComponent(str);	
	return sEncStr;
}

//replaces blanks and & with url code
function getUrlEncodedString(str)
{
	return encodeURIComponent(str);	
}
register("OX_Show_Help",
function(param) {
	window.open(''+param+'/' + configGetKey('language').substring(3,5).toLowerCase(),'oxhelp');
});
register("OX_Show_About",
function() {
	AboutPopup.openWindow()
	$("about_gui").firstChild.data=$("gui_version").firstChild.data;
	$("about_server").firstChild.data=configGetKey("serverVersion");
});
function setContentHeader(fields) {
	function setHeaderContent(oFolder){
		if(oFolder.oxfolder.data.created_by) {
			internalCache.getUsers([oFolder.oxfolder.data.created_by], function(cbObj){
				if(oFolder.oxfolder.data.type == 1 || oFolder.oxfolder.data.type == 3){
					for(var i=0;i<fields.length;i++) {
						$(fields[i]).firstChild.data = format(_("%s of %s"),oFolder.oxfolder.data.title,cbObj[oFolder.oxfolder.data.created_by].display_name);
					}
				}
				else if(oFolder.oxfolder.data.type == 2 ){
					for(var i=0;i<fields.length;i++) {
						$(fields[i]).firstChild.data = format(_("Public folder %s"),oFolder.oxfolder.data.title);
					} 
				} else {
					for(var i=0;i<fields.length;i++) {
						$(fields[i]).firstChild.data = format(_("Public folder %s"),oFolder.oxfolder.data.title);
					} 
				}
			});
		}
	}
	oMainFolderTree.cache.get_folder(activefolder,setHeaderContent);		
}
function setFolderOwnerHeader(aDivIds) {
	oMainFolderTree.cache.get_folder(activefolder,function (ofolder)
	{
				var OXFolder = ofolder.oxfolder;
				internalCache.getUsers([OXFolder.data.created_by],
					function(t) { 
						for(var h=0; h<aDivIds.length;h++) {
							if($(aDivIds[h]) && $(aDivIds[h]).firstChild) {
								$(aDivIds[h]).firstChild.data = format( _("InfoStore folder %s"),OXFolder.data.title);														
							}
						}
					}
				);	
	});
}

/* 
 * Converts an array with multiple addresses to a linked address list
 * @param {node} The node where the addresses will be appended to
 * @param {array} The server array which holds all the addresses 
 * @param {boolean} true = only the personal address information will be shown, other false
 * @return {node}
 * */
function getAdressStringLinked(node, addresses, personal) {	
	for (a = 0; a < addresses.length; a++) {
		var pAddr = addresses[a][0];
		var mAddr = addresses[a][1] || "";
		
		// quote mail address if not already quoted
		if (pAddr != null && (pAddr.split("\"").length <= 2 && pAddr.split("'").length <= 2)) {
				pAddr = "\"" + pAddr + "\"";			
		}
		
		// build visible address string, depending on the personal setting
		var vMailAddr = personal && pAddr ? pAddr : pAddr ? pAddr + " <" + mAddr + ">" : mAddr;
		// build full address string, used for the click to send
		var rMailAddr = pAddr ? pAddr + " <" + mAddr + ">" : mAddr;
		
		// create span with mail address
		var oDOMDiv = newnode("span", null, { className: "linkInView" }, 
			[ document.createTextNode(vMailAddr + (addresses.length-1 > a ? ", " : ""))] );
				
		addDOMEvent(oDOMDiv, "click", (function(oDOMDiv, rMailAddr) {
				return function(e) {
					//TODO SELECT MAIL	
					cancelDefault(e);
					corewindow.sendMailToRecipientMail(rMailAddr);
				};
			})(oDOMDiv, rMailAddr));
		
		registerContext(oDOMDiv,"mailaddress", null, rMailAddr);
		try {
			if(registerSource) {
				registerSource(oDOMDiv, "mailaddress", (function(rMailAddr) {
					return function() { 
						var o = {}; 
						o.email1=rMailAddr;
						return o;
					};
				})(rMailAddr), null, null, mailaddressdefaultdisabled, defaultdisabledremove);
			}
		} catch (e) { }
		
		node.appendChild(oDOMDiv);
	}
	
	return node;
}

/* 
 * function createas and writes creator information in the bottom of detailview
 * @param {Object}: should be contain fields modified_by, created_by, last_modified and creation_date
 * @param {String}: id of the container div
 * */
function writeBottomString(oObj,sDomIdContainer) {		
 	var nIdCreatedBy = oObj.created_by;
 	var nIdModifiedBy = (oObj.modified_by == undefined)?oObj.created_by:oObj.modified_by; 	

	var creation_date = formatDate(oObj.creation_date, "date");
	var last_modified = formatDate(oObj.last_modified, "date");
	internalCache.getUsers([nIdCreatedBy], function(arg) {
		var created_by = arg[nIdCreatedBy].display_name;
		internalCache.getUsers([nIdModifiedBy], function(arg) {
			var modified_by = arg[nIdModifiedBy].display_name;
			removeChildNodes($(sDomIdContainer));
			$(sDomIdContainer).appendChild((new I18nNode(function() {
				return format(_("Created on %s by %s, last changed on %s by %s"),
				              creation_date, created_by, last_modified, modified_by);
			})).node);
		});	
	});	
}
function getFrameElement(id) {
	return $ALL(id).contentWindow;
}
var $2,$ALL,removeTMPId, addTMPId;
var tmp_nodes;
(function() {
	tmp_nodes=new Object();	
	addTMPId= function (node) {
		tmp_nodes[node.id]=new Object();
		tmp_nodes[node.id]["node"]=node;
	}
	removeTMPId= function (id) {
		if(id.id) { id=id.id }
		delete tmp_nodes[id];
	}
	$2 = function(id) { 
		return (tmp_nodes[id]) ? tmp_nodes[id].node : undefined;
		
	}
	$ALL = function(id) { return $(id) || $2(id) || document.getElementById(id); }
})();


function getAbsolutePositionLeft(node)
{
	 var xPos=node.offsetLeft;
	 var oParent=node.offsetParent;
	 while(oParent != null) {xPos +=oParent.offsetLeft;oParent=oParent.offsetParent }
	 return xPos;
}
function getAbsolutePositionTop(node)
{
	 var yPos=node.offsetTop;
	 var oParent=node.offsetParent;
	 while(oParent != null) {yPos +=oParent.offsetTop;oParent=oParent.offsetParent }
	 return yPos;
}

/**
 * Compares an object with an old copy of that object and removes fields which
 * have not changed.
 * @param {Object} oldObject The old copy of the object.
 * @param {Object} newObject The current object which is modified.
 */
function checkModified(oldObject, newObject) {
	for (var i in newObject) {
		if (!(i in oldObject)) continue;
		var newItem = newObject[i];
		var oldItem = oldObject[i];
		if (newItem == oldItem) delete newObject[i];
		else if (typeof newItem == "object") {
			if (newItem instanceof Array) {
				Compare: if (newItem.length == oldItem.length) {
					for (var j = 0; j < newItem.length; j++)
						if (newItem[j] != oldItem[j]) break Compare;
					delete newObject[i];
				}
			} else {
				if (oldItem) checkModified(oldItem, newItem);
				if (isEmpty(newItem)) delete newObject[i];
			}
		}
	}
}


function loadFileForCacheOnInit(file) {
	loadFile(file,function(){},file);
}
register("LoginPageLoaded",function() {
	setTimeout(function() {
		preloadimagescore();
		preloadMailNewImages();
	},1000)
});

function preloadimagescore() {
	loadFileForCacheOnInit("img/dummy.gif");
	loadFileForCacheOnInit("img/mail/btnnew_email.gif");
	loadFileForCacheOnInit("img/mail/btnnew_email.gif");
	loadFileForCacheOnInit("img/mail/btnnew_email.gif");
	loadFileForCacheOnInit("img/portal/mod_portal_sel.gif");
	loadFileForCacheOnInit("img/calendar/mod_calendar.gif");
	loadFileForCacheOnInit("img/contacts/mod_contacts.gif");
	loadFileForCacheOnInit("img/tasks/mod_tasks.gif");
	loadFileForCacheOnInit("img/infostore/mod_infostore.gif");
	loadFileForCacheOnInit("img/configuration/mod_configuration.gif");
	loadFileForCacheOnInit("img/plus.gif");
	loadFileForCacheOnInit("img/noplus.gif");
	loadFileForCacheOnInit("img/folder/folder_closed.gif");
	
}

function preloadMailNewImages() {
	loadFileForCacheOnInit("3rdparty/tinymce/jscripts/tiny_mce/themes/advanced/images/spacer.gif");
	loadFileForCacheOnInit("3rdparty/tinymce/jscripts/tiny_mce/themes/advanced/images/separator.gif");
	loadFileForCacheOnInit("3rdparty/tinymce/jscripts/tiny_mce/themes/advanced/images/button_menu.gif");
	loadFileForCacheOnInit("3rdparty/tinymce/jscripts/tiny_mce/themes/advanced/images/buttons.gif");
}

function getDirectLinkLocal(oObj){
	if (!oObj) return;
	
	var myLocation = window.location.protocol+"//"+window.location.host;
	var myLocationPath = window.location.pathname;
	myLocation = myLocation+myLocationPath.substring(0,myLocationPath.lastIndexOf("ox.html"));	
	if(oObj.folder != undefined)
		return myLocation+"#m=" + (oObj.module || "infostore") + "&f="+oObj.folder + "&i="+oObj.id;
	else		
		return myLocation+"#m=" + (oObj.module || "infostore") + "&f="+oObj.folder_id + "&i="+oObj.id;
}

function getMimeImage(sMimeType){
	var sMimeTypeImgPath = "img/infostore/mimetypes/";
	var oImageMap = new Object();
	oImageMap["application/pdf"] = "pdf.png";
	oImageMap["text/plain"] = "txt.png";	
	oImageMap["application/vnd.oasis.opendocument.spreadsheet"] = "ooo_calc.png";		
	oImageMap["application/vnd.oasis.opendocument.text"] = "ooo_writer.png";			 	
	oImageMap["application/vnd.oasis.opendocument.graphics"] = "ooo_draw.png";			 				
	oImageMap["application/x-gzip"] = "tgz.png";
	oImageMap["application/x-tar"] = "tar.png";
	oImageMap["image/png"] = "image.png";
	oImageMap["image/jpeg"] = "image.png";		
	oImageMap["image/pjpeg"] = "image.png";				
	oImageMap["image/gif"] = "image.png";
	oImageMap["application/postscript"]	= "postscript.png";	
	oImageMap["application/octet-stream"]	= "binary.png";
	oImageMap["application/java-archive"]	= "java_jar.png";
	oImageMap["text/x-log"] = "log.png";
	oImageMap["video/x-ms-wmv"]	= "video.png";
	
	return (oImageMap[sMimeType] == undefined)?sMimeTypeImgPath+"empty.png":sMimeTypeImgPath+oImageMap[sMimeType];	
}

//@TODO: THIS NEEDS TO BE CHANGED ALL OVER THE APPLICATION!!!
var json = new JSON(); 

//fade(node,start,end,speed,step,cb)

function fade(node,start,end,duration,cb) {
	function final_cb() {
		if(cb) { cb(); }
	}
	if(configGetKey("gui.effects.global")) {
		animate(duration, Math.abs(start-end),
		function(val) {
			var tmpvalue=start;
			if(start>end) { tmpvalue=tmpvalue-val; }
			else { tmpvalue=tmpvalue+val; }
			fade_setOpacity(node,tmpvalue);
		},final_cb);
	} else {
		fade_setOpacity(node,end);
		final_cb();
	}
}
function fade_setOpacity(node,opacity) {
	if(configGetKey("gui.effects.fading")) {
		node.style.filter = "alpha(style=0,opacity:" + opacity + ")";	// IE
		node.style.MozOpacity = (opacity / 100);		// Gecko < 1.5
		node.style.opacity = (opacity / 100);		// Gecko >= 1.5
	} 
	if(opacity==0) {node.style.display="none"; return; }
	if(node.style.display=="none" || node.style.display=="NONE") {
		if(node.tagName == "DIV") { node.style.display = "block"; }
		else { node.style.display = ""; }
	} 
}
