/**
 * 
 * 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 addLanguageFile, 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);
}
	
(function() {
    var current, current_name;
    var files = ["lang/%s.js"];
    var languages = {};
    var originals = {};
	var counter = 0;

	_ = gettext = function(text) {
		text = String(text);
		if (!current) return text.substring(text.indexOf("|") + 1);
		if (current.dictionary)
			return current.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 (!current) return def;
		var translation = current.dictionary[singular + "\0" + plural];
		if (!translation) return def;
		return translation[Number(current.plural(n))] || def;
	};

    addLanguageFile = function(pattern) {
        if (current) {
            var lng = languages[name];
            if (!lng) lng = languages[name] = {};
            json.get(format(pattern, name), null,
                function(lang) {
                    for (var i in lang) lng[i] = lang[i];
                });
        } else
            files.push(pattern);
    }

	setLanguage = function(name) {
		if (languages[name]) {
			current = languages[name];
            current_name = name;
			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("LanguageChangedInternal");
			triggerEvent("LanguageChanged");
		} else {
			// check the main window, maybe we already loaded the i18n-js-file
			if (corewindow !== window && corewindow.getLanguage(name) != null) {
				languages[name] = corewindow.getLanguage(name);
				current=languages[name];
                setLanguage(name);
			} else {
                var json = new JSON();
                var join = new Join(function() { setLanguage(name); });
                var lng = languages[name] = {};
                for (var i = 0; i < files.length; i++) {
                    json.get(format(files[i], name), null,
                        join.add(function(lang) {
                            for (var i in lang) lng[i] = lang[i];
                        }));
				}
			}
		}
	};
	
	getLanguage = function(name) {
		if (languages && languages[name])
			return languages[name];
		else
			return null;
	};	

})();
/*
function parsePO(file) {
    var po = {};
    var tokenizer = /(^#.*$|^\s*$)|(.)|^(msg\w+)\s+|\s*"(.*)"\s*$/gm;
    tokenizer.lastIndex = 0;
    var line_no = 0;

    function error() {
        throw new Error("PO syntax error in line " + line_no);
    }
    
    var lookahead;
    function is(type) {
        if (tokenizer.lastIndex >= file.length) return false;
        if (!lookahead) lookahead = tokenizer.exec();
        return Boolean(lookahead[type]);
    }
    
    function token(type) {
        if (tokenizer.lastIndex >= file.length) error();
        if (!lookahead) lookahead = tokenizer.exec();
        return lookahead[type];
    }
    
    function token(type) {
        var li = tokenizer.lastIndex;
        if (li >= file.length) return;
        var t = tokenizer.exec(file);
        if (t[1] || t[4]) line_no++;
        if (t[2]) error();
        return t;
    }

    function string() {
        var t = token();
        if (!t[4]) error();
        var s = t[4];
        var li = tokenizer.lastIndex;
        while 
    }

    function next(name) {
        var t = token();
        if (t[3] == name) return string();
        error();
    }
    
    function optional(name) {
        var li = tokenizer.lastIndex;
        var t = token();
        if (t[3] == name) return string();
        tokenizer.lastIndex = li;
    }

    function entry() {
        var ctx = optional("msgctxt");
        var key = next("msgid");
        if (ctx) key = ctx + "\1" + key; 
        var plural = optional("msgid_plural");
        var value;
        if (plural) {
            key += "\0" + plural;
            value = {};
            for (var i = 0; i < nplurals; i++) {
                str[i] = next("msgstr[" + i + "]");
            }
        } else {
            value = next("msgstr");
        }
        current[key] = value;
    }
    
    while (token());
}

/**
 * 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.
 * @param {Object} template An optional object which is used for the initial
 * translation. All enumerable properties of the template will be copied to
 * the newly created object before the first call to callback.
 *
 * Fields of the created object:
 *
 * node: The DOM text node which is automatically translated.
 */
function I18nNode(callback, template) {
    if (template) for (var i in template) this[i] = template[i];
    this.callback = callback;
    this.index = ++I18nNode.counter;
	this.node = document.createTextNode(this.callback());
	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;
}

/**
 * Returns whether a date is today.
 * @param utc The date. Any valid parameter to new Date() will do.
 * @type Boolean
 * @return true if the parameter has today's date, false otherwise.
 */
function isToday(utc) {
    var today = new Date(now());
    today.setUTCHours(0, 0, 0, 0);
    var diff = (new Date(utc)).getTime() - today.getTime();
    return diff >= 0 && diff < 864e5; // ms/day
}

/**
 * The first week with at least daysInFirstWeek days in a given year is defined
 * as the first week of that year.
 */
var daysInFirstWeek = 4;

/**
 * First day of the week
 * 0 = Sunday, 1 = Monday and so on.
 */
var weekStart = 1;

function getDays(d) { return Math.floor(d / 864e5); }

/**
 * Computes the week number of the specified Date object, taking into account
 * daysInFirstWeek and weekStart.
 * @param {Date} d The date for which to calculate the week number.
 * @param {Boolean} inMonth True to compute the week number in a month,
 * False for the week number in a year 
 * @type Number
 * @return Week number of the specified date.
 */
function getWeek(d, inMonth) {
	var firstDay = getDays(d.getTime()) - (d.getUTCDay() - weekStart + 7) % 7;
	var keyDay = firstDay + 7 - daysInFirstWeek;
	var keyDate = new Date(keyDay * 864e5);
	var jan1st = Date.UTC(keyDate.getUTCFullYear(),
	                      inMonth ? keyDate.getUTCMonth() : 0);
	return Math.floor((keyDay - getDay(jan1st)) / 7) + 1;
}

/**
 * Formats a Date object according to a format string.
 * @function
 * @param {String} format The format string. It has the same syntax as Java's
 * java.text.SimpleDateFormat, assuming a Gregorian calendar.
 * @param {Date} date The Date object to format. It must contain a Time value as
 * defined in the HTTP API specification.
 * @type String
 * @return The formatted date and/or time.
 */
var formatDateTime;

/**
 * Parses a date and time according to a format string.
 * @function
 * @param {String} format The format string. It has the same syntax as Java's
 * java.text.SimpleDateFormat, assuming a Gregorian calendar.
 * @param {String} string The string to parse.
 * @type Date
 * @return The parsed date as a Date object. It will contain a Time value as
 * defined in the HTTP API specification.
 */
var parseDateTime;

/**
 * An array with translated week day names.
 */
var weekdays = [];

(function() {

    var regex = /(G+|y+|M+|w+|W+|D+|d+|F+|E+|a+|H+|k+|K+|h+|m+|s+|S+|z+|Z+)|\'(.+?)\'|(\'\')/g;

	function num(n, x) {
		var s = x.toString();
		n -= s.length;
		if (n <= 0) return s;
		var a = new Array(n);
		for (var i = 0; i < n; i++) a[i] = "0";
		a[n] = s;
		return a.join("");
	}
	function text(n, full, shrt) {
		return n >= 4 ? _(full) : _(shrt);
	}
	var months = [
		"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 shortMonths = [
		"Jan"/*i18n*/, "Feb"/*i18n*/, "Mar"/*i18n*/, "Apr"/*i18n*/,
		"May"/*i18n*/, "Jun"/*i18n*/, "Jul"/*i18n*/, "Aug"/*i18n*/,
		"Sep"/*i18n*/, "Oct"/*i18n*/, "Nov"/*i18n*/, "Dec"/*i18n*/
	];
	var days = weekdays.untranslated = [
		   "Sunday"/*i18n*/,   "Monday"/*i18n*/, "Tuesday"/*i18n*/,
		"Wednesday"/*i18n*/, "Thursday"/*i18n*/,  "Friday"/*i18n*/,
		 "Saturday"/*i18n*/
	];
	var shortDays = [
		"Sun"/*i18n*/, "Mon"/*i18n*/, "Tue"/*i18n*/, "Wed"/*i18n*/,
		"Thu"/*i18n*/, "Fri"/*i18n*/, "Sat"/*i18n*/
	];
	var funs = {
		G: function(n, d) {
			return d.getTime() < -62135596800000 ? _("BC") : _("AD");
		},
		y: function(n, d) {
			var y = d.getUTCFullYear();
			if (y < 1) y = 1 - y;
			return num(n, n == 2 ? y % 100 : y);
		},
		M: function(n, d) {
			var m = d.getUTCMonth();
			if (n >= 3) {
				return text(n, months[m], shortMonths[m]);
			} else {
				return num(n, m + 1);
			}
		},
		w: function(n, d) { return num(n, getWeek(d)); },
		W: function(n, d) { return num(n, getWeek(d, true)); },
		D: function(n, d) {
			return num(n,
				getDays(d.getTime() - Date.UTC(d.getUTCFullYear(), 0)) + 1);
		},
		d: function(n, d) { return num(n, d.getUTCDate()); },
		F: function(n, d) {
			return num(n, Math.floor(d.getUTCDate() / 7) + 1);
		},
		E: function(n, d) {
			var m = d.getUTCDay();
			return text(n, days[m], shortDays[m]);
		},
		a: function(n, d) {
            return d.getUTCHours() < 12 ? _("AM") : _("PM");
        },
		H: function(n, d) { return num(n, d.getUTCHours()); },
		k: function(n, d) { return num(n, d.getUTCHours() || 24); },
		K: function(n, d) { return num(n, d.getUTCHours() % 12); },
		h: function(n, d) { return num(n, d.getUTCHours() % 12 || 12); },
		m: function(n, d) { return num(n, d.getUTCMinutes()); },
		s: function(n, d) { return num(n, d.getUTCSeconds()); },
		S: function(n, d) { return num(n, d.getMilliseconds()); }
		// TODO: z and Z 
	};
	formatDateTime = function(format, date) {
		return format.replace(regex,
			function(match, fmt, text, quote) {
				if (fmt) {
					return funs[fmt.charAt(0)](fmt.length, date);
				} else if (text) {
					return text;
				} else if (quote) {
					return "'";
				}
			});
	};
    
    var f = "G+|y+|M+|w+|W+|D+|d+|F+|E+|a+|H+|k+|K+|h+|m+|s+|S+|z+|Z+";
    var pregexStr = "(" + f + ")(?!" + f + ")|(" + f + ")(?=" + f +
        ")|\'(.+?)\'|(\'\')|([$^\\\\.*+?()[\\]{}|])";
    var pregex = new RegExp(pregexStr, "g");
    
    var monthRegex = "(" + months.concat(shortMonths).join("|") + ")";
    
    var dayRegex = "(" + days.concat(shortDays).join("|") + ")";
    
    var monthMap = {};
    var dayMap = {};
    function recreateMaps() {
        monthMap = {};
        for (var i = 0; i < months.length; i++) {
            monthMap[_(months[i])] = i;
            monthMap[_(shortMonths[i])] = i;
        }
        dayMap = {};
        for (var i = 0; i < days.length; i++) {
            dayMap[days[i]] = i;
            dayMap[shortDays[i]] = i;
        }
        weekdays.length = days.length;
        for (var i = 0; i < days.length; i++) weekdays[i] = _(days[i]);
    }
    recreateMaps();
    register("LanguageChangedInternal", recreateMaps);
    
    function escape(rex) {
        return rex.replace(/[$^\\.*+?()[\]{}|]/g, "\\$");
    }

    var numRex = "([+-]?\\d+)";
    function number(n) { return numRex; }
        
    var prexs = {
        G: function(n) {
            return "(" + escape(_("BC")) + "|" + escape(_("AD")) + ")";
        },
        y: number,
        M: function(n) { return n >= 3 ? monthRegex : numRex; },
        w: number, W: number, D: number, d: number, F: number, E: number,
        a: function(n) {
            return "(" + escape(_("AM")) + "|" + escape(_("PM")) + ")";
        },
        H: number, k: number, K: number, h: number, m: number, s: number,
        S: number
        // TODO: z and Z
    };
    
    function mnum(n) {
        return n > 1 ? "([+-]\\d{1," + (n - 1) + "}|\\d{1," + n + "})"
                     :                           "(\\d{1," + n + "})"; }
    
    var mrexs = {
        G: prexs.G, y: mnum,
        M: function(n) { return n >= 3 ? monthRegex : mnum(n); },
        w: mnum, W: mnum, D: mnum, d: mnum, F: mnum, E: prexs.E, a: prexs.e,
        H: mnum, k: mnum, K: mnum, h: mnum, m: mnum, s: mnum, S: mnum
        // TODO: z and Z
    };
    
    var pfuns = {
        G: function(n) { return function(s, d) { d.bc = s == _("BC"); }; },
        y: function(n) {
            return function(s, d) {
                d.century = n <= 2 && s.match(/^\d\d$/);
                d.y = s;
            };
        },
        M: function(n) {
            return n >= 3 ? function (s, d) { d.m = monthMap[s]; }
                          : function(s, d) { d.m = s - 1; };
        },
        w: emptyFunction, W: emptyFunction, D: emptyFunction,
        d: function(n) { return function(s, d) { d.d = s }; },
        F: emptyFunction, E: emptyFunction,
        a: function(n) { return function(s, d) { d.pm = s == _("PM"); }; },
        H: function(n) { return function(s, d) { d.h = s; }; },
        k: function(n) { return function(s, d) { d.h = s == 24 ? 0 : s; }; },
        K: function(n) { return function(s, d) { d.h2 = s; }; },
        h: function(n) { return function(s, d) { d.h2 = s == 12 ? 0 : s; }; },
        m: function(n) { return function(s, d) { d.min = s; }; },
        s: function(n) { return function(s, d) { d.s = s; }; },
        S: function(n) { return function(s, d) { d.ms = s; }; }
        // TODO: z and Z
    };
    
    var threshold = new Date();
    var century = Math.floor((threshold.getUTCFullYear() + 20) / 100) * 100;
    
    parseDateTime = function(formatMatch, string) {
        var handlers = [];
        var rex = formatMatch.replace(pregex,
            function(match, pfmt, mfmt, text, quote, escape) {
                if (pfmt) {
                    handlers.push(pfuns[pfmt.charAt(0)](pfmt.length));
                    return prexs[pfmt.charAt(0)](pfmt.length);
                } else if (mfmt) {
                    handlers.push(pfuns[mfmt.charAt(0)](mfmt.length));
                    return mrexs[mfmt.charAt(0)](mfmt.length);
                } else if (text) {
                    return text;
                } else if (quote) {
                    return "'";
                } else if (escape) {
                    return "\\" + escape;
                }
            });
        var match = string.match(new RegExp("^\\s*" + rex + "\\s*$", "i"));
        if (!match) return null;
        var d = { bc: false, century: false, pm: false,
            y: 1970, m: 0, d: 1, h: 0, h2: 0, min: 0, s: 0, ms: 0 };
        for (var i = 0; i < handlers.length; i++)
            handlers[i](match[i + 1], d);
        if (d.century) {
            d.y = Number(d.y) + century;
            var date = new Date(0);
            date.setUTCFullYear(d.y - 20, d.m, d.d);
            date.setUTCHours(d.h, d.min, d.s, d.ms);
            if (date.getTime() > threshold.getTime()) d.y -= 100;
        }
        if (d.bc) d.y = 1 - d.y;
        if (!d.h) d.h = Number(d.h2) + (d.pm ? 12 : 0);
        var date = new Date(0);
        date.setUTCFullYear(d.y, d.m, d.d);
        date.setUTCHours(d.h, d.min, d.s, d.ms);
        return date;
    };

})();

/**
 * Format UTC into human readable date and time formats
 * @function
 * @param {Date} date The date and time as a Date object.
 * @param {String} format A string which selects one of the following predefined
 * formats: <dl>
 * <dt>date</dt><dd>only the date</dd>
 * <dt>time</dt><dd>only the time</dd>
 * <dt>datetime</dt><dd>date and time</dd>
 * <dt>dateday</dt><dd>date with the day of week</dd>
 * <dt>hour</dt><dd>hour (big font) for timescales in calendar views</dd>
 * <dt>suffix</dt><dd>suffix (small font) for timescales in calendar views</dd>
 * <dt>onlyhour</dt><dd>2-digit hour for timescales in team views</dd></dl>
 * @type String
 * @return The formatted string
 */
var formatDate;

/**
 * Parse human readable date and time formats
 * @function
 * @param {String} string The string to parse
 * @param {String} format A string which selects one of the following predefined
 * formats:<dl>
 * <dt>date</dt><dd>only the date</dd>
 * <dt>time</dt><dd>only the time</dd></dl>
 * @type Date
 * @return The parsed Date object or null in case of errors.
 */
var parseDateString;

(function() {
    var formats;
    function updateFormats() {
        var date_def = configGetKey("gui.global.region.date.predefined") != 0;
        var time_def = configGetKey("gui.global.region.time.predefined") != 0;
        var date = date_def ? _("yyyy-MM-dd")
                            : configGetKey("gui.global.region.date.format");
        var time = time_def ? _("HH:mm")
                            : configGetKey("gui.global.region.time.format");
        var hour = configGetKey("gui.global.region.time.format_hour");
        var suffix = configGetKey("gui.global.region.time.format_suffix");
        formats = {
            date: date,
            time: time,
            //#. The relative position of date and time.
            //#. %1$s is the date
            //#. %2$s is the time
            datetime: format(_("%1$s %2$s"), date, time),
            //#. The date with the day of the week.
            //#. EEEE is the full day of the week,
            //#. EEE is the short day of the week,
            //#. %s is the date.
            dateday: format(_("EEEE, %s"), date),
            //#. The format for calendar timescales
            //#. when the interval is at least one hour.
            //#. H is 1-24, HH is 01-24, h is 1-12, hh is 01-12, a is AM/PM,
            //#. mm is minutes.
            hour: time_def ? _("dayview|HH:mm") : hour,
            //#. The format for hours on calendar timescales
            //#. when the interval is less than one hour.
            prefix: time_def ? _("dayview|HH") : suffix ? "hh" : "HH",
            //#. The format for minutes on calendar timescales
            //#. when the interval is less than one hour.
            //#. 12h formats should use AM/PM ("a").
            //#. 24h formats should use minutes ("mm").
            suffix: time_def ? _("dayview|mm") : suffix ? "a" : "mm",
            //#. The format for team view timescales
            //#. HH is 01-24, hh is 01-12, H is 1-24, h 1-12, a is AM/PM
            onlyhour: time_def ? _("teamview|H") : suffix ? "ha" : "H"
        };
    }
    register("LanguageChangedInternal", updateFormats);
    register("OX_Configuration_Changed", updateFormats);

    formatDate = function(date, format) {
        return formatDateTime(formats[format], new Date(date));
    };    

    parseDateString = function(string, format) {
        return parseDateTime(formats[format || "date"].replace("yyyy","yy"), string);
    };

})();

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);
	}
}

var currencies = [
            { iso: "CAD", name: "Canadian dollar", isoLangCodes: [ "CA" ] },
            { iso: "CHF", name: "Swiss franc", isoLangCodes: [ "CH" ] },
            { iso: "DKK", name: "Danish krone", isoLangCodes: [ "DK" ] },
            { iso: "EUR", name: "Euro", isoLangCodes: [ "AT", "BE", "CY", "FI", "FR", "DE", "GR", "IE", "IT", "LU", "MT", "NL", "PT", "SI", "ES" ] },
            { iso: "GBP", name: "Pound sterling", isoLangCodes: [ "GB" ] },
            { iso: "PLN", name: "Zloty", isoLangCodes: [ "PL" ] },
            { iso: "RUB", name: "Russian rouble", isoLangCodes: [ "RU" ] },
            { iso: "SEK", name: "Swedish krona", isoLangCodes: [ "SE" ] },
            { iso: "USD", name: "US dollar", isoLangCodes: [ "US" ] }
        ];    
/**
 * 
 * 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 debug = false;  // IMPORTANT!: This var is for debugging and should never set to true.
var bUWAEnabled = true;
var AjaxRoot = "/ajax";
var help_location = "[protocol]://[hostname][path]help/[language{1}]/";
var logout_location = "[protocol]://[hostname][path]";
var sessionExpired_location = "[protocol]://[hostname][path]";
var directLink_location = "[protocol]://[hostname][path]#m=[module]&f=[folder]&i=[object_id]";

/**
 * Object holds Product-Info
 * Will be changed by build system, so don't edit it directly please!!
 */
var oxProductInfo = { 
	id: "OX6", 
	version: "6.6.0", 
	build: "6610", 
	product_name: "Open-Xchange Server", 
	vendor_address: "Open-Xchange Inc\n303 South Broadway\nTarrytown, New York 10591\nE-Mail: info@open-xchange.com" };

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.
 */

  ///////////////////////////
 //   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));	
	}
	if (!configContainsKey("gui.mail.auto_save_drafts")) {
		configSetKey("gui.mail.auto_save_drafts",3);
	}
});
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 {Function} 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   //
//////////////

/**
 * @class 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 cssload = new Join(myjoin.add(function() {
	preloadCSSFiles();
}))
var cacheload = new Join(myjoin.add());
var fileloaded = function() {};
var wholeelement=new Object();
var rootebene=this;
var htmljsarray=new Array();
var jsarray=new Array();
var cssarray=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);
	}
}
function preloadCSSFiles() {
	for(var i=0;i<cssarray.length;i++) {
		var node=wholeelement["css"][cssarray[i]]
		var d=newnode("style",null,{type : "text/css"},[]);
		d.appendChild(document.createTextNode(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("");
}

function loadCSSFileForNewWindow(page) {
	var d=newnode("link",null,{type : "text/css",  rel:"stylesheet", href : page });	
	body.parentNode.getElementsByTagName("head")[0].appendChild(d);
}

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 storeCSSFile(node,param) {
		if(!wholeelement["css"]) wholeelement["css"]=new Object();
		wholeelement["css"][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 loadCSSFile(page) {
		debugCSSLoad(page);
		return;
		if(debug) { debugCSSLoad(page)}  
		else {
			cssarray[cssarray.length]=page;
			loadFile(page,cssload.add(storeCSSFile),page);
		}
	}
	
	function debugJSLoad(page) {
		fileloaded=jsload.add();
		var d=newnode("script",null,{type : "text/javascript"  , src : page });	
		body.parentNode.getElementsByTagName("head")[0].appendChild(d);
	}	
	
	function debugCSSLoad(page) {
		//fileloaded=cssload.add();
		var d=newnode("link",null,{type : "text/css",  rel:"stylesheet", href : page });	
		body.parentNode.getElementsByTagName("head")[0].appendChild(d);
	}
	
	function removeCSSFile(page) {
		var d = body.parentNode.getElementsByTagName("link");
        for (var i=0; i < d.length; i++) {
        	if (d[i] && d[i].getAttribute("REL") && d[i].getAttribute("HREF") == page) {
        		d[i].parentNode.removeChild(d[i]);
        	}
        }
	}

	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(cssload.add())
//	tmparray.push(externalJoin.add());

	/*Load always needed contents */
	loadHTMLFile("mainpage.html");
	loadHTMLJSFile("mainpage.js","core");
	loadJSFile("concat_core.js");
	register("OX_Configuration_Loaded", function() {
		if(configContainsKey("gui.theme") && configContainsKey("modules.themes." + configGetKey("gui.theme").path))
			var themeStyle = configGetKey("gui.theme");
		else {
			var themeStyle = { name: "Default", path: "default"};
			configSetKey("gui.theme",themeStyle);
		}			
		loadCSSFile("themes/"+themeStyle.path+"/css/calendar.css");
		loadCSSFile("themes/"+themeStyle.path+"/css/global.css");
		loadCSSFile("themes/"+themeStyle.path+"/css/popup.css");
		loadCSSFile("themes/"+themeStyle.path+"/css/bgimages.css");

        // remove some of the default styleheets to prevent duplicate entries
	    if (themeStyle.path != "default") {
            removeCSSFile("themes/default/css/global.css");
            removeCSSFile("themes/default/css/bgimages.css");
	    }
	});
	
	/* 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/api_routing_planner.js")
		loadJSFile("js/contacts/contacts.js")
		loadJSFile("js/contacts/plugin_routing_planner.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);
		// change cached img path according to theme location
		changeImgPath(configGetKey("gui.theme.path"));
		appendNode("everything");
		
		register("Logout", logout);
	});
	
	triggerEvent("OX_Login");
}

/**
 * Returns the img path, required for themes when adding runtime images
 * @param String The image location, anything behind the theme location
 * (e.g. themes/default/)
 */
function getFullImgSrc(img) {
	return "themes/" + (configContainsKey("gui.theme.path") ? configGetKey("gui.theme.path") : "default") + "/" + img;
}

/**
 * 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;
					newAlert(_("Session has expired"), _("Your session has expired. Please log in again."), 
					   function() {
						   	window.onbeforeunload = null;
	                        setTimeout( function() { window.location.replace(sessionExpired_location.format()); },0);	   	
					   });

				} else
				    newAlert(_("Session has expired"), _("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 = function() {
    now = function() { return (new Date()).getTime() + now.offset; };
    now.offset = corewindow.now.offset;
    return now();
};

register("OX_Configuration_Loaded", function() {
    now();
    now.offset = configGetKey("currentTime") - (new Date()).getTime();
});

  ///////////////////////////
 //   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 =[];

var addModuleIcon = emptyFunction; // see sidepanel/sidepanel.js

function registerModule(name, text, priority) {
	addModuleIcon({ name: name, text: text, prio: 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) {
	addModuleIcon({ name: name, text: text, prio: priority, disable: true });
	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});
}

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 = getFullImgSrc(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.
     * @return A removed item if cache is full or <code>undefined</code>
     * found.
     */
    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 {
                var key1=this.list.removeFirst().key;
                var ret=this.cache[key1].data;
                delete this.cache[key1];
                return ret;
            } 
        }
    },

    /**
     * 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;
        if (index in this.storage.ids)
            return this.serialize(this.storage.ids[index]) in this.data;
        return false;
    },
    
    /**
     * 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){
				if (callback) callback();
				return;
			}
			if (this.storage.serverUpdates) {
				this.storage.serverUpdates();
				if (callback) callback();
				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 tabPanelColors font-color-disabled background-color-additional-content border-color-design font-weight-default' : 'tabPanel tabPanelColors font-color-disabled background-color-additional-content border-color-design font-weight-default';
				$(panelElement).className = classNameNew;
			}						
		} else {
			if($(tabElement))
				$(tabElement).style.display = 'block';
				if(eventList != null)
					triggerEvent(eventElement[0], eventElement[1]);			
				
			if($(panelElement))
			{
				classNameNew = (tabElementNr == 0)? 'tabPanelFirstHi tabPanelHiColor background-color-content font-style-lable border-color-content-default' : 'tabPanelHi tabPanelHiColor background-color-content font-style-lable border-color-content-default';
				$(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,newlivegrid){
    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 || ids[0].folder_id)
                + "&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) {  
                if(newlivegrid) {
                
                }
                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 || ids[i].folder_id), 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){
                if(newlivegrid) {
                }
                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(" + getFullImgSrc("img/tabs/tabPanelBorderBg.gif") + ") 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. The final callback can
 * be suppressed by specifying true as parameter to the cancelling function.
 */
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 && 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;
		}
	}
   if(corewindow != window) {
	   setLanguage(corewindow.configGetKey("language"));
	}
	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: getFullImgSrc("img/x.png") })])]);
		var logger = newnode("div", {zIndex: 9999, position: "absolute",
			width: "40em", height: "20em", overflow: "auto", right: 0,
			bottom: 0, border: "2px dashed red", background: "white"}, 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);
	
	
	var originalTitle = document.title; // Document title
	register("LanguageChanged", function() {
		document.title = _(originalTitle);
		resizeOXButtons(); // resizing ox buttons when changing the language
	});
	
	/*
	 * function to resize buttons, e.g. when changing the language
	 * _oxbuttons is an array, which holds a set of arrays with two parameter
	 */ 
	var _oxbuttons = [ ["mc_today_but", "mc_today_but-text"] ];
	function resizeOXButtons() {
		for (i = 0; i < _oxbuttons.length; i++) {
		    try {
		    	if (!$(_oxbuttons[i][0]) || Math.round(parseInt($(_oxbuttons[i][1]).offsetWidth) / pxPerEm) <= 0) continue;	    
			    $(_oxbuttons[i][0]).style.width = Math.round(parseInt($(_oxbuttons[i][1]).offsetWidth) / pxPerEm + 2)  + "em";
		    } catch (ex) { }
		}
    }

	// 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],
			             {module: module[1], folder: folder[1], id: id[1], folder_id: folder[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;
                newAlert(_("Session has expired"), _("Your session has expired. Please log in again."), 
                       function() {
                            window.onbeforeunload = null;
                            setTimeout( function() { window.location.replace(sessionExpired_location.format()); },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) return;
    var nodes = node.childNodes;
	if (nodes) while (nodes.length > 0) node.removeChild(nodes[0]);
}

/* 
 * 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)
{
    return String(withBlanks).replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1");
}

function validateEmail(mail) {
    var regexmail = /^([a-zA-Z0-9]|\\[-!#$%&'*+=?^_{|}~])+(\.([a-zA-Z0-9]|\\[-!#$%&'*+=?^_{|}~])+)*@[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\.[a-zA-Z]{2,6}$/
    return regexmail.test(mail);
}

//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);	
}

function extendConfObj(obj) {	
    obj["language{0}"] = configGetKey("language").split("_")[0];
    obj["language{1}"] = (configGetKey("language").split("_")[1]).toLowerCase();
    obj["protocol"] = location.protocol.match(/^(.*):/)[1];
    obj["hostname"] = location.host;
    obj["path"] = location.pathname.match(/(.*\/)(.*)/)[1];
    obj["file"] = location.pathname.match(/(.*\/)(.*)/)[2];
    return obj;
}

var _strFormatRegexp = new RegExp("\\[([^\\]]+)\\]", "g");
String.prototype.format = function(obj) {
	obj = extendConfObj(obj || {})
	function repl(_,val) {
        return obj[val] || configGetKey(val) || val;   
    }
    return this.replace(_strFormatRegexp, repl);
}

function redirect2Help(param) {
	window.open(param.format() || help_location.format(), 'oxhelp');
}
register("OX_Show_Help", redirect2Help);
		
register("OX_Show_About", function() {	
			$("about_product_name").firstChild.data = oxProductInfo.product_name;
			$("about_gui_version").firstChild.data = oxProductInfo.version + "-" + oxProductInfo.build;
		    $("about_vendor_address").innerHTML = oxProductInfo.vendor_address.replace(/\n/gi,"<br/>");
			$("about_server_version").firstChild.data = configGetKey("serverVersion");
			AboutPopup.openWindow();
		});

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" in window && 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 (newItem && 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(getFullImgSrc("img/dummy.gif"));
	loadFileForCacheOnInit(getFullImgSrc("img/mail/btnnew_email.gif"));
	loadFileForCacheOnInit(getFullImgSrc("img/mail/btnnew_email.gif"));
	loadFileForCacheOnInit(getFullImgSrc("img/mail/btnnew_email.gif"));
	loadFileForCacheOnInit(getFullImgSrc("img/portal/mod_portal_sel.gif"));
	loadFileForCacheOnInit(getFullImgSrc("img/calendar/mod_calendar.gif"));
	loadFileForCacheOnInit(getFullImgSrc("img/contacts/mod_contacts.gif"));
	loadFileForCacheOnInit(getFullImgSrc("img/tasks/mod_tasks.gif"));
	loadFileForCacheOnInit(getFullImgSrc("img/infostore/mod_infostore.gif"));
	loadFileForCacheOnInit(getFullImgSrc("img/configuration/mod_configuration.gif"));
	loadFileForCacheOnInit(getFullImgSrc("img/plus.gif"));
	loadFileForCacheOnInit(getFullImgSrc("img/noplus.gif"));
	loadFileForCacheOnInit(getFullImgSrc("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;
	oObj["folder"] = oObj.folder || oObj.folder_id;
	oObj["object_id"] = oObj.id;
	oObj["module"] = oObj.module || "infostore";
	return directLink_location.format(oObj);
}

function getMimeImage(sMimeType){
	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) ? getFullImgSrc("img/infostore/mimetypes/empty.png") : getFullImgSrc("img/infostore/mimetypes/" + 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) {
	if(configGetKey("gui.effects.fading")) {
		return 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);
    		}, cb);
	} else {
		fade_setOpacity(node, end);
		if (cb) cb();
        return emptyFunction;
	}
}
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
	} else {
		node.style.filter = "alpha(style=0,opacity:" + 100 + ")"; // IE
        node.style.MozOpacity = (100 / 100);        // Gecko < 1.5
        node.style.opacity = (100 / 100);       // Gecko >= 1.5
	}
*/
	if(opacity < 100 || configGetKey("gui.effects.fading")) {
    	node.style.filter = "alpha(style=0,opacity:" + opacity + ")"; // IE
        node.style.opacity = (opacity / 100);       // Gecko >= 1.5
    } else {
        node.style.filter = "";
        node.style.opacity = "";
    }
	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 = ""; }
	} 
}

/**
 * Compares two objects recursively.
 * @param a 
 * @param b 
 */
function equals(a, b) {
    if(a === b) return true;
    if (   !(a instanceof Object)
        || !(b instanceof Object)) return a == b;
    for(var child in a) 
        if(!equals(a[child], b[child])) return false;
    for(var child in b)
        if(!(child in a)) return false;
    return true;
}
  ///////////////////
 //   Selection   //
///////////////////

/**
 * Selection as a set of object IDs.
 */
function Selection2() {
    /**
     * Number of selected items.
     */
    this.count = 0;
    /**
     * A map from object IDs to object IDs.
     * @private
     */
    this.data = new LRUKeyList();
    
    /**
     * 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.collection;
    //TODO
    /**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);
    };***/
}

Selection2.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.collection) console.error("Selection.get without collection");
        var key = this.collection.objects[index];
        if (!key) return false;
        return this.data.get(key) != null;
    },
    
    /**
     * 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.collection) console.error("Selection.get2 without collection");
        var key = collection.objects[index];
        if (!key) return false;
        return this.data.get(key) != null;
    },
    /**
     * 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) {
        if (!this.collection) console.error("Selection.toggle without collection");
        var key = this.collection.objects[index];
        var oldValue = this.data.get(key) ;
        (oldValue)  ? this.data.remove(key) : this.data.set(key,true);
        this.count += oldValue ? -1 : 1;
        this.events.post("Selected", this.count);
        return !(oldValue != null) ;
    },
    
    /**
     * Deselects the specified serialized object IDs.
     * @param {Object} sids An object with serialized object IDs to deselect
     * as keys.
     */
    deselectSIDs: function(keys) {
        for (var i in keys) {
            var oldValue = this.data.get(keys[i]);
            this.count -= (oldValue) ? 1 : 0;
            this.data.remove(keys[i]);
        }
        this.events.post("Selected", this.count);
    },

    /**
     * Clears the entire selection.
     * @private
     */
    reset: function() {
        this.data = new LRUKeyList();
        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) {
         if (!this.collection) console.error("Selection.select without collection");
        for (var i = from; i < to; i++) {
            var key = this.collection.objects[i];
            var oldValue = this.data.get(key);
            if (!(oldValue)) {
                this.count++;
                this.data.set(key,true);
            }
        }
        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 myArray = new Array();
        var tmpKeys=this.data.keys();
        //FIX FOR UPDATES AFTER OBJECT IS STILL UPDATED E.G. UPDATE CONTACTS FLAGs two times on same object
        if(!this.collection) { return tmpKeys; }
        for(var i=0;i<tmpKeys.length;i++) {
            myArray.push(this.collection.objects[this.collection.map_objects.get(tmpKeys[i])-1]);
        }
	    return myArray;
    },
    
    /**
     * 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.collection) console.error("Selection.click without collection");
        if (index < 0 || index >= this.collection.objects.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.
     */
    setCollection: function(collection) {
        var oldcount=this.count;
        var updateSelection=true;
        if (collection) {
			var keys=this.data.keys();
            for (var id in keys) {
                if (!(collection.map_objects.get(keys[id]))) { 
                    this.data.remove(keys[id]);
                    this.count--;
                } 
                else { updateSelection=false; }
            }
        }
        this.collection = collection;
        if (collection) {
            if(updateSelection) { this.click(0); }
			else { this.events.post("Selected", this.count); }
        } 
    },
    
    /**
     * @deprecated
     */
    getID: function() {
        return this.data.get(this.data.keys()[0]);
    }
};
