/**
 * 
 * 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) Open-Xchange Inc., 2006-2011
 * Mail: info@open-xchange.com 
 * 
 * @author Viktor Pracht <viktor.pracht@open-xchange.com>
 * 
 */

var ox = {};
var debug = false;  // IMPORTANT!: This var is for debugging and should never set to true.
var debugPlugins = false;
var debugJSON = false;
var activefolder = null;
var bUWAEnabled = true, embedded = false;
var globalContextMenus = {}, menuarrows = {};
var AjaxRoot = "/ajax";
var help_location = "[protocol]://[hostname][path]@helplocation@/[language]/";
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]";
//#. The UWA module description at the user options.
//#. The two %s indicate the start and end of a hyperlink.
//#, c-format
var uwaLink = { text: "Please have a look at the %sInteresting UWA modules%s page. It shows a list of widgets and their associated data." /*i18n*/, link: "http://www.open-xchange.com/index.php?id=361&L=[language{0}]" };

/**
 * Object which holds Product-Information
 * Will be changed by the build system, so please don't edit it directly!!!
 */
var oxProductInfo = { 
    id:              "com.openexchange.ox.gui.dhtml", 
    version:         "@ox:version@", 
    revision:         "@ox:revision@",
    pversion:        "@ox:pversion@",
    build:             "@ox:build@", 
    product_name:     "Open-Xchange Server", 
    vendor_address:    "Open-Xchange AG\nMaxfeldstra&szlig;e 9\nD-90409 N&uuml;rnberg\nE-Mail: info@open-xchange.com" };

var oxThemeStyle = { name: "Default", path: "default" };

function isOldGecko() {
    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;
}
var oldGecko = isOldGecko(); 

////////////////////////////////
// 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);
    }
    var interval = configGetKey("cookielifetime");
    if (interval > 0) {
        setInterval(function() {
            ox.JSON.get(AjaxRoot + "/login?action=refreshSecret&session=" +
                session, jQuery.noop);
            if (autologinEnabled) storeSession();
        }, interval * 900);
    }
});
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 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.
     */
    alt: function(callback) {
        var Self = this;        
        return function() {
            var retval;
            if (callback) retval = callback.apply(this, arguments);
            if (!--Self.count) Self.callback();
            return retval;        
        };
    }
};

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

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

    var stopNormal = function (e) {
        e.preventDefault();
        e.stopPropagation();
    };
    
    var stopIE = function (e) {
        e.returnValue = false;
        e.cancelBubble = true;
    };
    
    // is IE? (copied from api.js' ox.browser namespace)
    var ua = navigator.userAgent;
    var isOpera = Object.prototype.toString.call(window.opera) === "[object Opera]";
    var IE = !!window.attachEvent && !isOpera;
    
    return IE ? stopIE : stopNormal;
    
}());

/**
 * 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.
 */
function cancelBubbling(e) {
    if (e.stopPropagation) e.stopPropagation(); else e.cancelBubble = true;
};

/**
 * @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 document.addEventListener ? addNormal : addIE;
    
})();

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


function setLoading () { }
function setLoadingCursor (){ }

  //////////////
 //   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
 */
var nativeJSON = JSON; // save native JSON
var JSON = function () {
    /**
     * @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 (ox.util.isArray(data)) { // cross-window IE support
        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.ignore404 = function(result, status) { return status == 404; };

JSON.count = 0;
JSON.cursorCount = 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
        };
        if (debug) request.stack = getStack(3, 2);
        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
        };
        if (debug) request.stack = getStack(3, 2);
        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
        };
        if (debug) request.stack = getStack(3, 2);
        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++;
        JSON.cursorCount++;
        if (!(this.processing = this.first != null)) {
            if (!--JSON.count) setLoading(false);
            if (!--JSON.cursorCount) setLoadingCursor(false);
            return;
        }
        var xmlhttp = this.getXmlHttp();
        var Self = this;
        if (debugJSON) {
            var debug_cb = JSON.debug(this.first.method + " " + this.first.uri,
                [this.first.data].concat(JSON.getHTMLStackTrace(2)),
                callback);
            xmlhttp.onreadystatechange = function() {
                if (xmlhttp.readyState == 4) debug_cb();
            };
        } else {
            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) setLoading(true);
        if (JSON.cursorCount == 1) setLoadingCursor(true);
        function callback() {
            if (xmlhttp.readyState != 4) return;
            JSON.count--;
            JSON.cursorCount--;
            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, uri = Self.first.uri, data = Self.first.data;
            if (debug) trace = Self.first.stack;
            Self.remove();
            var result = {};
            if (xmlhttp.status != 200) {
                errorHandler(xmlhttp.statusText, xmlhttp.status);
                if (debug) trace = null;
                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 response (%s): \"%s\"\n\nURL: %s\n\nData: %s\n\nResponse: %s"),
                            formatDate(new Date(), "datetime"),
                            e.message,
                            uri,
                            data,
                            s
                        )
                    );
                    if (debug) trace = null;
                    Self.process();
                    return;
                }
                if (result && typeof(result) == "object" && result.error) {
                    if (result.category == 13) {
                        newServerError(result);
                    } else {
                        errorHandler(result);
                        if (debug) trace = null;
                        Self.process();
                        return;
                    }
                }
            }
            if (window.console && console.exception) {
                try {
                    cb(result, xmlhttp.statusText, xmlhttp);
                } catch (e) {
                    console.exception(e);
                }
                if (debug) trace = null;
                Self.process();
            } else {
                try {
                    cb(result, xmlhttp.statusText, xmlhttp);
                } finally {
                    if (debug) trace = null;
                    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
        //#, c-format
        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();
            }
        }
    }
    if (debugJSON) {
        var requests = {};
        var id = 0;
        var w = JSON.debugWindow = open("about:blank", "oxJSONDebugger");
        w.document.open();
        w.document.write(
            "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01//EN\" " +
            "\"http://www.w3.org/TR/html4/strict.dtd\">" +
            "<html><head><title>JSON debugger</title></head>" +
            "<body><button type=\"button\" id=\"toggle\">Enable</button>" +
            "<button type=\"button\" id=\"record\">Record</button>" +
            "<div id=\"recorded\">&#xa0;</div>" +
            "<ul id=\"ul\"></ul></body></html>");
        w.document.close();
        JSON.debugDocument = w.document;
        var ul = w.document.getElementById("ul");
        var toggle = w.document.getElementById("toggle");
        var record = w.document.getElementById("record");
        var recorded = w.document.getElementById("recorded");
        toggle.addEventListener("click", function() {
            debugJSON = !debugJSON;
            record.disabled = debugJSON;
            toggle.firstChild.data = debugJSON ? "Disable" : "Enable";
        }, false);
        record.addEventListener("click", function() {
            if (debugJSON) {
                recorded.firstChild.data = JSON.getRecordedCombination();
                record.firstChild.data = "Record";
            } else {
                JSON.recordCombination();
                record.firstChild.data = "Stop";
            }
        }, false);
        debugJSON = false;
        JSON.debug = JSON.defaultDebug = function(caption, text, callback) {
            var plus = newnode("img", 0, { src: "themes/default/img/plus.gif" },
                0, w.document);
            for (var i = 0; i < text.length; i++) {
                if (typeof text[i] == "string") {
                    text[i] = newtext(text[i], w.document);
                }
            }
            var data = newnode("pre", { display: "none" }, 0, text, w.document);
            var finish = newnode("button", 0,
                { type: "button", disabled: true },
                [newtext("Finish", w.document)], w.document);
            var debugBtn = newnode("button", 0,
                { type: "button", disabled: true },
                [newtext("Debug", w.document)], w.document);
            var li = newnode("li", 0, 0,
                [plus, newtext(caption, w.document), finish, debugBtn, data],
                w.document);
            ul.appendChild(li);
            plus.addEventListener("click", function() {
                if (data.style.display == "none") {
                    plus.src = "themes/default/img/minus.gif";
                    data.style.display = "block";
                } else {
                    plus.src = "themes/default/img/plus.gif";
                    data.style.display = "none";
                }
            }, false);
            finish.addEventListener("click", function() {
                ul.removeChild(li);
                callback();
            }, false);
            debugBtn.addEventListener("click", function() {
                ul.removeChild(li);
                eval("debugger");
                callback();
            }, false);
            var f = function() {
                finish.disabled = false;
                debugBtn.disabled = false;
            };
            f.cancel = function() { ul.removeChild(li); };
            return f;
        };
        var oldSetTimeout = JSON.realSetTimeout = window.setTimeout;
        JSON.setDebugCombination = function(combination) {
            var c = this.debugCombination = combination ? combination.split(",")
                                                        : [];
            for (var i = 0; i < c.length; i++) c[i] = Number(c[i]);
            this.lastChoice = 0;
            var index = 0;
            var pending = [];
            var join = new Join(function() {
                while (!this.count && pending.length) {
                    var choice;
                    if (index >= c.length) {
                        choice = 0;
                    } else if (index == c.length - 1) {
                        choice = c[index] + 1;
                        
                    } else {
                        choice = c[index];
                    }
                    if (choice >= pending.length) {
                        throw new Error("Invalid debug choice " + choice +
                            " in [" + c + "] at " + index + ".");
                    }
                    c[index++] = choice;
                    if (choice < pending.length - 1) JSON.lastChoice = index;
                    var callback = pending[choice];
                    pending.splice(choice, 1);
                    callback();
                }
            });
            this.debug = function(caption, text, callback) {
                pending.push(callback);
                return join.add();
            };
            debugJSON = true;
            JSON.getDebugCombination = function() {
                debugJSON = false;
                return this.debugCombination.slice(0, this.lastChoice).join();
            };
        };
        JSON.recordCombination = function() {
            var recordedCombination = [], pending = [];
            var callID = 0;
            this.debug = function(caption, text, callback) {
                var id = callID++;
                pending.push({ id: id, caption: caption, callback: callback,
                    toString: function() {
                        return this.id + ": " + this.caption;
                    } });
                return function() {
                    for (var i = 0; i < pending.length; i++) {
                        if (pending[i].id == id) {
                            var cb = pending.splice(i, 1)[0];
                            recordedCombination.push(i);
                            cb.callback();
                            break;
                        }
                    }
                };
            };
            this.getRecordedCombination = function() {
                debugJSON = false;
                this.debug = this.defaultDebug;
                return recordedCombination.join();
            };
            debugJSON = true;
        };
    }
})();

  //////////////////////
 //   Stack traces   //
//////////////////////

(function() {
    if (!debug) return;
    trace = null;
    getStack = function(start, end) {
        return { stack: (new Error).stack, next: trace,
                 start: start, end: end };
    };
    try { getStack(); } catch (e) { return; }
    function wrap(f, stack) {
        return function() {
            trace = stack;
            var retval = f.apply(this, arguments);
            trace = null;
            return retval;
        };
    }
    
    getStackTrace = function() {
        var retval = [];
        for (var s = getStack(3); s; s = s.next) {
            var stack = s.stack.split("\n");
            var end = stack.length - (s.next ? s.next.end : 0);
            for (var i = s.start; i < end; i++) {
                var entry = stack[i];
                if (/function/.test(entry)) {
                    entry = entry.replace(/^(\w*\().*(\)@\S+:\d+)$/, "$1...$2");
                }
                if (/@data:/.test(entry)) {
                    entry = entry.replace(/(@data:)\S+(:\d+)$/, "$1...$2");
                }
                retval.push(entry);
            }
            retval.push("---");
        }
        return { stack: retval.join("\n") };
    };
    
    var sources = {}, sourceID = 0;
    function showSource(file, line) {
        return function(e) {
            e.preventDefault();
            var w = sources[file];
            if (!w || w.closed) {
                var target = "oxJSONDebuggerSource" + sourceID++;
                w = sources[file] = open("about:blank", target);
                w.document.open();
                w.document.write(
                    "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01//EN\" " +
                    "\"http://www.w3.org/TR/html4/strict.dtd\">" +
                    "<html><head><title>" +
                    file.replace(/^.*\/([^\/]+)$/, "$1") +
                    " - JSON debugger</title><style type=\"text/css\">" +
                    "pre {color:#666} a {color:black}" +
                    "</style></head><body><pre>");
                var xml = new XMLHttpRequest;
                xml.open("GET", file, false);
                xml.send();
                var lines = xml.responseText.split("\n");
                var lineNoLength = String(lines.length).length;
                for (var i = 1; i <= lines.length; i++) {
                    var lineNo = String(i);
                    while (lineNo.length < lineNoLength) lineNo = " " + lineNo;
                    w.document.write(lineNo + " <a id=\"" + i + "\">" +
                        escapeHTML(lines[i - 1]) + "</a>\n");
                }
                w.document.write("</pre></body></html>");
                w.document.close();
            }
            if (w.currentLine) w.currentLine.style.background = "";
            var l = w.currentLine = w.document.getElementById(line);
            l.style.background = "#ccf";
            w.document.documentElement.scrollTop = l.offsetTop -
                w.document.documentElement.clientHeight / 2;
            w.focus();
        };
    }
    
    JSON.getHTMLStackTrace = function(hide) {
        var doc = this.debugDocument;
        var retval = [];
        for (var s = getStack(hide + 4); s; s = s.next) {
            var stack = s.stack.split("\n");
            var end = stack.length - (s.next ? s.next.end : 0);
            for (var i = s.start; i < end; i++) {
                var entry = stack[i];
                if (/function/.test(entry)) {
                    entry = entry.replace(/^(\w*\().*(\)@\S+:\d+)$/, "$1...$2");
                }
                var match = /^((?:\w*\(.*\))?@)((\S+):(\d+))$/.exec(entry);
                if (match) {
                    var link = newnode("a", 0, {
                        href: match[3] + "#" + match[4],
                        target: "oxJSONDebuggerSource"
                    }, [newtext(match[2], doc)], doc);
                    link.addEventListener("click",
                        showSource(match[3], match[4]), false);
                    retval.push(newnode("div", 0, 0,
                        [newtext(match[1], doc), link], doc));
                } else {
                    retval.push(newnode("div", 0, 0, [newtext(entry, doc)],
                                        doc));
                }
            }
            retval.push(newtext("---", doc));
            hide = 0;
        }
        return retval;
    };
    
    var oldSetTimeout = window.setTimeout;
    window.setTimeout = function() {
        if (typeof arguments[0] == "function") {
            arguments[0] = wrap(arguments[0], getStack(3, debugJSON ? 3 : 2));
            if (debugJSON) {
                arguments[0] = JSON.debug("setTimeout " + arguments[1],
                    JSON.getHTMLStackTrace(0), arguments[0]);
            }
        }
        if (debugJSON && JSON.debugCombination) arguments[1] = 0;
        return oldSetTimeout.apply(this, arguments);
    };
    
    var oldSetInterval = window.setInterval;
    window.setInterval = function() {
        if (typeof arguments[0] == "function") {
            arguments[0] = wrap(arguments[0], getStack(3, 2));
        }
        return oldSetInterval.apply(this, arguments);
    };
    
    var oldAjax = jQuery.ajax;
    jQuery.ajax = function(options) {
        var stack = getStack(0, 0);
        var opt = jQuery.extend({}, options);
        if (opt.success) opt.success = wrap(opt.success, stack);
        if (opt.error) opt.error = wrap(opt.error, stack);
        if (opt.complete) opt.complete = wrap(opt.complete, stack);
        if (debugJSON) {
            var cb = JSON.debug(opt.type + " " + opt.url,
                [JSON.serialize(opt.data)].concat(JSON.getHTMLStackTrace(2)),
                function() {
                    for (var i = 0; i < cb.callbacks.length; i++) {
                        var c = cb.callbacks[i];
                        c.callback.apply(c.that, c.args);
                    }
                    cb.callbacks = [];
                });
            cb.callbacks = [];
            function ok(callback, that, args) {
                cb.callbacks.push({ callback: callback,
                                    that: that, args: args });
                cb();
            }
            if (opt.success) {
                cb.success = opt.success;
                opt.success = function() { ok(cb.success, this, arguments); };
            }
            if (opt.error) {
                cb.error = opt.error;
                opt.error = function() { ok(cb.error, this, arguments); };
            }
            if (opt.complete) {
                cb.complete = opt.complete;
                opt.complete = function() { ok(cb.complete, this, arguments); };
            }
        }
        return oldAjax.call(this, opt);
    };
})();

function traceVariable(object, name) {
    var variable = object[name];
    delete object[name];
    object.__defineGetter__(name, function() { return variable; });
    object.__defineSetter__(name, function(value) {
        console.info("variable trace", name, value, getStackTrace());
        variable = value;
    });
}

function traceAccess(object, name, cloning) {
    var variable = object[name];
    delete object[name];
    if (cloning) {
        object.__defineGetter__(name, function() {
            console.info("variable read", name, clone(variable), getStackTrace());
            return variable;
        });
        object.__defineSetter__(name, function(value) {
            console.info("variable write", name, clone(value), getStackTrace());
            variable = value;
        });
    } else {
        object.__defineGetter__(name, function() {
            console.info("variable read", name, variable, getStackTrace());
            return variable;
        });
        object.__defineSetter__(name, function(value) {
            console.info("variable write", name, value, getStackTrace());
            variable = value;
        });
    }
}

  //////////////
 //  Login   //
//////////////

var bClickedLogin = false, loginFader;
/**
 * @private
 */

// complex join: load modules, contents, and configuration
var loginReady = new Join(function () {
    loginSuccess();
});
var folderCache;
var contentReady = loginReady.add(function () {
    loadMessage("Rebuild Tree..."/*i18n*/, "50");
    triggerEvent("RebuildTree");
    initAll2(login);
});
var resizeReady = loginReady.add();
var configReady = loginReady.add();
var folderReady = loginReady.add(function () {
    // help cache
    folderCache.setComplete(0);
});
var accountsReady = loginReady.add();
var userReady = loginReady.add();
var modulesReady = loginReady.add(function () {
	// get theme & identifier first (see below)
	var identifier;
	var join = new Join(function () {
	    // pause
	    ox.api.http.pause();
	    // load config
	    ox.api.config.load(configReady);
	    // load general folders
	    var separator = config.modules.mail.defaultseparator;
	    var tree = config.modules.folder.tree || 0;
	    folderCache = ox.api.cache["folder" + tree];
	    // 1: Private folders 2: Public folders, 3: Shared folders, 6: Global Address Book, 9: InfoStore
	    if (tree == 0) {
	        ox.api.folder.getMultiple({
	            list: ["1", "2", "3", "6", "9", "default0", "default0" + separator + "INBOX"],
	            success: folderReady
	        });
	    } else {
	        ox.api.folder.getMultiple({
	            list: ["1", "2", "3", "6", "9", "default0" + separator + "INBOX"],
	            success: folderReady
	        });
	    }
	    // load all mail accounts
	    ox.api.account.all(accountsReady);
	    // get my user data
	    internalCache.getUsers([null], userReady);
	    // resume
	    ox.api.http.resume();
	    // store cookie
	    autologinEnabled = jQuery("#loading_data_complete input:radio:checked").
	       val() === "true";
	    if (autologinEnabled) {
	        // can be delayed (5 sec)
	        setTimeout(storeSession, 5000);
	    }
	    // load contents
	    loadContents();
	});
	// theme ready
	var themeReady = join.add(function (data) {
		// set theme until "real" config is available
	    // fresh users do not have a gui config (!), so...
	    if (data && data.theme) {
	        config.gui = { theme: data.theme };
	    }
		// update theme information
	    if (configContainsKey("gui.theme") && configContainsKey(["modules","themes",configGetKey("gui.theme").path])) {
	    	oxThemeStyle = configGetKey("gui.theme");
	    } else {
	    	configSetKey("gui.theme", oxThemeStyle);
	    }
	    // update wtk
	    if (url.theme !== undefined) {
	        // use theme defined via url
	        ox.gui.themePath = urlify("themes/" + url.theme + "/");
	        oxThemeStyle.path = url.theme;
	    } else {
	        // use current theme
	        ox.gui.themePath = getFullImgSrc("");
	    }
	});

	ox.api.config.load("config/gui", themeReady);
	
});


var myjoin = new Join(contentReady);

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

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

// build proper URLs (e.g. injection of version number)
function urlify (path) {
    return (path.substr(0,1) !== "/" && path.substr(0,4) !== "http" && corewindow == window && oxProductInfo.build !== null) ? oxProductInfo.build + "/" + path : path;
}

// Escape string for sizzle
function jEsc (str) {
    return str.replace(/([#;&,.+*~':"!\^$\[\]()=>|\/])/g, "\\$1");
};

// preload javascript files
function preloadJSFiles () {
    var i, node, $l = jsarray.length, list = wholeelement["js"];
    var head = document.getElementsByTagName("head")[0];
    for (i = 0; i < $l; i++) {
        head.appendChild(
            newnode("script", null, {
                type : "text/javascript",
                text: list[jsarray[i]]
            })
        );
    }
}

function switchTheme (path, name) {
    // remember current theme
    var old = oxThemeStyle.path, $ = jQuery;
    var regex = new RegExp("themes/" + old);
    if (old !== path) {
        // preload new theme
        $.ajax({
            url: urlify("themes/" + path + "/css/concat.cssz"),
            success: function () {
                // add theme
                loadCSSFileForNewWindow("themes/" + path + "/css/concat.cssz");
                removeCSSFile("themes/" + old + "/css/concat.cssz");
                // update all images
                $("img").each(function () {
                    var src = $(this).attr("src");
                    if (src) {
                        $(this).attr("src", src.replace(regex, "themes/" + path));
                    }
                });
                // update all background images
                $("*").each(function () {
                    var bg = $(this).css("background");
                    if (bg) {
                        $(this).css("background", bg.replace(regex, "themes/" + path));
                    }
                    var bgImg = $(this).css("backgroundImage");
                    if (bgImg) {
                        $(this).css("backgroundImage", bgImg.replace(regex, "themes/" + path));
                    }
                });
                // update theme information
                oxThemeStyle = { path: path, name: name };
                ox.gui.themePath = getFullImgSrc("");
            }
        });
    }
}

//preload CSS files
function preloadCSSFiles () {
    var i, code, node;
    for(i = 0; i < cssarray.length; i++) {
        code = wholeelement["css"][cssarray[i]];
        node = newnode("style", null, { type : "text/css" });
        node.appendChild(document.createTextNode(code));
    }

}
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    :
                    if (init[i] instanceof Array) {
                        for(var i2 in preload[i]) {
                            init[i].push(preload[i][i2]);
                        }
                    } else {
                        for(var i2 in preload[i]) {
                            init[i][i2]=preload[i][i2];
                        }
                    }
                    break;
            }    
        }
        delete preload;
    }
}
function loadFile(page,cb,params,sizetreeid) {
    (new JSON).get(urlify(page), null, function(s) {
        cb(s, params, sizetreeid);
    }, null, true);
}

function loadCSSFileForNewWindow(url) {
    // create <link> element
    var node = newnode("link", null, { type : "text/css", rel: "stylesheet", href: urlify(url) });
    // add to head
    document.getElementsByTagName("head")[0].appendChild(node);
}

function removeCSSFile(url) {
    // remove stylesheet (<link> element)
    jQuery("link[rel=stylesheet][href=" + jEsc(urlify(url)) + "]").remove();
}

function removeCSSFilesForNewWindow(path) {
    // previously, the default theme was always loaded, so custom themes
    // had to remove those default css file. now,
    // this one actually adds the proper css file
    loadCSSFileForNewWindow("themes/" + path + "/css/concat.cssz");
}

// boot
ox.boot = (function () {
    
    // locals
    var fileCache = {};
    var jsNodes = {};
    wholeelement.js = {};
    var loadingJS = {};
    var bundleParts = {
        "mainpage": { alias: "core", place: undefined },
        "mail-core": { alias: "mail_core", place: "contentarea" },
        "calendar-core": { alias: "cal_core", place: "contentarea" },
        "contacts-core": { alias: "cont_core", place: "contentarea" },
        "infostore-core": { alias: "info_core", place: "contentarea" },
        "tasks-core": { alias: "tasks_core", place: "contentarea" },
        "messaging": { alias: "messaging", place: "contentarea" }
    };

    return {
        
        // cache HTML file
        cacheHTML: function (opt) {
            // defaults
            var options = jQuery.extend({
                file: "",
                success: jQuery.noop,
                error: jQuery.noop
            }, opt);
            // in cache?
            if (fileCache[options.file] === undefined) {
                // cache miss
                jQuery.ajax({
                    url: oxProductInfo.build + "/" + options.file,
                    dataType: "xml", // allows preprocessing
                    success: function (data) {
                        // cache data
                        var node = document.createElement("DIV");
                        node.innerHTML = jQuery(data).text() || "";
                        // get all nodes with an id at once
                        var nodes = jQuery("*[id]", node), i = 0, $l = nodes.length, n, id;
                        for (; i < $l; i++) {
                            n = nodes[i];
                            id = n.getAttribute("id") || ""; 
                            if(allnodes[id] !== undefined) {
                                alert("Internal Error: ID \"" + id + "\" used multiple times!");
                                allnodes[id] = "Duplicate ID";
                            } else {
                                // node type?
                                if (id.substr(id.length-11) == "scaffold-js") {
                                    // JS node
                                    jsNodes[id] = n;
                                } else {
                                    // DOM node
                                    allnodes[id] = n;
                                }
                            }
                        }
                        // mark as loaded
                        fileCache[options.file] = true;
                        // callback
                        options.success(data);
                    },
                    error: function (xhr) {
                        if (debug) console.error("Error", options.file, options.id, xhr);
                        options.error();
                    }
                });
            }
        },
    
        loadHTML: function (opt) {
            // defaults
            var options = jQuery.extend({
                file: "",
                id: "",
                success: jQuery.noop,
                error: jQuery.noop
            }, opt);
            // in cache?
            if (fileCache[options.file] !== undefined) {
                // callback
                options.success(allnodes[options.id]);
                return;
            }
        },
    
        loadJS: function (opt) {
            // defaults
            var options = jQuery.extend({
                file: "",
                id: "",
                success: jQuery.noop,
                error: jQuery.noop,
                params: "",
                tree: undefined
            }, opt);
            // in cache?
            if (fileCache[options.file] !== undefined) {
                // get nodes
                var node = jsNodes[options.id];
                // init
                htmljsarray.push(options.params);
                // callback
                options.success(jQuery(node).text(), options.params, options.tree);
            }
        },
    
        loadJSFile: function (page) {
            if (loadingJS[page] === undefined) {
                // debug?
                if (debug) {
                    fileloaded = jsload.add();
                    var d = newnode("script", null, { type: "text/javascript", src: urlify(page) });    
                    document.getElementsByTagName("head")[0].appendChild(d);
                    loadingJS[page] = true;
                } else {
                    // add to list
                    jsarray.push(page);
                    // callback
                    var callback = function (node, param) {
                        loadingJS[page] = true;
                        wholeelement.js[param] = node;
                    };
                    // load
                    loadFile(page, jsload.add(callback), page);
                }
            }
        },
        
        // bundles
        bundles: {},
        getBundles: function () {
            var b = ox.boot.bundles;
            // just mail
            b.MAIL = configGetKey("modules.mail.module") &&
                configGetKey("modules.contacts.module");
            // PIM
            b.PIM = b.MAIL && 
                configGetKey("modules.portal.module") &&
                configGetKey("modules.calendar.module") &&
                configGetKey("modules.tasks.module");
            // Full groupware
            b.FULL = b.PIM && 
                configGetKey("modules.infostore.module");
            // All available modules
            b.ALL = b.FULL && 
                configGetKey("modules.messaging.module");
            return b;
        },
        
        loadBundle: function (opt) {
            var options = jQuery.extend({
                parts: [],
                name: "",
                success: jQuery.noop,
                htmlStore: jQuery.noop
            }, opt);
            // load JS
            this.loadJSFile(options.name + ".jsz");
            // load layout files
            var layoutFile = options.name + ".xmlz";
            var self = this;
            this.cacheHTML({ 
                file: layoutFile, 
                success: htmlload.add(function () {
                    // progress
                    loadMessage("Load Contents..." /*i18n*/, "30");
                    // load JS
                    var handler = function () {
                        return htmljsload.add(options.htmlStore);
                    };
                    // loop over parts
                    var p = options.parts, i = 0, $l = p.length, id, part;
                    for (; i < $l; i++) {
                        id = p[i];
                        part = bundleParts[id];
                        self.loadJS({ file: layoutFile, id: id + "-scaffold-js", params: part.alias, tree: part.place, success: handler() });
                        self.loadHTML({ file: layoutFile, id: id + "-scaffold", success: htmlload.add(jQuery.noop) });
                    }
                    // callback
                    options.success();
                })
            });
        }
    };
})();

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) {
        //setTimeout(function() {
            loadFile(file,emptyFunction,file);
        //}, 2000);
    }
    
    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: urlify(page) });    
        document.getElementsByTagName("head")[0].appendChild(d);
    }    
    
    function debugCSSLoad(url) {
        // exists?
        var links = jQuery("link[rel=stylesheet][href=" + jEsc(urlify(url)) + "]");
        if (links.length === 0) {
            // add CSS file
            var node = newnode("link", null, { type : "text/css", rel: "stylesheet", href: urlify(url) });    
            document.getElementsByTagName("head")[0].appendChild(node);
        }
    }
    
    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());

    register("OX_Configuration_Loaded", function() {

        // add basic css
        loadCSSFile("ox.css");
        
        // only load compressed themes
        loadCSSFile("themes/" + oxThemeStyle.path + "/css/concat.cssz");
//        if (oxThemeStyle.path == "default") {
//            // load concatenated css
//            loadCSSFile("themes/default/css/concat.cssz");
//        } else {
//            loadCSSFile("themes/"+oxThemeStyle.path+"/css/calendar.css");
//            loadCSSFile("themes/"+oxThemeStyle.path+"/css/global.css");
//            loadCSSFile("themes/"+oxThemeStyle.path+"/css/popup.css");
//            loadCSSFile("themes/"+oxThemeStyle.path+"/css/bgimages.css");
//        }
        // remove some of the default styleheets to prevent duplicate entries
        if (oxThemeStyle.path != "default") {
            removeCSSFile("themes/default/css/global.css");
            removeCSSFile("themes/default/css/bgimages.css");
        }
    });
    
    /* Which bundle? */
    var bundle = ox.boot.getBundles();
    
    var finalize = function () {
        if (!bundle.ALL) {
            /* Messaging */
            if (configGetKey("modules.messaging.module")) {
                loadHTMLFile("messaging.html");
                loadHTMLJSFile("messaging.js", "messaging", "contentarea");
                loadJSFile("concat_messaging.js");
            }
        }
        // go
        for (var i=0;i<tmparray.length;i++) {
            tmparray[i]();
        }
        // progress
        loadMessage("Load Contents..." /*i18n*/, "40");
    };
    
    // refs
    var loadJS = ox.boot.loadJS;
    var loadJSFile = ox.boot.loadJSFile;
    var loadHTML = ox.boot.loadHTML;
    var cacheHTML = ox.boot.cacheHTML;
    
    if (bundle.FULL || bundle.ALL) {
        // load bundle
        var name = "bundle_" + (bundle.ALL ? "all" : "full");
        var parts = ["mainpage", "mail-core", "calendar-core", "contacts-core", "infostore-core", "tasks-core"];
        if (bundle.ALL) {
            parts.push("messaging");
        }
        // load bundle
        ox.boot.loadBundle({
            name: name,
            parts: parts,
            htmlStore: storeHTMLJSFile,
            success: finalize
        });
    }
    else if (bundle.PIM) {
        // load bundle
        var name = "bundle_pim";
        var parts = ["mainpage", "mail-core", "calendar-core", "contacts-core", "tasks-core"];
        // load bundle
        ox.boot.loadBundle({
            name: name,
            parts: parts,
            htmlStore: storeHTMLJSFile,
            success: finalize
        });
    } else if (bundle.MAIL) {
        // load bundle
        var name = "bundle_mail";
        var parts = ["mainpage", "mail-core", "contacts-core"];
        // load bundle
        ox.boot.loadBundle({
            name: name,
            parts: parts,
            htmlStore: storeHTMLJSFile,
            success: finalize
        });
    }
    else {
        
        /*Load always needed contents */
        loadHTMLFile("mainpage.html");
        loadHTMLJSFile("mainpage.js","core");
        loadJSFile("concat_core.jsz");
        
        /* Messaging */
        if (configGetKey("modules.messaging.module")) {
            loadHTMLFile("messaging.html");
            loadHTMLJSFile("messaging.js", "messaging", "contentarea");
            loadJSFile("concat_messaging.js");
        }
        
        /* Load portal contents */
        if(configGetKey("modules.portal.module")) {
            loadJSFile("concat_portal.js");                
        }
    
        /*Load mail contents*/
        if(configGetKey("modules.mail.module")) {
            loadHTMLFile("mail_core.html");
            loadHTMLJSFile("mail_core.js","mail_core","contentarea");
            loadJSFile("concat_mail.js");
            if(configGetKey("fastgui.preload.mail")) {
                loadFileForCache("concat_mailnew.jsz");
                loadFileForCache("concat_maildetail.jsz");
                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("concat_minicalendar.js");
        // ===
        //loadHTMLFile("calendarmini.html");
        //loadHTMLJSFile("calendarmini.js","cal_mini","below_folders");         
    
        /*Load calendar contents*/
        if(configGetKey("modules.calendar.module")) {
            loadHTMLFile("calendar_core.html");
            loadHTMLJSFile("calendar_core.js","cal_core","contentarea");
            loadJSFile("concat_calendar.jsz");
            if(configGetKey("fastgui.preload.calendar")) {
                loadFileForCache("concat_calendarnew.jsz");
                loadFileForCache("newappointment.css");
                loadFileForCache("newAppointment.html");
            }
        }
        if(configGetKey("modules.contacts.module")) {
            loadHTMLFile("contacts_core.html");
            loadHTMLJSFile("contacts_core.js","cont_core","contentarea");
            loadJSFile("concat_contacts.js");
            if(configGetKey("fastgui.preload.contacts")) {
                loadFileForCache("concat_contactsnew.jsz");
                loadFileForCache("newcontact.css");
                loadFileForCache("newContact.html");
            }
            if(configGetKey("fastgui.preload.distributionlist")) {
                loadFileForCache("concat_distributionnew.jsz");
                loadFileForCache("newDistributionList.html");
                loadFileForCache("newcontact.css");
            }
    
        }
        if(configGetKey("modules.infostore.module")) {
            loadHTMLFile("infostore_core.html");
            loadHTMLJSFile("infostore_core.js","info_core","contentarea");
            loadJSFile("concat_infostore.js");    
            if(configGetKey("fastgui.preload.infostore")) {
                loadFileForCache("concat_infostorenew.jsz");
                loadFileForCache("newInfoItemMain.html");
                loadFileForCache("newtask.css");
            }
    
        }
    
        if (configGetKey("modules.tasks.module")) {
            loadHTMLFile("tasks_core.html");
            loadHTMLJSFile("tasks_core.js","tasks_core","contentarea");
            loadJSFile("tasks.js");
            if (configGetKey("fastgui.preload.tasks")) {
                loadFileForCache("concat_tasksnew.js");
                loadFileForCache("newTask.html");
                loadFileForCache("newtask.css");
            }
        }
        
        /* Portal Classes */
        loadJSFile("concat_sidepanel.js");
        loadJSFile("concat_config.jsz"); // <==
        
        for (var i=0;i<tmparray.length;i++) {
            tmparray[i]();
        }
        loadFileForCache("concat_all.jsz");
    }
}

function loadModules (result) {

    session = result.session;

    // make sure to have an object
    config = config || {};
    
    config.fastgui = {
        preload: {
            mail: true,
            calendar: true,
            tasks: true,
            contacts: true,
            distributionlist: true,
            infostore: true
        }
    };
    
    // set modules
    config.modules = result.modules;
    modulesReady(); 
}

function uuid() {
    function hex(len, x) {
        if (x === undefined) x = Math.random();
        var s = new Array(len);
        for (var i = 0; i < len; i++) {
            x *= 16;
            var digit = x & 15;
            s[i] = digit + (digit < 10 ? 48 : 87); // '0' and 'a' - 10
        }
        return String.fromCharCode.apply(String, s);
    }
    return [hex(8), "-", hex(4), "-4", hex(3), "-",
            hex(4, 0.5 + Math.random() / 4), "-", hex(12)].join("");
}

function storeSession () {
    // is supported?
    if (supportForAutoLogin === true) {
        ox.api.http.GET({
            module: "login",
            params: {
                action: "store"
            }
        });
    }
}


function login() {
    /*avoid double login*/
    if (bClickedLogin) {
        return;
    }
    bClickedLogin=true;
    if (loginFader) { clearTimeout(loginFader); }
    jQuery("#loginStatus").stop().hide();
    
    // disable input button
    jQuery("#loginPrompt input").attr("disabled", "disabled");
    
    /*avoid double login end*/
    
    var commitLogin = function () {
        var form = document.getElementById("login");
        
        new JSON().post(AjaxRoot +"/login?action=login&modules=true&client=" +
            encodeURIComponent(oxProductInfo.id || "") + "&version=" +
            encodeURIComponent(oxProductInfo.pversion || "") + "&authId=" + uuid(),
            { name: form.username.value, password: form.password.value },
            null,
            function(result) {
                // hide form
                loadMessage("Login" /*i18n*/ + "...", "10");
                jQuery("#loading_data_complete").hide();
                jQuery("#loading_data").show();
                jQuery("#loginPrompt input").removeAttr("disabled");
                loadModules(result);
            },
            function(result, status) {
                jQuery("#loginPrompt input").removeAttr("disabled");
                // shake effect (on loginScreen; IE cannot shake loginPrompt properly)
                jQuery("#loginScreen").effect("shake", { times: 2, distance: 10 }, 70,
                        function () {
                            var msg = formatError(result);
                            
                            triggerEvent("OX_Login_Failed");
                            jQuery("#loading_data_complete").show();
                            jQuery("#loading_data").hide();
                            if (!status && result.code == "LGI-0006") {
                                msg = _("Login failed. Please check your user name and password and try again.");
                            } else {
                                //#. HTTP Errors from the server
                                //#. %1$s is the numeric HTTP status code
                                //#. %2$s is the corresponding HTTP status text
                                //#, c-format
                                msg = format(_("Error: %1$s - %2$s"), status, result);
                            }
                            jQuery("#loginStatus td").empty();
                            jQuery("#loginStatus td").text(msg.toString());
                            
                            jQuery("#loginStatus").fadeIn(500, function() {
                                if (loginFader) { 
                                    clearTimeout(loginFader);
                                }
                                loginFader = setTimeout(function() {
                                    jQuery("#loginStatus").hide();
                                }, 10000); // hide after 10 sec
                            });
                            bClickedLogin = false;
                            jQuery("#username").focus();
                        }
                );
                return true;
            },
            null
        );
    };
    
    setTimeout(commitLogin, 10);
    
    return false;
}

function loginSuccess () {
    
    // change cached img path according to theme location
    changeImgPath(oxThemeStyle.path);
    register("Logout", logout);
    
    if (url.embedded && new Boolean(url.embedded) == true) {
        embedded = true;
    }
    
    $("everything").style.visibility = "hidden";
    appendNode("everything");
    
    var view = null;
    // mail is the fallback module
    var module = "mail";
    
    // checking the default folder of mail
    // if it's null or empty somethings broken in the 
    // mail backend and we can't connect to the mail server
    if (!ox.api.config.get("mail.folder.inbox") && ox.api.ui.getModule("mail")) {
        // to prevent further errors we disable mail
        ox.api.ui.getModule("mail").disabled = true;
        ox.api.config.set("modules.mail.module", false);
        newAlert(_("The E-Mail module is not available"), 
            _("Unable to establish a connection to the E-Mail server. Possible reasons: the mail server is (temporarily) down or there are network connection problems. To prevent further errors the module has been disabled. Please contact your administrator."), /**i18n**/
            null);
    }
    
    // iterating through all available modules after login and 
    // set the first module we found as activated. we also try
    // to select the standard folder of this module
    var modules = ox.api.ui.listModules();
    for (var i=0; i < modules.length; i++) {
        var myModule = modules[i];
        if (myModule.disabled) {
            continue;
        }
        module = myModule.name;
        break;
    }
    
    loadingComplete(true);
    
    // set language now
    setLanguage(configGetKey("language"), function () {
        
        // boot widget toolkit
        ox.gui.morph();
        
        // trigger
        triggerEvent('OX_Configuration_Loaded');
        triggerEvent('OX_Configuration_Loaded_Complete');
        
        // load plugins
        loadPlugins(function () {

            triggerEvent("OX_Plugins_Loaded");
            
            var originalTitle = document.title; // Document title
            register("LanguageChanged", function () {
                document.title = _(originalTitle) + "";
            });
            
            // set landing page as active view
            if (configGetKey("gui.global.landing_page") != null) {
                // only set module if not disabled!
                if (ox.api.ui.getModule(configGetKey("gui.global.landing_page").module).disabled === false) {
                    module = configGetKey("gui.global.landing_page").module;
                }
            } else {
                // set default (any user has mail)
                configSetKey("gui.global.landing_page.module", module);
            }
            
            // get default module view
            view = configGetKey("gui." + module + ".view") || module;
            
            // remember
            ox.UIController.initialModule = module;
            ox.UIController.initialView = view;
            
            // validate wtk
            ox.desktop.validate();
            
            // update progress bar
            loadMessage("", "100");
            
            triggerSingleton("OX_Login");
            
            // init toolbar resize
            ox.ToolBarController.initialResize();
            
            setTimeout(function () {
                
            	// continuation
            	var cont = function () {
                    // initialize branding
                    jQuery("#branding").append(
                        jQuery("<img/>", { id: "branding-logo", src: getFullImgSrc("img/dummy.gif"), alt: "" })
                    );
                    triggerEvent('OX_Apply_Branding');
                    // remove login screen from DOM because we don't need it anymore
                    $("body").removeChild($("loginScreen"));
                    // show UI
                    $("everything").style.visibility = "visible";
                    // remove login css
                    removeCSSFile("themes/login/default.css");
                    // trigger ready
                    triggerSingleton("Ready");
            	};
                
                // use expert tree?
                if (configGetKey("gui.global.expert_tree") === 1) {
                    ox.UIController.firstSetExpertTree = true;
                    ox.UIController.setExpertTree(true);
                }
                
            	// direct link?
            	var m = ox.util.getHash("m"), f = ox.util.getHash("f"), id = ox.util.getHash("i");
            	if (m !== undefined && f !== undefined) {
            		// get folder path (for both trees)
            		var join = new Join(function () {
                		// use direct link
                        triggerEvent("OX_Direct_Linking", m, {
                        	module: m, folder: f, id: id !== undefined ? id : null, folder_id: f
                        });
                        cont();
            		});
            		var tree0 = join.add(), tree1 = join.add();
            		ox.api.folder.getParents({ folder: f, tree: 0, success: tree0 });
            		ox.api.folder.getParents({ folder: f, tree: 1, success: tree1 });
            		
            	} else {
            	    
                    // set module via UIController
        			ox.UIController.setModule({
        			    module: module,
        			    folder: "default",
        			    success: function () {
            			    // view has been set?
                            if (view) {
                                triggerEvent("OX_Switch_View", view);
                            }
                            triggerEvent("OX_Selected_Attachments", 0);
                            cont();
        			    }
        			});
            	}
            }, 10);
        });
    });
}

/**
 * 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 urlify("themes/" + (corewindow.oxThemeStyle.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) {
            triggerEvent("OX_New_Error", 2,
                //#. HTTP Errors from the server
                //#. %1$s is the numeric HTTP status code
                //#. %2$s is the corresponding HTTP status text
                //#, c-format
                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.") 
                            + " (" + format(_("Error-ID: %1$s"), [ result.error_id ]) + ")", 
                       function() {
                            triggerEvent("OX_Session_Expired");
                            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.") 
                            + " (" + format(_("Error-ID: %1$s"), [ result.error_id ]) + ")");
            }
        } else {
            newServerError(result,4,window.opener);
        }
    };
}
  ///////////////
 //  Logout   //
///////////////

/**
 * @private
 */
function logout() {
    // use new api
    configuration_askforSave(ox.api.ui.logout);
}

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

register("OX_Apply_Branding", function() {
    var brandingHeight = jQuery("#branding").outerHeight(); // height of branding div
    if (corewindow.embedded === true) {
        // embedded mode activated?
        corewindow.embedd = true;
        jQuery("#header").hide();
        jQuery("#header-decorator").hide();
        jQuery("#notheader").css({ top: "0px" });
        // no branding, top position is 0
        if (jQuery("#branding").css("display") == "none") {
            brandingHeight = 0;
        }
        jQuery("#notheader").css({ top: brandingHeight + "px" });
    } else if (jQuery("#branding").css("display") != "none") {
         // re-set top positions when branding is enabled
        corewindow.branding = true;
        var topPanelHeight = jQuery("#header").outerHeight();
        jQuery("#header").css({ top: brandingHeight + "px" });
        jQuery("#header-decorator").css({ top: (brandingHeight + topPanelHeight)  + "px" });
        jQuery("#notheader").css({ top: (brandingHeight + topPanelHeight) + "px" });
    }
});

  //////////////////////
 //   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 optional 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 popupBlockerTitle = "Error"; /*i18n*/
    var popupBlockerText = "The window could not be opened. Most likely it has been blocked by a pop-up or advertisement blocker. Please check your browser settings and make sure pop-ups are allowed for this domain."; /*i18n*/
    var windows = {};
    setInterval(function() {
        for (var i in windows) {
            if (windows[i] && 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;
        // absolute or relative path?
        uri = urlify(uri);
        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);
            }
            if (!win) {
                delete windows[sid];
                newAlert(_(popupBlockerTitle), _(popupBlockerText));
            }    
        } else {
            win = nwin.open(uri, "OX" + counter++, options);
            if (!win) {
                newAlert(_(popupBlockerTitle), _(popupBlockerText));
            }
        }
        return win;
    };
})();

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

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

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

/**
 * A map from module names to callback functions. Each callback function
 * is called with a folder object as parameter and returns an object with
 * the fields "open" and "closed", which contain the paths to 16px*16px icons
 * for the open and closed folder, respectively.
 */
var customFolderIcons = {};

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

// ****************************************************************
// Old functions only required as dummy!
// Marked as DEPRECATED. Will be removed later!
// Please check the API documentation for new calls.
// ****************************************************************
function registerModule(name, text, priority) {
	var module = ox.api.ui.getModule(name);
	if (module !== null) {
		module.disabled = false;
	} else {
		ox.api.ui.registerModule({ name: name, text: text, priority: priority, disabled: false });
	}
}
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) {
    
    // get previous view
    var previous = (currentpath2 || []).join("/");
    
    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;
    
    // trigger event?
    if (previous !== currentpath.join("/")) {
        triggerEvent("OX_View_Changed", {
            module: ox.UIController.getModule(),
            path: currentpath
        });
    }
    
    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   //
///////////////////////////////////////

/**
 * @function Sets the mouse cursor for the entire aplication. Must be followed
 * by a call to {@link #removeMouseCursor}.
 * @param {String} cursor The CSS cursor value to set.
 * @type GlobalCursor
 * @return An object which must be passed to {@link #removeMouseCursor} to revert
 * the cursor change.
 */
var setMouseCursor;

/**
 * @function Removes a mouse cursor which was previously set with
 * {@link #setMouseCursor}.
 * @param {GlobalCursor} item the return value of a previous call to
 * {@link #setMouseCursor}.
 */
var removeMouseCursor;

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

    var setCursorCSS = emptyFunction;
    register("Loaded", function() {
        var stylesheet = document.styleSheets[0];
        var rules = stylesheet.cssRules || stylesheet.rules;
        var insertRule = stylesheet.insertRule ?
            function(cursor, index) {
                stylesheet.insertRule("* { cursor: " + cursor
                    + " !important; }", cursorIndex);
            } : function(cursor, index) {
                stylesheet.addRule("*", "cursor: " + cursor + " !important");
            };
        var deleteRule = stylesheet.deleteRule  
            ? function(index) { stylesheet.deleteRule(index); }
            : function(index) { stylesheet.removeRule(index); };

        setCursorCSS = function(cursor) {
            if (cursorIndex >= 0) {
                deleteRule(cursorIndex);
                cursorIndex = -1;
            }
            if (cursor) {
                cursorIndex = rules.length;
                insertRule(cursor, cursorIndex);
            }
        };
    });
    
    setMouseCursor = function(cursor) {
        var item = {cursor: cursor};
        cursors.addLast(item);
        setCursorCSS(cursor);
        return item;
    };

    removeMouseCursor = function(item) {
        if (item === cursors.last) {
            cursors.remove(item);
            setCursorCSS(cursors.last.cursor);
        } else
            cursors.remove(item);
    };
})();

  /////////////////////////////////
 //   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 {Event} e An optional event object which specifies which modifier
     * keys were held down at the time of the click.
     */
    click: function(index, e) {
        if (!this.storage) console.error("Selection.click without storage");
        if (index < 0 || index >= this.storage.ids.length) {
            this.reset();
            return;
        }
        if (!e || !(Mac ? e.metaKey : e.ctrlKey)) this.reset();
        if (e && e.shiftKey)
            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;
        }
    };
})();

  //////////////////////
 //   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)) {
                if ($(panelElement).nodeName == "TD" || $(panelElement).className.match(/^tabbing_tab/g)) {                    
                    $(panelElement).className = "tabbing_tab_inactive";
                } else {
                    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)) {
                if ($(panelElement).nodeName == "TD" || $(panelElement).className.match(/^tabbing_tab/g)) {
                    if (tabElementNr > 0) $(panelsList[tabElementNr-1]).className = "tabbing_tab_inactive_left";
                    $(panelElement).className = "tabbing_tab_active";
                } else {
                    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;
                }
            }
        }
    }
}

  //////////////////////////
 //   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].recurrence_position = 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();

/**
 * 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) {    
    if (node && node.focus) {
        setTimeout(function() { 
            try {
                node.focus();
                focusedElement = node;
            } catch (e) { } 
        }, 0);
    }
}

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

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

function newtext(text, doc) {
    if (!doc) doc = document;
    return doc.createTextNode(text || "");
}

function newradio(style, props, doc) {
    style = style || {};
    props = props || {};
    if (IE) {
        delete props.type;
        return newnode('<input type="radio" name="' + (props.name || "") + '">', style, props, 0, doc);
    } else {
        props.type = "radio";
        return newnode("input", style, props, 0, doc);
    }
}

function newfrag(doc) {
    if (!doc) { doc = document; }
    return doc.createDocumentFragment();
}

function newtab(style, props, children, doc) {
    props = props || {};
    props.border = props.cellSpacing = props.cellPadding = "0";
    return newnode("table", style, props, children, doc);
}

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

// cannot be inside initAll since "login" (the function) is out of scope
function initLoginClick () {
    // explicit login click/submit (speed up)
    jQuery("#inner-login-button").bind("click", function (e) {
        login();
        return false;
    });
    jQuery("#login").bind("submit", function (e) {
        login();
        return false;
    });
    
    // init helptext-button for "security"-selection
    jQuery("#publicMachineDiv, #privateMachineDiv").hide();
    
    jQuery("#show_loginsecurity").toggle(function(){
        
        var oldBoxHeight = jQuery("#loginPrompt").height();
    
        jQuery("#show_loginsecurity").text(_("Hide description") + "");
        jQuery("#publicMachineDiv, #privateMachineDiv").slideDown("slow",function(){
            var deltaBoxHeight = jQuery("#loginPrompt").height()- oldBoxHeight;
            var pxVal = -120 - deltaBoxHeight/2 + "px";
            jQuery("#loginPrompt").animate({"margin-top": pxVal},"slow");
        });
            
    },
    function(){
        jQuery("#show_loginsecurity").text(_("Description") + "");
        jQuery("#publicMachineDiv, #privateMachineDiv").slideUp("slow",function(){
            jQuery("#loginPrompt").animate({"margin-top": "-120px"},"slow");
        });
        
    });
}

// auto login support
// the only way to determine missing support for auto login
// is getting a specific error code from the login request
// (SVL-0015; see below)
// determining this is tricky therefore the check is for the
// invalid cookies error code that is only given if autologin
// is enabled and but autologin failed.
var supportForAutoLogin = false;

function initAll(login) {
    
    // get body tag
    body = document.getElementsByTagName("body")[0];
    
    if (login) {
        
        // init login click
        initLoginClick();
        
        // maintain hasFocus of current window
        corewindow.hasFocus = true;
        if (IE) {
            document.onfocusin = function(e) { corewindow.hasFocus = true; };
            document.onfocusout = function(e) { corewindow.hasFocus = false; };
        } else {
            addDOMEvent(window, "focus", function() { corewindow.hasFocus = true; });
            addDOMEvent(window, "blur", function() { corewindow.hasFocus = false; });
        }
        
        loadMessage = function(message, status) {
            var str = message ? _(message) + " " : "";
            jQuery("#loadmessage_text").text(str + status + "%");
        };

        if (url.session) { // SSO
            window.location.hash = String(window.location.hash).replace(/&?\bsession=[^&]+/, "");
            (new JSON()).get(AjaxRoot + "/config/modules?session=" + url.session, null, function (result) {                
                loadModules({ session: url.session, modules: result.data });
                if (url.store && url.store == "true") {
                    window.location.hash = String(window.location.hash).replace(
                        /&?\bstore=[^&]+/, "");
                    // set support flag
                    supportForAutoLogin = true;
                    // store session
                    storeSession();
                }
            });
        } else {
            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;
                    }
                }
            }
            
            // set language
            setLanguage(lang);
            
            // try auto login
            (new JSON()).get(AjaxRoot + "/login?action=autologin&modules=true&client=" + encodeURIComponent(oxProductInfo.id || "") + "&version=" + encodeURIComponent(oxProductInfo.pversion || ""), null,
                // success
                function (result) {
                    // implies support for auto login
                    supportForAutoLogin = true;
                    // continue
                    loadModules(result);
                },
                // error
                function (result, status) {
                    if (!status && (result.code == "SVL-0003" || "SES-0205")) {
                        // auto login is turned on and autologin failed
                        supportForAutoLogin = true;
                    } else {
                        // other errors imply maybe a not working support for autologin
                        supportForAutoLogin = false;
                        // hide auto login feature
                        jQuery("#rememberme").remove();
                    }
                    // enable login form
                    if (document.getElementById("loading_data")) document.getElementById("loading_data").style.display="none";
                    if (document.getElementById("loading_data_complete")) document.getElementById("loading_data_complete").style.display=""; //instead of "block" remove this style by setting it to empty string
                    if (document.getElementById("login")) document.getElementById("login").username.focus();
                    triggerEvent("LoginPageLoaded");
                    return true;
                }
            );
        }
        
        (new JSON).get(urlify("plugins/static.conf"), null, function(reply) {
            var modules = reply.split("\n");
            for (var i = 0; i < modules.length; i++) {
                if (modules[i]) loadModule(modules[i]);
            }
        }, function() { return true; }, true);
        
    } else {
        initAll2(login);
    }
}
function initAll2(login) {
    
    // Callback for plugins in the parent window.
    // Deprecated, use the success callback in ox.api.window.open().
    if (!login && "initCallback" in window) initCallback();
    
    body = document.getElementsByTagName("body")[0];
    
    function getComputedStyle(node) {
        return window.getComputedStyle ? window.getComputedStyle(node, "")
                                       : node.currentStyle;
    }

    loadMessage("Rebuild Tree...", /*i18n*/
    "60");
    
    
    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*/
                "70");

        // 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*/
                "80");

        // 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]) 
                        eval("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) { resizeReady(); } else {
                if ($("loading_data")) $("loading_data").style.display="none";
                if ($("loading_data_complete")) $("loading_data_complete").style.display="block";
            }
            triggerEvent("OX_menu_Change_Height", true);
        }
        
        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", backgroundColor: "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=\"%1\".", 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.style.zIndex = 20;
                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], true));
    }

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

    // 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) {
//            triggerEvent("OX_Direct_Linking", module[1],
//                         { module: module[1], folder: folder[1], id: id && id.length ? id[1] : null, 
//                           folder_id: folder[1]});
//        }
//    });
    
    // Automatic logout handling
    var loggingOut = false;
    if (login) JSON.errorHandler = function(result, status) {
        if (status) {
            triggerEvent("OX_New_Error", 2,
                //#. HTTP Errors from the server
                //#. %1$s is the numeric HTTP status code
                //#. %2$s is the corresponding HTTP status text
                //#, c-format
                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() {
                            triggerEvent("OX_Session_Expired");
                            window.onbeforeunload = null;
                            setTimeout( function() { window.location.replace(sessionExpired_location.format()); },0);       
                       });
            }
        } else
            newServerError(result,4);
    };
    
//    // TODO: move to separate event handlers.
//    addDOMEvent(body, "click", function(e) {
//        triggerEvent('OX_GLOBAL_CLICK',e);
//    });
}

function unloadMessageMainLogin(e) { }

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
            //#, c-format
            return format(pgettext("bytes", "%1$s %2$s"),
                          formatNumbers(Math.round(bytes * 100) / 100),
                          _(units[i]));
        bytes /= 1024;
    }
}

function clone(element, sourceWindow) {
    if (typeof(element) != "object") return element;
    var array = sourceWindow ? sourceWindow.Array : Array;
    return subclone(element);

    function subclone(element) {
        if(!element) return null;
        var retval = element.constructor == array ? [] : {};
        //var retval = Object.prototype.toString.call(element) == "[object Array]"
        //             ? [] : {};
        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 isIDNEmail(mail) {
    var regexmail = /^([+a-zA-Z0-9_-]|\\[!#$%&'*+=?^_{|}~])+(\.([+a-zA-Z0-9_-]|\\[!#$%&'*+=?^_{|}~])+)*@.(.{0,61}.)?(\..(.{0,61}.)?)*\.[a-zA-Z]{2,6}$/;
    return regexmail.test(mail);
}

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 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];
    obj["session"] = session;
    obj["loginname"] = $("username").value || "";
    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.pversion;
            $("about_vendor_address").innerHTML = oxProductInfo.vendor_address.replace(/\n/gi,"<br/>");
            $("about_server_version").firstChild.data = configGetKey("serverVersion");
            AboutPopup.openWindow();
        });

register("Loaded", function() {
    // Context menu for the help and about dialog in the upper panel
    var helpImage = $("help_image");
    if (helpImage) {
        var menu = globalContextMenus.help = new PulldownMenu(helpImage);
        menu.addItem(new MenuItem(_("Help"),
            function() { triggerEvent("OX_Show_Help", help_location); }));
        menu.addItem(new MenuItem(_("Error messages"),
            function() { showErrorinPopup(); }));
        menu.addItem(new MenuItem(_("About"),
            function() { triggerEvent("OX_Show_About"); }));
    }
    var menu = globalContextMenus.mailAddress = new ContextMenu();
    menu.addItem((new MenuItem(_("Add to address book"), function() {
        createNewContactfromMail(this.getContext());
    })).setIcon("img/folder/newfolder.gif", "img/folder/newfolder_d.gif"));
});

/**
 * Global object for the help menu in the top right corner of the Groupware.
 */
var HelpMenu = {
    /**
     * Adds a plain text entry to the help menu.
     * @param {String} text The human-readable text which is displayed
     * in the menu.
     * @param {Function} callback A callback function which is called when
     * the user clicks on the added entry.
     */
    addText: function(text, callback) {
        globalContextMenus.help.addItem(new MenuItem(text, callback));
    }
};

function setContentHeader(fields) {
    function setHeaderContent(data){
        if(data.created_by) {
            internalCache.getUsers([data.created_by], function(cbObj){
                if(data.type == 1 || data.type == 3){
                    for(var i=0;i<fields.length;i++) {
                        //#. Title of the content area when a shared folder is displayed.
                        //#. %1$s is the folder name.
                        //#. %2$s is the user name of the folder's owner.
                        //#, c-format
                        $(fields[i]).firstChild.data = format(_("%1$s of %2$s"),data.title,cbObj[data.created_by].display_name);
                    }
                } else if(data.type == 2 ){
                    for(var i=0;i<fields.length;i++) {
                        //#. Title of the content area when a public folder is displayed.
                        //#. %s is the folder name.
                        //#, c-format
                        $(fields[i]).firstChild.data = format(_("Public folder %s"),data.title);
                    } 
                } else {
                    for(var i=0;i<fields.length;i++) {
                        //#. Title of the content area when a public folder is displayed.
                        //#. %s is the folder name.
                        //#, c-format
                        $(fields[i]).firstChild.data = format(_("Public folder %s"),data.title);
                    } 
                }
            });
        }
    }
    ox.api.folder.get({
        folder: activefolder,
        success: setHeaderContent
    });
}

/* 
 * 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/unquote
        if (pAddr !== null) {
            if (personal) {
                // remove quotes
                pAddr = pAddr.replace(/(^"|^'|'$|"$)/g, "");
            } else {
                // quote mail address if not already quoted
                if ((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", 0, { className: "linkInView", title: personal ? mAddr : "" }, 
            [ 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));
        
        var o = {};
        o.email1 = mAddr;
        o.last_name = pAddr || "";
        o.last_name = o.last_name.replace(/"|'/g,""); /*remove quotes*/
        /* good guess check to get first and last name (cudos to vp for the regexp :))*/
        var m = o.last_name.match(/(([^,]*),\s*(.+))|((\S*)\s+(.+))/);
        if (m) {
          o.last_name = m[2] || m[6];
          o.first_name =  m[3] || m[5];
        }
        addDOMEvent(oDOMDiv, "contextmenu", (function(o) {
            return function(e) {
                globalContextMenus.mailAddress.display(e.clientX, e.clientY, o);
            };
        })(o));
        try {
            if ("registerSource" in window && registerSource) {
                registerSource(oDOMDiv, "mailaddress", (function(o) {
                    return function() {
                        return o;
                    };
                })(o), 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, "datetime");
    var last_modified = formatDate(oObj.last_modified, "datetime");
    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(addTranslated(
                //#. General object information at the bottom of the content area.
                //#. %1$s is the creation date.
                //#. %2$s is the name of the creator.
                //#. %3$s is the last modification date.
                //#. %4$s is the name of the last editor.
                //#, c-format
                format(_("Created on %1$s by %2$s, last changed on %3$s by %4$s"),
                       creation_date, created_by, last_modified, modified_by)
            ));
        });    
    });    
}
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.scrollLeft || 0);
         oParent=oParent.offsetParent;
     }
     return xPos;
}

function getAbsolutePositionTop(node) {
    var yPos=node.offsetTop;
    var oParent=node.offsetParent;
    while(oParent != null) {
        yPos +=oParent.offsetTop - (oParent.scrollTop || 0);
        oParent=oParent.offsetParent;
    }
    return yPos;
}

/**
 * Returns the absolute position of a DOM node in pixels.
 * @param {DOMNode} node The DOM node for which to determine the position.
 * @type Object
 * @return An object with the members <code>x</code> and <code>y</code>,
 * representing the horizontal and vertical position in pixels, respectively. 
 */
function getAbsolutePosition(node) {
    for (var x = 0, y = 0; node; node = node.offsetParent) {
        x += node.offsetLeft;
        y += node.offsetTop;
    }
    return { x: x, y: y };
}

/**
 * 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) {
    (new JSON).get(file, null, emptyFunction, null, true);
}
register("LoginPageLoaded",function() {
    setTimeout(function() {
        // load tinymce's image map
        preloadMailNewImages();
    },10);
});

function preloadMailNewImages() {
    loadFileForCacheOnInit(urlify("3rdparty/tinymce/jscripts/tiny_mce/themes/advanced/img/icons.gif"));
}

function getDirectLinkLocal(oObj){
    if (!oObj) return;
    oObj["folder"] = oObj.folder || oObj.folder_id;
    if (oObj.id) oObj["object_id"] = oObj.id;
    oObj["module"] = oObj.module || "infostore";
    return directLink_location.format(oObj);
}

function getMimeImage(sMimeType){
    if (sMimeType.indexOf(";") != -1) {
        sMimeType = sMimeType.substring(0, sMimeType.indexOf(";"));
    }
    var oImageMap = new Object();
    oImageMap["application/pdf"] = "pdf.png";
    oImageMap["text/plain"] = "txt.png";
    oImageMap["text/ical"] = "ical.gif";
    oImageMap["text/x-ical"] = "ical.gif";
    oImageMap["text/calendar"] = "ical.gif";
    oImageMap["text/x-calendar"] = "ical.gif";
    oImageMap["text/vcard"] = "vcard.gif";
    oImageMap["text/x-vcard"] = "vcard.gif";
    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,true);
            }, cb);
    } else {
        fade_setOpacity(node, end, false);
        if (cb) cb();
        return emptyFunction;
    }
}

function fade_setOpacity(node,opacity,fading) {
/*
    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 || 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 = ""; }
    } 
}

var windowWidth = 800;
var windowHeight = 660;
function getWindowOptions(width, height, optional) {
    var x = (screen.width - (width || windowWidth)) / 2;
    var y = (screen.height - (height ||windowHeight)) / 2 - (25);    
    return "width=" + (width || windowWidth) + "px,height=" + (height ||windowHeight) + "px,resizable=yes,menubar=no,toolbar=no,status=no,left=" + x + ",top=" + y + ",screenX=" + x + ",screenY=" + y + (optional ? ", " + optional : "");
}

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

function getDefaultCalendarView() {
    var defaults = configGetKey("gui.calendar.views");
    return "calendar/" + defaults.view + "/" + defaults[defaults.view];
}

function removeFolder(id) {
    var currentFolder = ox.UIController.getFolder();
    var currentModule = ox.UIController.getModule();
    
    var remove = function () {
        // remove folder via API
        ox.api.folder.remove({ id: id , success: function(){
            if( ox.UIController.getModule() === "mail") {
               // for email folder refresh trash folder after delete
                ox.api.folder.getSubFolders({
                    folder: ox.api.config.get("modules.mail.defaultFolder.trash"),
                    cache:false,
                    success: function() {
                        ox.api.folder.dispatcher.trigger("remove modify *");
                    }
                });
            }
        } });
    };
    
    // continuation
    var yes = function () {
        // remove current folder?
        if (id === currentFolder) {
            // get path
            ox.api.folder.getParents({
                folder: id,
                success: function (path) {
                    if (path.length < 2 || !ox.api.ui.isModule(path[1].module)) {
                        // change to default folder
                        ox.UIController.setModule({
                            module: currentModule,
                            folder: "default",
                            success: remove
                        });
                    } else {
                        // change folder
                        ox.UIController.setFolder({
                            folder: path[1].id,
                            module: "default",
                            success: remove
                        });
                    }
                }
            });
        } else {
            // remove folder
            remove();
        }
    };
    
    // ask user first
    newConfirm(
        _("Delete Folder"),
        _('Are you sure you want to delete the selected folder?'),
        AlertPopup.YESNO, null, null, yes, null
    );
}

var track = function (options) {
    triggerEvent("User_Action", options);
};

function requireOAuth() { requireOAuth.required = true; }
