/*
 * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
 * Digest Algorithm, as defined in RFC 1321.
 * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.
 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
 * Distributed under the BSD License
 * See http://pajhome.org.uk/crypt/md5 for more info.
 */

/*
 * Configurable variables. You may need to tweak these to be compatible with
 * the server-side, but the defaults work in most cases.
 */
var hexcase = 0;  /* hex output format. 0 - lowercase; 1 - uppercase        */
var b64pad  = ""; /* base-64 pad character. "=" for strict RFC compliance   */
var chrsz   = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode      */

/*
 * These are the functions you'll usually want to call
 * They take string arguments and return either hex or base-64 encoded strings
 */
function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));}
function b64_md5(s){ return binl2b64(core_md5(str2binl(s), s.length * chrsz));}
function str_md5(s){ return binl2str(core_md5(str2binl(s), s.length * chrsz));}
function hex_hmac_md5(key, data) { return binl2hex(core_hmac_md5(key, data)); }
function b64_hmac_md5(key, data) { return binl2b64(core_hmac_md5(key, data)); }
function str_hmac_md5(key, data) { return binl2str(core_hmac_md5(key, data)); }

/*
 * Perform a simple self-test to see if the VM is working
 */
function md5_vm_test()
{
  return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72";
}

/*
 * Calculate the MD5 of an array of little-endian words, and a bit length
 */
function core_md5(x, len)
{
  /* append padding */
  x[len >> 5] |= 0x80 << ((len) % 32);
  x[(((len + 64) >>> 9) << 4) + 14] = len;

  var a =  1732584193;
  var b = -271733879;
  var c = -1732584194;
  var d =  271733878;

  for(var i = 0; i < x.length; i += 16)
  {
    var olda = a;
    var oldb = b;
    var oldc = c;
    var oldd = d;

    a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
    d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
    c = md5_ff(c, d, a, b, x[i+ 2], 17,  606105819);
    b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
    a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
    d = md5_ff(d, a, b, c, x[i+ 5], 12,  1200080426);
    c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
    b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
    a = md5_ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
    d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
    c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
    b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
    a = md5_ff(a, b, c, d, x[i+12], 7 ,  1804603682);
    d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
    c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
    b = md5_ff(b, c, d, a, x[i+15], 22,  1236535329);

    a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
    d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
    c = md5_gg(c, d, a, b, x[i+11], 14,  643717713);
    b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
    a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
    d = md5_gg(d, a, b, c, x[i+10], 9 ,  38016083);
    c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
    b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
    a = md5_gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
    d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
    c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
    b = md5_gg(b, c, d, a, x[i+ 8], 20,  1163531501);
    a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
    d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
    c = md5_gg(c, d, a, b, x[i+ 7], 14,  1735328473);
    b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);

    a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
    d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
    c = md5_hh(c, d, a, b, x[i+11], 16,  1839030562);
    b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
    a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
    d = md5_hh(d, a, b, c, x[i+ 4], 11,  1272893353);
    c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
    b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
    a = md5_hh(a, b, c, d, x[i+13], 4 ,  681279174);
    d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
    c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
    b = md5_hh(b, c, d, a, x[i+ 6], 23,  76029189);
    a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
    d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
    c = md5_hh(c, d, a, b, x[i+15], 16,  530742520);
    b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);

    a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
    d = md5_ii(d, a, b, c, x[i+ 7], 10,  1126891415);
    c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
    b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
    a = md5_ii(a, b, c, d, x[i+12], 6 ,  1700485571);
    d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
    c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
    b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
    a = md5_ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
    d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
    c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
    b = md5_ii(b, c, d, a, x[i+13], 21,  1309151649);
    a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
    d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
    c = md5_ii(c, d, a, b, x[i+ 2], 15,  718787259);
    b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);

    a = safe_add(a, olda);
    b = safe_add(b, oldb);
    c = safe_add(c, oldc);
    d = safe_add(d, oldd);
  }
  return Array(a, b, c, d);

}

/*
 * These functions implement the four basic operations the algorithm uses.
 */
function md5_cmn(q, a, b, x, s, t)
{
  return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
}
function md5_ff(a, b, c, d, x, s, t)
{
  return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
}
function md5_gg(a, b, c, d, x, s, t)
{
  return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
}
function md5_hh(a, b, c, d, x, s, t)
{
  return md5_cmn(b ^ c ^ d, a, b, x, s, t);
}
function md5_ii(a, b, c, d, x, s, t)
{
  return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
}

/*
 * Calculate the HMAC-MD5, of a key and some data
 */
function core_hmac_md5(key, data)
{
  var bkey = str2binl(key);
  if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz);

  var ipad = Array(16), opad = Array(16);
  for(var i = 0; i < 16; i++)
  {
    ipad[i] = bkey[i] ^ 0x36363636;
    opad[i] = bkey[i] ^ 0x5C5C5C5C;
  }

  var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);
  return core_md5(opad.concat(hash), 512 + 128);
}

/*
 * Add integers, wrapping at 2^32. This uses 16-bit operations internally
 * to work around bugs in some JS interpreters.
 */
function safe_add(x, y)
{
  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
  return (msw << 16) | (lsw & 0xFFFF);
}

/*
 * Bitwise rotate a 32-bit number to the left.
 */
function bit_rol(num, cnt)
{
  return (num << cnt) | (num >>> (32 - cnt));
}

/*
 * Convert a string to an array of little-endian words
 * If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
 */
function str2binl(str)
{
  var bin = Array();
  var mask = (1 << chrsz) - 1;
  for(var i = 0; i < str.length * chrsz; i += chrsz)
    bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32);
  return bin;
}

/*
 * Convert an array of little-endian words to a string
 */
function binl2str(bin)
{
  var str = "";
  var mask = (1 << chrsz) - 1;
  for(var i = 0; i < bin.length * 32; i += chrsz)
    str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);
  return str;
}

/*
 * Convert an array of little-endian words to a hex string.
 */
function binl2hex(binarray)
{
  var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
  var str = "";
  for(var i = 0; i < binarray.length * 4; i++)
  {
    str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
           hex_tab.charAt((binarray[i>>2] >> ((i%4)*8  )) & 0xF);
  }
  return str;
}

/*
 * Convert an array of little-endian words to a base-64 string
 */
function binl2b64(binarray)
{
  var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  var str = "";
  for(var i = 0; i < binarray.length * 4; i += 3)
  {
    var triplet = (((binarray[i   >> 2] >> 8 * ( i   %4)) & 0xFF) << 16)
                | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 )
                |  ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);
    for(var j = 0; j < 4; j++)
    {
      if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
      else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
    }
  }
  return str;
}

// OneCode Application Framework | Copyright (C) 2005-2009 Radek Tetik | www.onecode.cz

Prototype.Browser.IE6 = Prototype.Browser.IE && parseInt(navigator.userAgent.substring(navigator.userAgent.indexOf("MSIE")+5)) == 6;
Prototype.Browser.IE7 = Prototype.Browser.IE && parseInt(navigator.userAgent.substring(navigator.userAgent.indexOf("MSIE")+5)) == 7;
Prototype.Browser.IE8 = Prototype.Browser.IE && !Prototype.Browser.IE6 && !Prototype.Browser.IE7;

/**
* Inheritance, traits, dynamic loading etc.
*/
var Wsf =
{
    /**
    * From the book Pro Javascript Design Patterns, page 43.
    */
    extend: function (subClass, superClass)
    {
        var F = function() {};
        F.prototype = superClass.prototype;
        subClass.prototype = new F();
        subClass.prototype.constructor = subClass;
    },

    use: function (class_, trait)
    {
        for (o in trait) class_.prototype[o] = trait[o];
    },

    requireClass: function (className)
    {
        if (eval("typeof "+className) != "undefined") return;

        var url = className.replace(/\_/g, '/');
        var code = null;

        Wsf.requireScript("/"+url.substr(1)+".js");
    },

    requireScript: function (file)
    {
        var code = false;

        var r = new Ajax.Request(file, {
            method: "get",
            asynchronous: false,
            onSuccess: function(transport) { code = transport.responseText; }
        });

        if (!code) { alert('Error loading class. Please restart the application by refreshing the page (F5).'); return; }

        var script = document.createElement('script');
        script.type = 'text/javascript';
        script.text = code;
        document.body.appendChild(script);
    },

    requireStylesheet: function (file)
    {
        var code = false;

        var r = new Ajax.Request(file, {
            method: "get",
            asynchronous: false,
            onSuccess: function(transport) { code = transport.responseText; }
        });

        if (!code) { alert('Error loading stylesheet. Please restart the application by refreshing the page (F5).'); return; }

        var e = document.createElement('style');
        e.setAttribute('type', 'text/css');

        if (e.styleSheet)
            e.styleSheet.cssText = code;
        else
            e.appendChild(document.createTextNode(code));

        document.getElementsByTagName("head")[0].appendChild(e);
    },

    sprintf: function ( ) {
        // Return a formatted string
        //
        // version: 909.322
        // discuss at: http://phpjs.org/functions/sprintf
        // +   original by: Ash Searle (http://hexmen.com/blog/)
        // + namespaced by: Michael White (http://getsprink.com)
        // +    tweaked by: Jack
        // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
        // +      input by: Paulo Ricardo F. Santos
        // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
        // +      input by: Brett Zamir (http://brett-zamir.me)
        // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
        // *     example 1: sprintf("%01.2f", 123.1);
        // *     returns 1: 123.10
        // *     example 2: sprintf("[%10s]", 'monkey');
        // *     returns 2: '[    monkey]'
        // *     example 3: sprintf("[%'#10s]", 'monkey');
        // *     returns 3: '[####monkey]'
        var regex = /%%|%(\d+\$)?([-+\'#0 ]*)(\*\d+\$|\*|\d+)?(\.(\*\d+\$|\*|\d+))?([scboxXuidfegEG])/g;
        var a = arguments, i = 0, format = a[i++];

        // pad()
        var pad = function (str, len, chr, leftJustify) {
            if (!chr) {chr = ' ';}
            var padding = (str.length >= len) ? '' : Array(1 + len - str.length >>> 0).join(chr);
            return leftJustify ? str + padding : padding + str;
        };

        // justify()
        var justify = function (value, prefix, leftJustify, minWidth, zeroPad, customPadChar) {
            var diff = minWidth - value.length;
            if (diff > 0) {
                if (leftJustify || !zeroPad) {
                    value = pad(value, minWidth, customPadChar, leftJustify);
                } else {
                    value = value.slice(0, prefix.length) + pad('', diff, '0', true) + value.slice(prefix.length);
                }
            }
            return value;
        };

        // formatBaseX()
        var formatBaseX = function (value, base, prefix, leftJustify, minWidth, precision, zeroPad) {
            // Note: casts negative numbers to positive ones
            var number = value >>> 0;
            prefix = prefix && number && {'2': '0b', '8': '0', '16': '0x'}[base] || '';
            value = prefix + pad(number.toString(base), precision || 0, '0', false);
            return justify(value, prefix, leftJustify, minWidth, zeroPad);
        };

        // formatString()
        var formatString = function (value, leftJustify, minWidth, precision, zeroPad, customPadChar) {
            if (precision != null) {
                value = value.slice(0, precision);
            }
            return justify(value, '', leftJustify, minWidth, zeroPad, customPadChar);
        };

        // doFormat()
        var doFormat = function (substring, valueIndex, flags, minWidth, _, precision, type) {
            var number;
            var prefix;
            var method;
            var textTransform;
            var value;

            if (substring == '%%') {return '%';}

            // parse flags
            var leftJustify = false, positivePrefix = '', zeroPad = false, prefixBaseX = false, customPadChar = ' ';
            var flagsl = flags.length;
            for (var j = 0; flags && j < flagsl; j++) {
                switch (flags.charAt(j)) {
                    case ' ': positivePrefix = ' '; break;
                    case '+': positivePrefix = '+'; break;
                    case '-': leftJustify = true; break;
                    case "'": customPadChar = flags.charAt(j+1); break;
                    case '0': zeroPad = true; break;
                    case '#': prefixBaseX = true; break;
                }
            }

            // parameters may be null, undefined, empty-string or real valued
            // we want to ignore null, undefined and empty-string values
            if (!minWidth) {
                minWidth = 0;
            } else if (minWidth == '*') {
                minWidth = +a[i++];
            } else if (minWidth.charAt(0) == '*') {
                minWidth = +a[minWidth.slice(1, -1)];
            } else {
                minWidth = +minWidth;
            }

            // Note: undocumented perl feature:
            if (minWidth < 0) {
                minWidth = -minWidth;
                leftJustify = true;
            }

            if (!isFinite(minWidth)) {
                throw new Error('sprintf: (minimum-)width must be finite');
            }

            if (!precision) {
                precision = 'fFeE'.indexOf(type) > -1 ? 6 : (type == 'd') ? 0 : undefined;
            } else if (precision == '*') {
                precision = +a[i++];
            } else if (precision.charAt(0) == '*') {
                precision = +a[precision.slice(1, -1)];
            } else {
                precision = +precision;
            }

            // grab value using valueIndex if required?
            value = valueIndex ? a[valueIndex.slice(0, -1)] : a[i++];

            switch (type) {
                case 's': return formatString(String(value), leftJustify, minWidth, precision, zeroPad, customPadChar);
                case 'c': return formatString(String.fromCharCode(+value), leftJustify, minWidth, precision, zeroPad);
                case 'b': return formatBaseX(value, 2, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
                case 'o': return formatBaseX(value, 8, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
                case 'x': return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
                case 'X': return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad).toUpperCase();
                case 'u': return formatBaseX(value, 10, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
                case 'i':
                case 'd':
                    number = parseInt(+value, 10);
                    prefix = number < 0 ? '-' : positivePrefix;
                    value = prefix + pad(String(Math.abs(number)), precision, '0', false);
                    return justify(value, prefix, leftJustify, minWidth, zeroPad);
                case 'e':
                case 'E':
                case 'f':
                case 'F':
                case 'g':
                case 'G':
                    number = +value;
                    prefix = number < 0 ? '-' : positivePrefix;
                    method = ['toExponential', 'toFixed', 'toPrecision']['efg'.indexOf(type.toLowerCase())];
                    textTransform = ['toString', 'toUpperCase']['eEfFgG'.indexOf(type) % 2];
                    value = prefix + Math.abs(number)[method](precision);
                    return justify(value, prefix, leftJustify, minWidth, zeroPad)[textTransform]();
                default: return substring;
            }
        };

        return format.replace(regex, doFormat);
    }
}

/**
* DOM element extensions
*/
var WsfElement =
{
    /**
    * Sets the position and optionally sets the content size.
    */
    move: function(element,x,y,w,h) {
        element.style.left = x + "px";
        element.style.top = y + "px";
        if (w !== null) element.style.width = w + "px";
        if (h !== null) element.style.height = h + "px";
    },

    /**
    * Sets the content size (=not including padding and border)
    */
    setContentSize: function(element,w,h) {
        if (w !== null) element.style.width = w + "px";
        if (h !== null) element.style.height = h + "px";
    },

    getTextContent: function (element) {
        return Prototype.Browser.IE ? element.innerText : element.textContent;
    },

    setTextContent: function (element, text) {
        if (Prototype.Browser.IE)
            element.innerText = text;
        else
            element.textContent = text;
    }
}

Element.addMethods(WsfElement);

/**
* Events trait
*/
var Wsf_EventsTrait = {
    attachEventHandler:  function (eventName, fnCallback)
    {
        if (this._events[eventName] == undefined) { this._events[eventName] = []; }
        this._events[eventName].push(fnCallback);
    },

    /**
    * @param eventData    an object { sender } plus custom properties
    */
    raiseEvent: function (eventName, eventData)
    {
        if (this._events[eventName] == undefined) return;
        if (!eventData) eventData = {};
        eventData.sender = this;

        for (var i=this._events[eventName].length; i--;) {
            this._events[eventName][i](eventData);
        }
    }
};

/*****************************************************************************/
function Wsf_App(state)
{
    this._ctrls = {};
    this._rootCtrl = null;
    this.lang = state.lang;
    this._deletedServerCtrls = [];
    this._nextCtrlId = state._nextCtrlId;
    this._nextServerCtrlId = this._nextCtrlId;

    // RSH

    if (window.dhtmlHistory) {
        window.dhtmlHistory.create({
            toJSON: function(o) { return Object.toJSON(o); },
            fromJSON: function(s) { return s.evalJSON(); }
        });
        window.dhtmlHistory.initialize();
    }

    // ...

    document.observe('dom:loaded', this.onDomReady.bind(this));
}

/*****************************************************************************/
Wsf_App.prototype.onDomReady = function()
{
    if (Prototype.Browser.IE6) $(document.body).addClassName('ie6');

    // Init controls

    var ctrls = this._CreateCtrlTree();

    for (var id in ctrls) {
        this._ctrls[id] = ctrls[id][0];
    }

    for (var id in ctrls) {
        this._ctrls[id].createFromServerState(ctrls[id][1]);
    }

    this._rootCtrl._makeStateSnapshot();
    this._rootCtrl.initFromMarkup();
    this._rootCtrl.onCreate();

    // Resize

    Event.observe(window, 'resize', this._onResize.bindAsEventListener(this));
    this._onResize();

    // In chrome and IE CSS is not ready in the domready event, so update the the layout in the load event

    Event.observe(window, 'load', this._onResize.bind(this));

    // Start automatic UI update

    this._rootCtrl.updateUI();
    this._startUpdateUI();
}

/*****************************************************************************/
Wsf_App.prototype._startUpdateUI = function()
{
    if (!this._updateUIExecuter) {
        this._updateUIExecuter = new PeriodicalExecuter(this._rootCtrl.updateUI.bind(this._rootCtrl), .25);
    }
}

/*****************************************************************************/
Wsf_App.prototype._stopUpdateUI = function()
{
    if (this._updateUIExecuter) {
        this._updateUIExecuter.stop();
        this._updateUIExecuter = null;
    }
}

/**
* Sets a cookie.
* The valus is encoded using encodeURIComponent(). You must use urldecode() in PHP.
* @param expireDate       Date object or null for session duration
*/
Wsf_App.prototype.setCookie = function(name, value, expireDate, isGlobal)
{
    var s = name + "=" + encodeURIComponent(value);
    if (expireDate !== null) s += "; expires=" + expireDate.toGMTString();
    if (isGlobal) s += "; path" + "=/";      // to make HTTRACK dizzy ;-)
    document.cookie = s;
}

/*****************************************************************************/
Wsf_App.prototype.getCookie = function (name, defValue)
{
    // cookies are separated by semicolons
    // IE bug: cookies in total can have up-to 4096KB
    var cookies = document.cookie.split("; ");

    for (var i=0; i < cookies.length; i++) {
        // a name/value pair (a crumb) is separated by an equal sign
        var aCrumb = cookies[i].split("=");
        if (name == aCrumb[0]) return unescape(aCrumb[1]);
    }

    // a cookie with the requested name does not exist
    return defValue;
}

/*****************************************************************************/
Wsf_App.prototype.getCookieInt = function (name, defValue)
{
    return parseInt(this.getCookie(name, defValue));
}

/*****************************************************************************/
Wsf_App.prototype.refresh = function ()
{
    window.location.reload();
}

/*****************************************************************************/
Wsf_App.prototype.Navigate = function (url)
{
    window.location.href = url;
}

/*******************************************************************************/
/*
/* CONTROLS
/*
/*******************************************************************************/

/*****************************************************************************/
Wsf_App.prototype.getRootCtrl = function()
{
    return this._rootCtrl;
}

/*****************************************************************************/
Wsf_App.prototype._registerCtrl = function (id, ctrl)
{
    this._ctrls[id] = ctrl;
}

/*****************************************************************************/
Wsf_App.prototype._deregisterCtrl = function (ctrl)
{
    var id = ctrl.getId();
    delete this._ctrls[id];

    if (id.substr(1) < this._nextServerCtrlId) {
        this._deletedServerCtrls.push(id);
    }
}

/*****************************************************************************/
Wsf_App.prototype.getCtrlById = function (id)
{
    return id ? this._ctrls[id] : null;
}

/*******************************************************************************/
/*
/* CMDS
/*
/*******************************************************************************/

/*******************************************************************************/
Wsf_App.prototype.updateUI = function ()
{
    this._rootCtrl.updateUI();
}

/*******************************************************************************/
Wsf_App.prototype.invokeUpdateCmd = function (sourceCtrl, cmdId, CmdUI)
{
    var method = "onUpdate" + cmdId;

    var a = sourceCtrl.getCmdTarget();

    for (; a; a=a.getParent()) {
        if (a[method] != undefined) {
            a[method](CmdUI);
            break;
        }
        else if (a.onUpdateCmdUI(cmdId, CmdUI))
            break;
    }
}

/*******************************************************************************/
Wsf_App.prototype.invokeCmd = function (sourceCtrl, cmdId, params)
{
    // Ensure cmd is enabled

    var cmdUI = {
        isEnabled: true,
        enable: function (isEnabled) { this.isEnabled = isEnabled; },
        check: function (isChecked) { }
    };

    this.invokeUpdateCmd(sourceCtrl, cmdId, cmdUI);
    if (!cmdUI.isEnabled) return;

    // Handle on client
    // Try concrete OnXYZ() handler and then generic OnCommand() handler

    var method = "on" + cmdId;
    var cmdTarget = sourceCtrl.getCmdTarget();
    params.sender = sourceCtrl;
    params.cmdId = cmdId;

    for (var a=cmdTarget; a; a=a.getParent()) {
        if (a[method] != undefined) {
            if (a[method](params) !== false) return;
            break;
        }
        else if (a['onCommand'] != undefined) {
            if (a.onCommand(params) !== false) return;
        }
    }

    // Handle on server

    delete params.sender;
    delete params.cmdId;

    Wsf_Web_CallServerMethodHandler.invoke("Wsf_Web_MVC_CtrlManager", "_HandleCommand",
        { cmdId: cmdId, cmdParams: params, srcCtrlId: cmdTarget.getId() },
        null, null,
        false);
}

/*******************************************************************************/
/*
/* EVENTS
/*
/*******************************************************************************/

Wsf_App.prototype._onResize = function (ev)
{
    if (this._inResize) return;   // IE calls resize multiple times
    this._inResize = true;
    this._rootCtrl.onSize();
    this._inResize = false;
}


var Wsf_Web_MVC_ControlStateCoder = {

   /*******************************************************************************/
   DecodeStateVariable : function (value)
   {
      if (value === null) {
         return value;
      }
      else if (typeof(value) == "string") {
         return value.charAt(0) == '*' ? value.substr(1) : g_app.getCtrlById(value);
      }
      else if (typeof(value) == "object") {
         if (Object.isArray(value)) {
            for (var i=0; i < value.length; ++i) {
               value[i] = this.DecodeStateVariable(value[i]);
            }      
            return value;
         }
         else {
            for (var name in value) {
               value[name] = this.DecodeStateVariable(value[name]);
            }      
            return value;
         }
      }
      else {
         return value;
      }
   },
  
   /*******************************************************************************/
   encodeVariable : function (value)
   {
      if (value === null)
      {
         return value;
      }
      else if (typeof(value) == "object")
      {
         if (value.isA)
         {
            return value.getId();
         }
         else if (Object.isArray(value))
         {
            var newArray = [];
            
            for (var i=0; i < value.length; ++i)
            {
               newArray[i] = this.encodeVariable(value[i]);
            }
            
            return newArray;
         }
         else
         {
            var newObj = {};
            
            for (var name in value)
            {
               newObj[name] = this.encodeVariable(value[name]);
            }
            
            return newObj;
         }
      }
      else if (typeof(value) == "string")
      {
         return "*"+value;
      }
      else 
      {
         return value;
      }
   }
};


/**
* Command handling:
* - define concrete handler OnXYZ(params)
*   Return TRUE or nothing if handled or FALSE to handle on the server
* - define generic handler OnCommand(eventData), where eventData = { cmdId, sender, params1, param2, etc. }
*   Return TRUE if handled or FALSE to continue routing.
*/

/*******************************************************************************/
function Wsf_Ctrls_Control()
{
   this._classes = ['Wsf_Ctrls_Control'];
   this._offsetWidth = null;
   this._offsetHeight = null;
   this._clientWidth = null;
   this._clientHeight = null;
   this._events = {};
   this._children = [];
   this._serverState = {};
}

Wsf_Ctrls_Control.EVENT_AFTER_DELETE =  100001;

Wsf.use(Wsf_Ctrls_Control, Wsf_EventsTrait);

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.createFromServerState = function (serverState)
{
   for (var name in serverState) this._serverState[name] = null;
   this._loadState(serverState);
   
   if (this._parent) 
      this._parent._children.push(this);
   else 
      g_app._rootCtrl = this;  
}

/**
* Init control from markup. May be called multiple times during the life of the control.
*/
Wsf_Ctrls_Control.prototype.initFromMarkup = function ()
{
   this.elem = $(this._id);
   if (!this.elem) console.log(this._id, " not found");
   this.elem.WsfCtrl = this;     // Got error? The control has not been rendered
   
   this._layout = this._childrenLayout ? eval('new '+this._childrenLayout+'(this);') : null;
   if (!this._enabled) this._enableInternal(false);
   
   if (this._layout) this.elem.style.overflow = "hidden";
   
   for (var i=0,n=this._children.length ; i < n; ++i) this._children[i].initFromMarkup();
}

/**
* Called after all controls have been properly inited. 
* Overload to init the control, you may do RPC here.
*/
Wsf_Ctrls_Control.prototype.onCreate = function ()
{
   if (this._parent) this._parent.onChildAdded(this);

   for (var i=0,n=this._children.length ; i < n; ++i) this._children[i].onCreate();
}

/**
* Called in ajax, when state is updated.
*/
Wsf_Ctrls_Control.prototype.onStateChanged = function ()
{
   this.show(this._visible);
}

Wsf_Ctrls_Control.prototype.onChildAdded = function (child)
{
}

Wsf_Ctrls_Control.prototype.onChildDeleted = function (child)
{
}

/*******************************************************************************
*
* STATE
*
*******************************************************************************/

/**
* Load state from the server. 
*/
Wsf_Ctrls_Control.prototype._loadState = function (state)
{
   for (var name in state) 
   {
      this[name] = Wsf_Web_MVC_ControlStateCoder.DecodeStateVariable(state[name]);
   }
}
 
/*******************************************************************************/
Wsf_Ctrls_Control.prototype.saveState = function (ctrlsState)
{
   var state = this._getControlState();
   // save only if non-empty
   for (var name in state) { ctrlsState[this.getId()] = state; break; } 
   
   for (var i=0; i < this._children.length; ++i)
   {
      this._children[i].saveState(ctrlsState);
   }
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype._makeStateSnapshot = function ()
{
   for (var name in this._serverState) 
   {
      this._serverState[name] = Object.toJSON(Wsf_Web_MVC_ControlStateCoder.encodeVariable(this[name]));
   }
   
   for (var i=0; i < this._children.length; ++i) this._children[i]._makeStateSnapshot();
}

/**
* Returns a state object to send to the server.
* You can overload it to add custom state to be passed to the server.
*/
Wsf_Ctrls_Control.prototype._getControlState = function ()
{
   if (!this._serverState) return {};

   var state = { };
   
   this._visible = this.elem.visible();

   for (var name in this._serverState) 
   {
      var newValue = Wsf_Web_MVC_ControlStateCoder.encodeVariable(this[name]);
      if (Object.toJSON(newValue) !== this._serverState[name]) state[name] = newValue;
   }

   return state;
}

/*******************************************************************************/
/*
/* ...
/*
/*******************************************************************************/

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.onPreDelete = function ()
{
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.deleteCtrl = function ()
{
   // Already deleted? View cannot be deleted so this test is ok.
   if (!this._parent) return;
   
   this._parent._children = this._parent._children.without(this);
   this.elem.parentNode.removeChild(this.elem);  // removes the whole HTML subtree
   
   this._deleteInternal();
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype._deleteInternal = function ()
{
   this.onPreDelete();

   this.elem = null;
   var parent = this._parent;
   this._parent = null;   
   
   this._children.each(function (ctrl) { ctrl._deleteInternal(); });
   this._children = [];

   g_app._deregisterCtrl(this);
   
   parent.onChildDeleted(this);
   this.raiseEvent(Wsf_Ctrls_Control.EVENT_AFTER_DELETE, { sender: this });
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.getElem = function ()
{
   return this.elem;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype._getChildrenContainerElem = function ()
{
   return this.elem;
}

/**
* Register a class name to be able to RTTI (e.g. isA())
*/
Wsf_Ctrls_Control.prototype._addClass = function (className)
{
   this._classes.push(className);
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.isA = function (className)
{
   return this._classes.indexOf(className) != -1;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.getAncestorByClass = function (className)
{
   for (var o=this; o; o=o._parent) {
      if (o.isA(className)) return o;
   }
   return null;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.getView = function ()
{
   return Wsf_Ctrls_ViewHtml.prototype._instance;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.getTitle = function ()
{
   return this._title;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.getId = function ()
{
   return this._id;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.getParent = function ()
{
   return this._parent;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.getChildByIdx = function (idx)
{
   return this._children[idx];
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.getChildrenCount = function ()
{
   return this._children.length;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.getChildren = function ()
{
   return this._children;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.updateUI = function ()
{
   for (var i=0; i < this._children.length; ++i)
   {
      this._children[i].updateUI();    
   }
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.getForm = function ()
{
   for (var p = this; p && !p.isA("Wsf_Forms_CtrlForm"); p=p._parent) /* dummy */;
   
   return p;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.getWindow = function ()
{
   for (var p = this; p && !p.isA("Wsf_Ctrls_Window"); p=p._parent) /* dummy */;
   
   return p;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.focus = function ()
{
   if (!this.isVisible() || !this.isEnabled()) return false;
   
   this.elem.focus();   
   return true;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.hasFocus = function ()
{
   return this.getView().getFocus() === this;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.enable = function (enable)
{
   if (enable == this._enabled) return;
   this._enableInternal(enable);
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype._enableInternal = function (enable)
{
   if (enable) 
      this.elem.removeClassName('disabled'); 
   else 
      this.elem.addClassName('disabled'); 
   
   this.elem.disabled = !enable;
   this._enabled = enable;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.isEnabled = function ()
{
   return this._enabled;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.isEnabled = function ()
{
   return this._enabled;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.isReadOnly = function ()
{
   return this._readOnly;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.getStyle = function (style, defValue)
{
   return this._styles && this._styles[style] ? this._styles[style] : defValue;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.setTitle = function (title, isHtml)
{
   if (isHtml) {
      this._titleHtml = title;
      this._title = null;
   }
   else {
      this._titleHtml = null;
      this._title = title;
   }
   
   return this;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.callServerMethod = function (method, params, fnOnSuccess, fnOnFailure)
{
   return Wsf_Web_CallServerMethodHandler.invoke(this, method, params, fnOnSuccess, fnOnFailure); 
}


/*******************************************************************************/
/* 
/* VISIBILITY
/*
/*******************************************************************************/

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.isVisible = function ()
{
   return this.elem.visible();
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.show = function (show)
{
   if (show || show == undefined) 
      this.elem.show();
   else 
      this.elem.hide();

   if (this._parent) this._parent.onSize();
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.hide = function ()
{
   this.show(false);
}

/*******************************************************************************/
/* 
/* POSITION & SIZE - this is slow
/*
/*******************************************************************************/

/*******************************************************************************/
Wsf_Ctrls_Control.prototype._cacheComputedStyles = function ()
{
//   this._margin = [0,0,0,0];
//   this._border = [0,0,0,0];
//   this._padding = [0,0,0,0];
//   return;

   var s = window.getComputedStyle ? window.getComputedStyle(this.elem, null) : this.elem.currentStyle;

   var a = [parseInt(s.paddingTop), parseInt(s.paddingRight), parseInt(s.paddingBottom), parseInt(s.paddingLeft) ];
   if (isNaN(a[0])) a[0] = 0;
   if (isNaN(a[1])) a[1] = 0;
   if (isNaN(a[2])) a[2] = 0;
   if (isNaN(a[3])) a[3] = 0;
   this._padding = a;

   a = [parseInt(s.marginTop), parseInt(s.marginRight), parseInt(s.marginBottom), parseInt(s.marginLeft)];
   if (isNaN(a[0])) a[0] = 0;
   if (isNaN(a[1])) a[1] = 0;
   if (isNaN(a[2])) a[2] = 0;
   if (isNaN(a[3])) a[3] = 0;
   this._margin = a;
                 
   a = [parseInt(s.borderTopWidth), parseInt(s.borderRightWidth), parseInt(s.borderBottomWidth), parseInt(s.borderLeftWidth)];
   if (isNaN(a[0])) a[0] = 0;
   if (isNaN(a[1])) a[1] = 0;
   if (isNaN(a[2])) a[2] = 0;
   if (isNaN(a[3])) a[3] = 0;
   this._border = a;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype._clearSizeCache = function ()
{
   this._offsetWidth = null;
   this._offsetHeight = null;
   this._clientWidth = null;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype._cacheOffsetWidth = function ()
{
   this._offsetWidth = this.elem.offsetWidth;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype._cacheOffsetHeight = function ()
{
   this._offsetHeight = this.elem.offsetHeight;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype._cacheClientSize = function ()
{
   this._clientWidth = this.elem.clientWidth;
   this._clientHeight = this.elem.clientHeight;
}

/**
* content+padding+border
*/
Wsf_Ctrls_Control.prototype.getOffsetSize = function ()
{
   if (this._offsetWidth === null) this._cacheOffsetWidth();
   if (this._offsetHeight === null) this._cacheOffsetHeight();
   return { width: this._offsetWidth, height: this._offsetHeight };
}

/**
* content+padding+border
*/
Wsf_Ctrls_Control.prototype.getOffsetWidth = function () 
{
   if (this._offsetWidth === null) this._cacheOffsetWidth();
   return this._offsetWidth;
}

/**
* content+padding+border
*/
Wsf_Ctrls_Control.prototype.getOffsetHeight = function () 
{
   if (this._offsetHeight === null) this._cacheOffsetHeight();
   return this._offsetHeight;
}

/**
* content+padding 
*/
Wsf_Ctrls_Control.prototype.getClientSize = function ()
{
   if (this._clientWidth === null || this._clientHeight === null) this._cacheClientSize();
   return { width: this._clientWidth, height: this._clientHeight };
}

/**
* Slow!
*/
Wsf_Ctrls_Control.prototype.getMargin = function() 
{
   if (!this._margin) this._cacheComputedStyles();
   return this._margin;
}

/**
* Slow!
*/
Wsf_Ctrls_Control.prototype.getPadding = function() 
{
   if (!this._padding) this._cacheComputedStyles();
   return this._padding;
}

/**
* Slow!
*/
Wsf_Ctrls_Control.prototype._getMarginBorderPadding = function() 
{
   if (!this._margin) this._cacheComputedStyles();
   
   return [this._margin[0]+this._border[0]+this._padding[0],
      this._margin[1]+this._border[1]+this._padding[1],
      this._margin[2]+this._border[2]+this._padding[2],
      this._margin[3]+this._border[3]+this._padding[3]
      ];
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.move = function (x,y,w,h)
{
   var s = this.elem.style;
   
   if (x < 0) x = 0;
   if (y < 0) y = 0;
   
   if (s.position != "fixed") s.position = "absolute";

   s.left = x+"px";
   s.top = y+"px";
   
   if (w !== null || h !==null)
   {
      this.setSize(w, h);
   }
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.move2 = function (x1,y1,x2,y2,w,h)
{
   var s = this.elem.style;
   
   if (s.position != "fixed") s.position = "absolute";

   if (x1 !== null) s.left = x1+"px";
   if (y1 !== null) s.top = y1+"px";
   if (x2 !== null) s.right = x2+"px";
   if (y2 !== null) s.bottom = y2+"px";
   
   if ((x1 !== null && x2 !== null) || (y1 !== null && y2 !== null)) this._clearSizeCache();

   if (w !== null || h !== null)
   {
      this.setSize(w, h);
   }
   else this.onSize();
}
    
/*******************************************************************************/
Wsf_Ctrls_Control.prototype.setSize = function (w, h)
{
   var s = this.elem.style;
   var b = this._getMarginBorderPadding();
   
   if (w !== null) 
   {
      this._offsetWidth = w;
      w -= b[1] + b[3];
      if (w < 0) w = 0;
      s.width = w + "px";
      this._offsetWidth = w + b[1] + b[3];
   }
   
   if (h !== null) 
   {
      this._offsetHeight = h;
      h -= b[0] + b[2];
      if (h < 0) h = 0;
      s.height = h  + "px";
      this._offsetHeight = h + b[0] + b[2];
   }

   this.onSize();
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.onSize = function ()
{
   if (!this.isVisible()) return;
   
   if (this._layout) 
   {
      for (var i=0,n=this._children.length; i < n; ++i)
      {
         this._children[i]._clearSizeCache();
      }   
      this._layout.layoutChildren(this);
   }
   else
   {
      // Default CSS layout of children
      for (var i=0,n=this._children.length; i < n; ++i)
      {
         var o = this._children[i];
         o._clearSizeCache();
         o.onSize();
      }   
   }   
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.setZIndex = function (zIndex)
{
   this.elem.style.zIndex = zIndex;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.moveBelow = function (ctrl)
{
   this.elem.style.zIndex = ctrl.elem.style.zIndex - 1;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.setOpacity = function (opacityFloat)
{
   this.elem.setOpacity(opacityFloat);
}

/*******************************************************************************/
/* 
/* COMMANDS
/*
/*******************************************************************************/

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.getCmdTarget = function ()
{
   return this._cmdTarget ? this._cmdTarget : this;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.onUpdateCmdUI = function (cmdId, cmdui)
{
   return false;
}


/*******************************************************************************/
function Wsf_Ctrls_LayoutStack(ctrl)
{
   ctrl.elem.style.overflow = "auto";
}

/*******************************************************************************/
Wsf_Ctrls_LayoutStack.prototype.layoutChildren = function (ctrl)
{  
   // Get the height of all fixed controls and # of stretch controls
   
   var fixedH = 0;
   var stretchCount = 0;
   
   for (var i=ctrl._children.length; i--;)
   {
      var o = ctrl._children[i];
      if (!o.isVisible()) continue;
      
      if (o.getStyle('stretch',false))
      {
         ++stretchCount;
         o.elem.style.overflow = "auto";
      }
      else {                           
         var m = o.getMargin();
         fixedH += o.getOffsetHeight() + m[0] + m[2];
      }
   }
   
   // Layout

   var padding = ctrl.getPadding();
   var a = ctrl.getClientSize();
   var w = a['width'] - padding[1] - padding[3];
   var h = a['height'] - padding[0] - padding[2];
//   var x = padding[3];
   var y = padding[0];
   var stretchH = Math.max(h - fixedH, 0);
   
   if (stretchCount > 0)
   {
      var avgStretchH = Math.floor(stretchH / stretchCount);
      var lastStretchH = h - avgStretchH*(stretchCount-1) - fixedH;
   }
   
   for (var i=0,n=ctrl._children.length; i < n; ++i)
   {
      var o = ctrl._children[i];
      if (!o.isVisible()) continue;

      // e.g. input[text] does not stretch to 100% by default -> we must set also width
      
      if (o.getStyle('stretch',false))
      {
         h = (--stretchCount ? avgStretchH : lastStretchH);
//         o.move(x, y, w, h);
         o.setSize(w,h);
//         o.elem.style.position = "relative";
      }
      else
      {
         o.setSize(w,null);
//         var m = o.getMargin();
//         h = o.getOffsetHeight() + m[0] + m[2];
//         o.move(x, y, w, null);
//         o.elem.style.position = "relative";
      }
      
//      y += h;
   }   
}


/*******************************************************************************/
function Wsf_Ctrls_LayoutFullsize(ctrl)
{
}

/*******************************************************************************/
Wsf_Ctrls_LayoutFullsize.prototype.layoutChildren = function (ctrl)
{  
   for (var i=0,n=ctrl.getChildrenCount(); i<n; ++i)
   {
      var t = ctrl.getChildByIdx(i);

      if (t.isVisible())
      {
         var p = ctrl.getPadding();
         t.move2(p[3],p[0],p[1],p[2],null,null);
         break;
      }
   }
}


/*****************************************************************************/
var Wsf_Web_Vocabulary = { 
   
   // Vocabularies are added as new classes are loaded via AJAX
   _vocabularies : [],
   
   add : function (vocabulary)
   {
      this._vocabularies.push(vocabulary);
   },
   
   getWord : function (id)
   {
      a = id.split('.');
   
      for (var i=0,n=this._vocabularies.length; i < n; ++i)
      {
         var ns = this._vocabularies[i][a[0]];
         
         if (ns)
         {
            var w = ns[a[1]];
            if (w) return w;
         }
      }
      
      return "[***"+id+"***]";
   }
};

/*****************************************************************************/
function W(id)
{
   return Wsf_Web_Vocabulary.getWord(id);
}

/*******************************************************************************/
function Wsf_Web_CallServerMethodHandler(ctrl, method, params, fnOnSuccess, fnOnFailure, async)
{
    if (async === undefined) async = true;

    // Busy info

    var div = $(document.createElement('div'));
    div.id = 'wsfAjaxStatus';
    div.update(W('Wsf_Ctrls.LOADING')+"...");
    document.body.appendChild(div);

    // Overlay is needed to disable clicking 

    var div = $(document.createElement('div'));
    div.id = 'wsfDisableUiOverlay';
    document.body.appendChild(div);

    // DTO

    var postParams = { 
      wsfState: {
         async: async, 
         object: Wsf_Web_MVC_ControlStateCoder.encodeVariable(ctrl),
         method: method, 
         params: params === undefined ? {} : Wsf_Web_MVC_ControlStateCoder.encodeVariable(params), 
         ctrlsState: {},
         deletedServerCtrls: g_app._deletedServerCtrls
      }
    };

    g_app.getRootCtrl().saveState(postParams.wsfState.ctrlsState);
    postParams.wsfState = Object.toJSON(postParams.wsfState);

    // GO!

    if (async)
    {
        var a = $$('input','select','textarea');
        var filePresent = false;

        for (var i=0,n=a.length; i < n; ++i)
        {
            var elem = a[i];
            var name = elem.name;
            if (name == 'wsfState') continue;

            if (elem.type == 'file') 
            {
                if (!elem.files.length) continue;
                postParams[name] = { filename: elem.files[0].fileName, fileObject: elem.files[0] };
                filePresent = true;
            }
            else
            {         
                if (elem.type == 'checkbox' && !elem.checked) continue;
                var value = elem.value;

                if (postParams[name] === undefined)
                    postParams[name] = value;
                else {
                    if (!Object.isArray(postParams[name])) postParams[name] = [postParams[name]];
                    postParams[name].push(value);
                }
            }
      }

      if (filePresent)
      {
          var boundary = "wsfAjax--------------" + (new Date).getTime();      
          new Ajax.Request(document.location.href, {
                asynchronous: true,
                method: 'post',
                encoding: null, // default is UTF-8 which as appended to contentType, but this is an error for multipart
                contentType: "multipart/form-data; boundary=" + boundary,
                postBody: this._buildMessage(postParams, boundary), 
                wsfSendAsBinary: true,
                onSuccess: this.onSuccess.bind(this, fnOnSuccess, fnOnFailure),
                onFailure: this.onFailure.bind(this, fnOnFailure)
             });
      }
      else {      
          new Ajax.Request(document.location.href, {
                asynchronous: true,
                method: 'post',
                parameters: postParams,
                onSuccess: this.onSuccess.bind(this, fnOnSuccess, fnOnFailure),
                onFailure: this.onFailure.bind(this, fnOnFailure)
             });
      }
    }
    else
    {
      $('wsfState').value = postParams.wsfState; 
      $('wsfForm').submit();   
      this.done();      
    }
}
                         
/*******************************************************************************/
Wsf_Web_CallServerMethodHandler._instance = null;
Wsf_Web_CallServerMethodHandler._queue = [];

/**
* From http://www.webtoolkit.info/javascript-utf8.html
*/
Wsf_Web_CallServerMethodHandler._encodeUtf8 = function (string) 
{
//    string = string.replace(/\r\n/g,"\n");
    var utftext = "";

    for (var n=0, m=string.length; n < m; n++) 
    {
        var c = string.charCodeAt(n);

        if (c < 128) {
            utftext += String.fromCharCode(c);
        }
        else if((c > 127) && (c < 2048)) {
            utftext += String.fromCharCode((c >> 6) | 192);
            utftext += String.fromCharCode((c & 63) | 128);
        }
        else {
            utftext += String.fromCharCode((c >> 12) | 224);
            utftext += String.fromCharCode(((c >> 6) & 63) | 128);
            utftext += String.fromCharCode((c & 63) | 128);
        }

    }

    return utftext;
}

/*******************************************************************************/
Wsf_Web_CallServerMethodHandler.prototype._buildMessage = function(postParams, boundary) 
{
    var parts = [];

    for (var name in postParams)
    {
        var part  = "";
        var param = postParams[name];
        
        if (typeof(param) == "object")
        {
            if (Object.isArray(param)) {
                alert("Wsf_Web_CallServerMethodHandler.prototype._buildMessage: NOT IMPLEMENTED");
            }
            else {
                part += 'Content-Disposition: form-data; ';
                part += 'name="' + name + '"; ';
                part += 'filename="'+ Wsf_Web_CallServerMethodHandler._encodeUtf8(param.filename) + '"' + "\r\n";
                part += "Content-Type: application/octet-stream" + "\r\n\r\n";
                part += param.fileObject.getAsBinary() + "\r\n";
            }
        } else {
            part += 'Content-Disposition: form-data; ';
            part += 'name="' + name + '"' + "\r\n\r\n";
            //part += "Content-Type: text/plain; charset=UTF-8" + "\r\n\r\n";
            part += Wsf_Web_CallServerMethodHandler._encodeUtf8(param) + "\r\n";
        }

        parts.push(part);
    };

    var request = "--" + boundary + "\r\n";
    request += parts.join("--" + boundary + "\r\n");
    request += "--" + boundary + "--" + "\r\n";

    return request;
}
 
/**
* Parameters can be passed as an object of named parameters or an array of unnamed parameters.
* NOTE: You may not alter the control tree until method call has finished.
*/
Wsf_Web_CallServerMethodHandler.invoke = function (ctrl, method, params, fnOnSuccess, fnOnFailure, async)
{
   if (Wsf_Web_CallServerMethodHandler._instance)
   {
      Wsf_Web_CallServerMethodHandler._queue.push([ctrl, method, params, fnOnSuccess, fnOnFailure, async]);
      return;
   }
   
   Wsf_Web_CallServerMethodHandler._instance = new Wsf_Web_CallServerMethodHandler(ctrl, method, params, fnOnSuccess, fnOnFailure, async);
   return true; 
}

/*******************************************************************************/
Wsf_Web_CallServerMethodHandler.prototype.done = function ()
{
   var e = $('wsfAjaxStatus');
   e.parentNode.removeChild(e);
   e = $('wsfDisableUiOverlay');
   e.parentNode.removeChild(e);

   Wsf_Web_CallServerMethodHandler._instance = null;
   
   if (Wsf_Web_CallServerMethodHandler._queue.length) 
   {
      var p = Wsf_Web_CallServerMethodHandler._queue.shift();
      new Wsf_Web_CallServerMethodHandler(p[0], p[1], p[2], p[3], p[4], p[5]);
   }
}

/*******************************************************************************/
Wsf_Web_CallServerMethodHandler.prototype.onFailure = function (fnOnFailure, transport)
{
   alert(W('Wsf_Web.AJAX_SERVER_ERROR') + (DEBUG ? "\n\n"+transport.responseText : ''));
   if (fnOnFailure) fnOnFailure();
   this.done();   
}   
   
/*******************************************************************************/
Wsf_Web_CallServerMethodHandler.prototype.onSuccess = function (fnOnSuccess, fnOnFailure, transport)
{
   try {
      // If PHP failed, transport.responseText is invalid and usually contains a PHP error message (in debug mode).
      var response = transport.responseText.evalJSON();
   }
   catch (e) {
      this.onFailure(fnOnFailure, transport);
      return;
   }
   
   try
   {
      g_app._stopUpdateUI();
      
      g_app._nextCtrlId = response.nextCtrlId;
      g_app._nextServerCtrlId = response.nextCtrlId;
      g_app._deletedServerCtrls = [];
      
      // Load style sheets
      
      if (response.styleSheet) Wsf.requireStylesheet(response.styleSheet);
      
      // Load scripts

      for (var i=0,n=response.scripts.length; i < n; ++i)
      {
         var s = response.scripts[i];
         if (s.charAt(0) == '/' || s.substr(0,4) == 'http')
            Wsf.requireScript(s);
         else
            eval(s);
      }
      
      // Deleted controls
      
      for (var i=0,n=response.deletedCtrls.length; i < n; ++i) {
         var c = g_app.getCtrlById(response.deletedCtrls[i]);
         if (c) c.deleteCtrl();
      }  

      // 1. Add/update markup to the DOM
      
      if (response.renderedSubtrees !== undefined)
      {
         for (var id in response.renderedSubtrees)
         {
            var s = response.renderedSubtrees[id];
            var o = g_app.getCtrlById(id);
            
            if (o)
               o.getElem().replace(s.html);
            else {
               // TODO: insert before the right child
               var p = g_app.getCtrlById(s.pid);
               p._getChildrenContainerElem().insert(s.html);
            }
         }
      }
      
      // 2a) Create new controls
      
      if (response.newCtrls !== undefined)
      {
         eval('var ctrls = ' + response.newCtrls);

         for (var id in ctrls) {           
            g_app._registerCtrl(id, ctrls[id][0]);
         }
         
         for (var id in ctrls) {           
            g_app.getCtrlById(id).createFromServerState(ctrls[id][1]);
         }
      }
      
      // 2b) Load state of updated controls
      // Must be called after all new controls have been created

      if (response.ctrlsState !== undefined)
      {
         for (var id in response.ctrlsState) {
            var o = g_app.getCtrlById(id);
            o._loadState(response.ctrlsState[id]);
         }
      }

      // 4) MakeStateSnapshot() for all controls
      
      g_app.getRootCtrl()._makeStateSnapshot();
      
      // 5) .initFromMarkup(), OnCreate() for all rendered controls

      if (response.renderedSubtrees !== undefined)
      {
         for (var id in response.renderedSubtrees) {
            var c = g_app.getCtrlById(id);
            c.initFromMarkup();
         }
         for (var id in response.renderedSubtrees) {
            g_app.getCtrlById(id).onCreate();
         }
      }
      
      // 6) OnStateChanged() for updated controls
      
      if (response.ctrlsState !== undefined)
      {
         for (var id in response.ctrlsState) {
            var o = g_app.getCtrlById(id);
            o.onStateChanged();
         }
      }

      // Done
      
      g_app.getRootCtrl().onSize();  // force reflow
      if (fnOnSuccess) fnOnSuccess(Wsf_Web_MVC_ControlStateCoder.DecodeStateVariable(response.returnValue));
      g_app._startUpdateUI();
      this.done();   
   }
   
   catch (e) {
      alert(W('Wsf_Web.AJAX_FATAL_ERROR') + (DEBUG ? "\r\n\r\n"+e+"\r\n\r\n"+e.stack : ""));
      g_app.refresh();
   }
}

// script.aculo.us scriptaculous.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009

// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// For details, see the script.aculo.us web site: http://script.aculo.us/

var Scriptaculous = {
  Version: '1.8.3',
  require: function(libraryName) {
    try{
      // inserting via DOM fails in Safari 2.0, so brute force approach
      document.write('<script type="text/javascript" src="'+libraryName+'"><\/script>');
    } catch(e) {
      // for xhtml+xml served content, fall back to DOM methods
      var script = document.createElement('script');
      script.type = 'text/javascript';
      script.src = libraryName;
      document.getElementsByTagName('head')[0].appendChild(script);
    }
  },
  REQUIRED_PROTOTYPE: '1.6.0.3',
  load: function() {
    function convertVersionString(versionString) {
      var v = versionString.replace(/_.*|\./g, '');
      v = parseInt(v + '0'.times(4-v.length));
      return versionString.indexOf('_') > -1 ? v-1 : v;
    }

    if((typeof Prototype=='undefined') ||
       (typeof Element == 'undefined') ||
       (typeof Element.Methods=='undefined') ||
       (convertVersionString(Prototype.Version) <
        convertVersionString(Scriptaculous.REQUIRED_PROTOTYPE)))
       throw("script.aculo.us requires the Prototype JavaScript framework >= " +
        Scriptaculous.REQUIRED_PROTOTYPE);

    var js = /scriptaculous\.js(\?.*)?$/;
    $$('head script[src]').findAll(function(s) {
      return s.src.match(js);
    }).each(function(s) {
      var path = s.src.replace(js, ''),
      includes = s.src.match(/\?.*load=([a-z,]*)/);
      (includes ? includes[1] : 'builder,effects,dragdrop,controls,slider,sound').split(',').each(
       function(include) { Scriptaculous.require(path+include+'.js') });
    });
  }
};

Scriptaculous.load();
// script.aculo.us effects.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009

// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// Contributors:
//  Justin Palmer (http://encytemedia.com/)
//  Mark Pilgrim (http://diveintomark.org/)
//  Martin Bialasinki
//
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/

// converts rgb() and #xxx to #xxxxxx format,
// returns self (or first argument) if not convertable
String.prototype.parseColor = function() {
  var color = '#';
  if (this.slice(0,4) == 'rgb(') {
    var cols = this.slice(4,this.length-1).split(',');
    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
  } else {
    if (this.slice(0,1) == '#') {
      if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
      if (this.length==7) color = this.toLowerCase();
    }
  }
  return (color.length==7 ? color : (arguments[0] || this));
};

/*--------------------------------------------------------------------------*/

Element.collectTextNodes = function(element) {
  return $A($(element).childNodes).collect( function(node) {
    return (node.nodeType==3 ? node.nodeValue :
      (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
  }).flatten().join('');
};

Element.collectTextNodesIgnoreClass = function(element, className) {
  return $A($(element).childNodes).collect( function(node) {
    return (node.nodeType==3 ? node.nodeValue :
      ((node.hasChildNodes() && !Element.hasClassName(node,className)) ?
        Element.collectTextNodesIgnoreClass(node, className) : ''));
  }).flatten().join('');
};

Element.setContentZoom = function(element, percent) {
  element = $(element);
  element.setStyle({fontSize: (percent/100) + 'em'});
  if (Prototype.Browser.WebKit) window.scrollBy(0,0);
  return element;
};

Element.getInlineOpacity = function(element){
  return $(element).style.opacity || '';
};

Element.forceRerendering = function(element) {
  try {
    element = $(element);
    var n = document.createTextNode(' ');
    element.appendChild(n);
    element.removeChild(n);
  } catch(e) { }
};

/*--------------------------------------------------------------------------*/

var Effect = {
  _elementDoesNotExistError: {
    name: 'ElementDoesNotExistError',
    message: 'The specified DOM element does not exist, but is required for this effect to operate'
  },
  Transitions: {
    linear: Prototype.K,
    sinoidal: function(pos) {
      return (-Math.cos(pos*Math.PI)/2) + .5;
    },
    reverse: function(pos) {
      return 1-pos;
    },
    flicker: function(pos) {
      var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4;
      return pos > 1 ? 1 : pos;
    },
    wobble: function(pos) {
      return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5;
    },
    pulse: function(pos, pulses) {
      return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5;
    },
    spring: function(pos) {
      return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));
    },
    none: function(pos) {
      return 0;
    },
    full: function(pos) {
      return 1;
    }
  },
  DefaultOptions: {
    duration:   1.0,   // seconds
    fps:        100,   // 100= assume 66fps max.
    sync:       false, // true for combining
    from:       0.0,
    to:         1.0,
    delay:      0.0,
    queue:      'parallel'
  },
  tagifyText: function(element) {
    var tagifyStyle = 'position:relative';
    if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';

    element = $(element);
    $A(element.childNodes).each( function(child) {
      if (child.nodeType==3) {
        child.nodeValue.toArray().each( function(character) {
          element.insertBefore(
            new Element('span', {style: tagifyStyle}).update(
              character == ' ' ? String.fromCharCode(160) : character),
              child);
        });
        Element.remove(child);
      }
    });
  },
  multiple: function(element, effect) {
    var elements;
    if (((typeof element == 'object') ||
        Object.isFunction(element)) &&
       (element.length))
      elements = element;
    else
      elements = $(element).childNodes;

    var options = Object.extend({
      speed: 0.1,
      delay: 0.0
    }, arguments[2] || { });
    var masterDelay = options.delay;

    $A(elements).each( function(element, index) {
      new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
    });
  },
  PAIRS: {
    'slide':  ['SlideDown','SlideUp'],
    'blind':  ['BlindDown','BlindUp'],
    'appear': ['Appear','Fade']
  },
  toggle: function(element, effect, options) {
    element = $(element);
    effect  = (effect || 'appear').toLowerCase();
    
    return Effect[ Effect.PAIRS[ effect ][ element.visible() ? 1 : 0 ] ](element, Object.extend({
      queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
    }, options || {}));
  }
};

Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;

/* ------------- core effects ------------- */

Effect.ScopedQueue = Class.create(Enumerable, {
  initialize: function() {
    this.effects  = [];
    this.interval = null;
  },
  _each: function(iterator) {
    this.effects._each(iterator);
  },
  add: function(effect) {
    var timestamp = new Date().getTime();

    var position = Object.isString(effect.options.queue) ?
      effect.options.queue : effect.options.queue.position;

    switch(position) {
      case 'front':
        // move unstarted effects after this effect
        this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
            e.startOn  += effect.finishOn;
            e.finishOn += effect.finishOn;
          });
        break;
      case 'with-last':
        timestamp = this.effects.pluck('startOn').max() || timestamp;
        break;
      case 'end':
        // start effect after last queued effect has finished
        timestamp = this.effects.pluck('finishOn').max() || timestamp;
        break;
    }

    effect.startOn  += timestamp;
    effect.finishOn += timestamp;

    if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
      this.effects.push(effect);

    if (!this.interval)
      this.interval = setInterval(this.loop.bind(this), 15);
  },
  remove: function(effect) {
    this.effects = this.effects.reject(function(e) { return e==effect });
    if (this.effects.length == 0) {
      clearInterval(this.interval);
      this.interval = null;
    }
  },
  loop: function() {
    var timePos = new Date().getTime();
    for(var i=0, len=this.effects.length;i<len;i++)
      this.effects[i] && this.effects[i].loop(timePos);
  }
});

Effect.Queues = {
  instances: $H(),
  get: function(queueName) {
    if (!Object.isString(queueName)) return queueName;

    return this.instances.get(queueName) ||
      this.instances.set(queueName, new Effect.ScopedQueue());
  }
};
Effect.Queue = Effect.Queues.get('global');

Effect.Base = Class.create({
  position: null,
  start: function(options) {
    if (options && options.transition === false) options.transition = Effect.Transitions.linear;
    this.options      = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { });
    this.currentFrame = 0;
    this.state        = 'idle';
    this.startOn      = this.options.delay*1000;
    this.finishOn     = this.startOn+(this.options.duration*1000);
    this.fromToDelta  = this.options.to-this.options.from;
    this.totalTime    = this.finishOn-this.startOn;
    this.totalFrames  = this.options.fps*this.options.duration;

    this.render = (function() {
      function dispatch(effect, eventName) {
        if (effect.options[eventName + 'Internal'])
          effect.options[eventName + 'Internal'](effect);
        if (effect.options[eventName])
          effect.options[eventName](effect);
      }

      return function(pos) {
        if (this.state === "idle") {
          this.state = "running";
          dispatch(this, 'beforeSetup');
          if (this.setup) this.setup();
          dispatch(this, 'afterSetup');
        }
        if (this.state === "running") {
          pos = (this.options.transition(pos) * this.fromToDelta) + this.options.from;
          this.position = pos;
          dispatch(this, 'beforeUpdate');
          if (this.update) this.update(pos);
          dispatch(this, 'afterUpdate');
        }
      };
    })();

    this.event('beforeStart');
    if (!this.options.sync)
      Effect.Queues.get(Object.isString(this.options.queue) ?
        'global' : this.options.queue.scope).add(this);
  },
  loop: function(timePos) {
    if (timePos >= this.startOn) {
      if (timePos >= this.finishOn) {
        this.render(1.0);
        this.cancel();
        this.event('beforeFinish');
        if (this.finish) this.finish();
        this.event('afterFinish');
        return;
      }
      var pos   = (timePos - this.startOn) / this.totalTime,
          frame = (pos * this.totalFrames).round();
      if (frame > this.currentFrame) {
        this.render(pos);
        this.currentFrame = frame;
      }
    }
  },
  cancel: function() {
    if (!this.options.sync)
      Effect.Queues.get(Object.isString(this.options.queue) ?
        'global' : this.options.queue.scope).remove(this);
    this.state = 'finished';
  },
  event: function(eventName) {
    if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
    if (this.options[eventName]) this.options[eventName](this);
  },
  inspect: function() {
    var data = $H();
    for(property in this)
      if (!Object.isFunction(this[property])) data.set(property, this[property]);
    return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
  }
});

Effect.Parallel = Class.create(Effect.Base, {
  initialize: function(effects) {
    this.effects = effects || [];
    this.start(arguments[1]);
  },
  update: function(position) {
    this.effects.invoke('render', position);
  },
  finish: function(position) {
    this.effects.each( function(effect) {
      effect.render(1.0);
      effect.cancel();
      effect.event('beforeFinish');
      if (effect.finish) effect.finish(position);
      effect.event('afterFinish');
    });
  }
});

Effect.Tween = Class.create(Effect.Base, {
  initialize: function(object, from, to) {
    object = Object.isString(object) ? $(object) : object;
    var args = $A(arguments), method = args.last(),
      options = args.length == 5 ? args[3] : null;
    this.method = Object.isFunction(method) ? method.bind(object) :
      Object.isFunction(object[method]) ? object[method].bind(object) :
      function(value) { object[method] = value };
    this.start(Object.extend({ from: from, to: to }, options || { }));
  },
  update: function(position) {
    this.method(position);
  }
});

Effect.Event = Class.create(Effect.Base, {
  initialize: function() {
    this.start(Object.extend({ duration: 0 }, arguments[0] || { }));
  },
  update: Prototype.emptyFunction
});

Effect.Opacity = Class.create(Effect.Base, {
  initialize: function(element) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    // make this work on IE on elements without 'layout'
    if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
      this.element.setStyle({zoom: 1});
    var options = Object.extend({
      from: this.element.getOpacity() || 0.0,
      to:   1.0
    }, arguments[1] || { });
    this.start(options);
  },
  update: function(position) {
    this.element.setOpacity(position);
  }
});

Effect.Move = Class.create(Effect.Base, {
  initialize: function(element) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({
      x:    0,
      y:    0,
      mode: 'relative'
    }, arguments[1] || { });
    this.start(options);
  },
  setup: function() {
    this.element.makePositioned();
    this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
    this.originalTop  = parseFloat(this.element.getStyle('top')  || '0');
    if (this.options.mode == 'absolute') {
      this.options.x = this.options.x - this.originalLeft;
      this.options.y = this.options.y - this.originalTop;
    }
  },
  update: function(position) {
    this.element.setStyle({
      left: (this.options.x  * position + this.originalLeft).round() + 'px',
      top:  (this.options.y  * position + this.originalTop).round()  + 'px'
    });
  }
});

// for backwards compatibility
Effect.MoveBy = function(element, toTop, toLeft) {
  return new Effect.Move(element,
    Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));
};

Effect.Scale = Class.create(Effect.Base, {
  initialize: function(element, percent) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({
      scaleX: true,
      scaleY: true,
      scaleContent: true,
      scaleFromCenter: false,
      scaleMode: 'box',        // 'box' or 'contents' or { } with provided values
      scaleFrom: 100.0,
      scaleTo:   percent
    }, arguments[2] || { });
    this.start(options);
  },
  setup: function() {
    this.restoreAfterFinish = this.options.restoreAfterFinish || false;
    this.elementPositioning = this.element.getStyle('position');

    this.originalStyle = { };
    ['top','left','width','height','fontSize'].each( function(k) {
      this.originalStyle[k] = this.element.style[k];
    }.bind(this));

    this.originalTop  = this.element.offsetTop;
    this.originalLeft = this.element.offsetLeft;

    var fontSize = this.element.getStyle('font-size') || '100%';
    ['em','px','%','pt'].each( function(fontSizeType) {
      if (fontSize.indexOf(fontSizeType)>0) {
        this.fontSize     = parseFloat(fontSize);
        this.fontSizeType = fontSizeType;
      }
    }.bind(this));

    this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;

    this.dims = null;
    if (this.options.scaleMode=='box')
      this.dims = [this.element.offsetHeight, this.element.offsetWidth];
    if (/^content/.test(this.options.scaleMode))
      this.dims = [this.element.scrollHeight, this.element.scrollWidth];
    if (!this.dims)
      this.dims = [this.options.scaleMode.originalHeight,
                   this.options.scaleMode.originalWidth];
  },
  update: function(position) {
    var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
    if (this.options.scaleContent && this.fontSize)
      this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
    this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
  },
  finish: function(position) {
    if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
  },
  setDimensions: function(height, width) {
    var d = { };
    if (this.options.scaleX) d.width = width.round() + 'px';
    if (this.options.scaleY) d.height = height.round() + 'px';
    if (this.options.scaleFromCenter) {
      var topd  = (height - this.dims[0])/2;
      var leftd = (width  - this.dims[1])/2;
      if (this.elementPositioning == 'absolute') {
        if (this.options.scaleY) d.top = this.originalTop-topd + 'px';
        if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
      } else {
        if (this.options.scaleY) d.top = -topd + 'px';
        if (this.options.scaleX) d.left = -leftd + 'px';
      }
    }
    this.element.setStyle(d);
  }
});

Effect.Highlight = Class.create(Effect.Base, {
  initialize: function(element) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { });
    this.start(options);
  },
  setup: function() {
    // Prevent executing on elements not in the layout flow
    if (this.element.getStyle('display')=='none') { this.cancel(); return; }
    // Disable background image during the effect
    this.oldStyle = { };
    if (!this.options.keepBackgroundImage) {
      this.oldStyle.backgroundImage = this.element.getStyle('background-image');
      this.element.setStyle({backgroundImage: 'none'});
    }
    if (!this.options.endcolor)
      this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
    if (!this.options.restorecolor)
      this.options.restorecolor = this.element.getStyle('background-color');
    // init color calculations
    this._base  = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
    this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
  },
  update: function(position) {
    this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
      return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) });
  },
  finish: function() {
    this.element.setStyle(Object.extend(this.oldStyle, {
      backgroundColor: this.options.restorecolor
    }));
  }
});

Effect.ScrollTo = function(element) {
  var options = arguments[1] || { },
  scrollOffsets = document.viewport.getScrollOffsets(),
  elementOffsets = $(element).cumulativeOffset();

  if (options.offset) elementOffsets[1] += options.offset;

  return new Effect.Tween(null,
    scrollOffsets.top,
    elementOffsets[1],
    options,
    function(p){ scrollTo(scrollOffsets.left, p.round()); }
  );
};

/* ------------- combination effects ------------- */

Effect.Fade = function(element) {
  element = $(element);
  var oldOpacity = element.getInlineOpacity();
  var options = Object.extend({
    from: element.getOpacity() || 1.0,
    to:   0.0,
    afterFinishInternal: function(effect) {
      if (effect.options.to!=0) return;
      effect.element.hide().setStyle({opacity: oldOpacity});
    }
  }, arguments[1] || { });
  return new Effect.Opacity(element,options);
};

Effect.Appear = function(element) {
  element = $(element);
  var options = Object.extend({
  from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
  to:   1.0,
  // force Safari to render floated elements properly
  afterFinishInternal: function(effect) {
    effect.element.forceRerendering();
  },
  beforeSetup: function(effect) {
    effect.element.setOpacity(effect.options.from).show();
  }}, arguments[1] || { });
  return new Effect.Opacity(element,options);
};

Effect.Puff = function(element) {
  element = $(element);
  var oldStyle = {
    opacity: element.getInlineOpacity(),
    position: element.getStyle('position'),
    top:  element.style.top,
    left: element.style.left,
    width: element.style.width,
    height: element.style.height
  };
  return new Effect.Parallel(
   [ new Effect.Scale(element, 200,
      { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
     new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
     Object.extend({ duration: 1.0,
      beforeSetupInternal: function(effect) {
        Position.absolutize(effect.effects[0].element);
      },
      afterFinishInternal: function(effect) {
         effect.effects[0].element.hide().setStyle(oldStyle); }
     }, arguments[1] || { })
   );
};

Effect.BlindUp = function(element) {
  element = $(element);
  element.makeClipping();
  return new Effect.Scale(element, 0,
    Object.extend({ scaleContent: false,
      scaleX: false,
      restoreAfterFinish: true,
      afterFinishInternal: function(effect) {
        effect.element.hide().undoClipping();
      }
    }, arguments[1] || { })
  );
};

Effect.BlindDown = function(element) {
  element = $(element);
  var elementDimensions = element.getDimensions();
  return new Effect.Scale(element, 100, Object.extend({
    scaleContent: false,
    scaleX: false,
    scaleFrom: 0,
    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
    restoreAfterFinish: true,
    afterSetup: function(effect) {
      effect.element.makeClipping().setStyle({height: '0px'}).show();
    },
    afterFinishInternal: function(effect) {
      effect.element.undoClipping();
    }
  }, arguments[1] || { }));
};

Effect.SwitchOff = function(element) {
  element = $(element);
  var oldOpacity = element.getInlineOpacity();
  return new Effect.Appear(element, Object.extend({
    duration: 0.4,
    from: 0,
    transition: Effect.Transitions.flicker,
    afterFinishInternal: function(effect) {
      new Effect.Scale(effect.element, 1, {
        duration: 0.3, scaleFromCenter: true,
        scaleX: false, scaleContent: false, restoreAfterFinish: true,
        beforeSetup: function(effect) {
          effect.element.makePositioned().makeClipping();
        },
        afterFinishInternal: function(effect) {
          effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
        }
      });
    }
  }, arguments[1] || { }));
};

Effect.DropOut = function(element) {
  element = $(element);
  var oldStyle = {
    top: element.getStyle('top'),
    left: element.getStyle('left'),
    opacity: element.getInlineOpacity() };
  return new Effect.Parallel(
    [ new Effect.Move(element, {x: 0, y: 100, sync: true }),
      new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
    Object.extend(
      { duration: 0.5,
        beforeSetup: function(effect) {
          effect.effects[0].element.makePositioned();
        },
        afterFinishInternal: function(effect) {
          effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
        }
      }, arguments[1] || { }));
};

Effect.Shake = function(element) {
  element = $(element);
  var options = Object.extend({
    distance: 20,
    duration: 0.5
  }, arguments[1] || {});
  var distance = parseFloat(options.distance);
  var split = parseFloat(options.duration) / 10.0;
  var oldStyle = {
    top: element.getStyle('top'),
    left: element.getStyle('left') };
    return new Effect.Move(element,
      { x:  distance, y: 0, duration: split, afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) {
        effect.element.undoPositioned().setStyle(oldStyle);
  }}); }}); }}); }}); }}); }});
};

Effect.SlideDown = function(element) {
  element = $(element).cleanWhitespace();
  // SlideDown need to have the content of the element wrapped in a container element with fixed height!
  var oldInnerBottom = element.down().getStyle('bottom');
  var elementDimensions = element.getDimensions();
  return new Effect.Scale(element, 100, Object.extend({
    scaleContent: false,
    scaleX: false,
    scaleFrom: window.opera ? 0 : 1,
    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
    restoreAfterFinish: true,
    afterSetup: function(effect) {
      effect.element.makePositioned();
      effect.element.down().makePositioned();
      if (window.opera) effect.element.setStyle({top: ''});
      effect.element.makeClipping().setStyle({height: '0px'}).show();
    },
    afterUpdateInternal: function(effect) {
      effect.element.down().setStyle({bottom:
        (effect.dims[0] - effect.element.clientHeight) + 'px' });
    },
    afterFinishInternal: function(effect) {
      effect.element.undoClipping().undoPositioned();
      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
    }, arguments[1] || { })
  );
};

Effect.SlideUp = function(element) {
  element = $(element).cleanWhitespace();
  var oldInnerBottom = element.down().getStyle('bottom');
  var elementDimensions = element.getDimensions();
  return new Effect.Scale(element, window.opera ? 0 : 1,
   Object.extend({ scaleContent: false,
    scaleX: false,
    scaleMode: 'box',
    scaleFrom: 100,
    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
    restoreAfterFinish: true,
    afterSetup: function(effect) {
      effect.element.makePositioned();
      effect.element.down().makePositioned();
      if (window.opera) effect.element.setStyle({top: ''});
      effect.element.makeClipping().show();
    },
    afterUpdateInternal: function(effect) {
      effect.element.down().setStyle({bottom:
        (effect.dims[0] - effect.element.clientHeight) + 'px' });
    },
    afterFinishInternal: function(effect) {
      effect.element.hide().undoClipping().undoPositioned();
      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom});
    }
   }, arguments[1] || { })
  );
};

// Bug in opera makes the TD containing this element expand for a instance after finish
Effect.Squish = function(element) {
  return new Effect.Scale(element, window.opera ? 1 : 0, {
    restoreAfterFinish: true,
    beforeSetup: function(effect) {
      effect.element.makeClipping();
    },
    afterFinishInternal: function(effect) {
      effect.element.hide().undoClipping();
    }
  });
};

Effect.Grow = function(element) {
  element = $(element);
  var options = Object.extend({
    direction: 'center',
    moveTransition: Effect.Transitions.sinoidal,
    scaleTransition: Effect.Transitions.sinoidal,
    opacityTransition: Effect.Transitions.full
  }, arguments[1] || { });
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    height: element.style.height,
    width: element.style.width,
    opacity: element.getInlineOpacity() };

  var dims = element.getDimensions();
  var initialMoveX, initialMoveY;
  var moveX, moveY;

  switch (options.direction) {
    case 'top-left':
      initialMoveX = initialMoveY = moveX = moveY = 0;
      break;
    case 'top-right':
      initialMoveX = dims.width;
      initialMoveY = moveY = 0;
      moveX = -dims.width;
      break;
    case 'bottom-left':
      initialMoveX = moveX = 0;
      initialMoveY = dims.height;
      moveY = -dims.height;
      break;
    case 'bottom-right':
      initialMoveX = dims.width;
      initialMoveY = dims.height;
      moveX = -dims.width;
      moveY = -dims.height;
      break;
    case 'center':
      initialMoveX = dims.width / 2;
      initialMoveY = dims.height / 2;
      moveX = -dims.width / 2;
      moveY = -dims.height / 2;
      break;
  }

  return new Effect.Move(element, {
    x: initialMoveX,
    y: initialMoveY,
    duration: 0.01,
    beforeSetup: function(effect) {
      effect.element.hide().makeClipping().makePositioned();
    },
    afterFinishInternal: function(effect) {
      new Effect.Parallel(
        [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
          new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
          new Effect.Scale(effect.element, 100, {
            scaleMode: { originalHeight: dims.height, originalWidth: dims.width },
            sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
        ], Object.extend({
             beforeSetup: function(effect) {
               effect.effects[0].element.setStyle({height: '0px'}).show();
             },
             afterFinishInternal: function(effect) {
               effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle);
             }
           }, options)
      );
    }
  });
};

Effect.Shrink = function(element) {
  element = $(element);
  var options = Object.extend({
    direction: 'center',
    moveTransition: Effect.Transitions.sinoidal,
    scaleTransition: Effect.Transitions.sinoidal,
    opacityTransition: Effect.Transitions.none
  }, arguments[1] || { });
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    height: element.style.height,
    width: element.style.width,
    opacity: element.getInlineOpacity() };

  var dims = element.getDimensions();
  var moveX, moveY;

  switch (options.direction) {
    case 'top-left':
      moveX = moveY = 0;
      break;
    case 'top-right':
      moveX = dims.width;
      moveY = 0;
      break;
    case 'bottom-left':
      moveX = 0;
      moveY = dims.height;
      break;
    case 'bottom-right':
      moveX = dims.width;
      moveY = dims.height;
      break;
    case 'center':
      moveX = dims.width / 2;
      moveY = dims.height / 2;
      break;
  }

  return new Effect.Parallel(
    [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
      new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
      new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
    ], Object.extend({
         beforeStartInternal: function(effect) {
           effect.effects[0].element.makePositioned().makeClipping();
         },
         afterFinishInternal: function(effect) {
           effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
       }, options)
  );
};

Effect.Pulsate = function(element) {
  element = $(element);
  var options    = arguments[1] || { },
    oldOpacity = element.getInlineOpacity(),
    transition = options.transition || Effect.Transitions.linear,
    reverser   = function(pos){
      return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5);
    };

  return new Effect.Opacity(element,
    Object.extend(Object.extend({  duration: 2.0, from: 0,
      afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
    }, options), {transition: reverser}));
};

Effect.Fold = function(element) {
  element = $(element);
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    width: element.style.width,
    height: element.style.height };
  element.makeClipping();
  return new Effect.Scale(element, 5, Object.extend({
    scaleContent: false,
    scaleX: false,
    afterFinishInternal: function(effect) {
    new Effect.Scale(element, 1, {
      scaleContent: false,
      scaleY: false,
      afterFinishInternal: function(effect) {
        effect.element.hide().undoClipping().setStyle(oldStyle);
      } });
  }}, arguments[1] || { }));
};

Effect.Morph = Class.create(Effect.Base, {
  initialize: function(element) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({
      style: { }
    }, arguments[1] || { });

    if (!Object.isString(options.style)) this.style = $H(options.style);
    else {
      if (options.style.include(':'))
        this.style = options.style.parseStyle();
      else {
        this.element.addClassName(options.style);
        this.style = $H(this.element.getStyles());
        this.element.removeClassName(options.style);
        var css = this.element.getStyles();
        this.style = this.style.reject(function(style) {
          return style.value == css[style.key];
        });
        options.afterFinishInternal = function(effect) {
          effect.element.addClassName(effect.options.style);
          effect.transforms.each(function(transform) {
            effect.element.style[transform.style] = '';
          });
        };
      }
    }
    this.start(options);
  },

  setup: function(){
    function parseColor(color){
      if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
      color = color.parseColor();
      return $R(0,2).map(function(i){
        return parseInt( color.slice(i*2+1,i*2+3), 16 );
      });
    }
    this.transforms = this.style.map(function(pair){
      var property = pair[0], value = pair[1], unit = null;

      if (value.parseColor('#zzzzzz') != '#zzzzzz') {
        value = value.parseColor();
        unit  = 'color';
      } else if (property == 'opacity') {
        value = parseFloat(value);
        if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
          this.element.setStyle({zoom: 1});
      } else if (Element.CSS_LENGTH.test(value)) {
          var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
          value = parseFloat(components[1]);
          unit = (components.length == 3) ? components[2] : null;
      }

      var originalValue = this.element.getStyle(property);
      return {
        style: property.camelize(),
        originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0),
        targetValue: unit=='color' ? parseColor(value) : value,
        unit: unit
      };
    }.bind(this)).reject(function(transform){
      return (
        (transform.originalValue == transform.targetValue) ||
        (
          transform.unit != 'color' &&
          (isNaN(transform.originalValue) || isNaN(transform.targetValue))
        )
      );
    });
  },
  update: function(position) {
    var style = { }, transform, i = this.transforms.length;
    while(i--)
      style[(transform = this.transforms[i]).style] =
        transform.unit=='color' ? '#'+
          (Math.round(transform.originalValue[0]+
            (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
          (Math.round(transform.originalValue[1]+
            (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
          (Math.round(transform.originalValue[2]+
            (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
        (transform.originalValue +
          (transform.targetValue - transform.originalValue) * position).toFixed(3) +
            (transform.unit === null ? '' : transform.unit);
    this.element.setStyle(style, true);
  }
});

Effect.Transform = Class.create({
  initialize: function(tracks){
    this.tracks  = [];
    this.options = arguments[1] || { };
    this.addTracks(tracks);
  },
  addTracks: function(tracks){
    tracks.each(function(track){
      track = $H(track);
      var data = track.values().first();
      this.tracks.push($H({
        ids:     track.keys().first(),
        effect:  Effect.Morph,
        options: { style: data }
      }));
    }.bind(this));
    return this;
  },
  play: function(){
    return new Effect.Parallel(
      this.tracks.map(function(track){
        var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options');
        var elements = [$(ids) || $$(ids)].flatten();
        return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) });
      }).flatten(),
      this.options
    );
  }
});

Element.CSS_PROPERTIES = $w(
  'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' +
  'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
  'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
  'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
  'fontSize fontWeight height left letterSpacing lineHeight ' +
  'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
  'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
  'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
  'right textIndent top width wordSpacing zIndex');

Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;

String.__parseStyleElement = document.createElement('div');
String.prototype.parseStyle = function(){
  var style, styleRules = $H();
  if (Prototype.Browser.WebKit)
    style = new Element('div',{style:this}).style;
  else {
    String.__parseStyleElement.innerHTML = '<div style="' + this + '"></div>';
    style = String.__parseStyleElement.childNodes[0].style;
  }

  Element.CSS_PROPERTIES.each(function(property){
    if (style[property]) styleRules.set(property, style[property]);
  });

  if (Prototype.Browser.IE && this.include('opacity'))
    styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);

  return styleRules;
};

if (document.defaultView && document.defaultView.getComputedStyle) {
  Element.getStyles = function(element) {
    var css = document.defaultView.getComputedStyle($(element), null);
    return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {
      styles[property] = css[property];
      return styles;
    });
  };
} else {
  Element.getStyles = function(element) {
    element = $(element);
    var css = element.currentStyle, styles;
    styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) {
      results[property] = css[property];
      return results;
    });
    if (!styles.opacity) styles.opacity = element.getOpacity();
    return styles;
  };
}

Effect.Methods = {
  morph: function(element, style) {
    element = $(element);
    new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));
    return element;
  },
  visualEffect: function(element, effect, options) {
    element = $(element);
    var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);
    new Effect[klass](element, options);
    return element;
  },
  highlight: function(element, options) {
    element = $(element);
    new Effect.Highlight(element, options);
    return element;
  }
};

$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
  'pulsate shake puff squish switchOff dropOut').each(
  function(effect) {
    Effect.Methods[effect] = function(element, options){
      element = $(element);
      Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
      return element;
    };
  }
);

$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each(
  function(f) { Effect.Methods[f] = Element[f]; }
);

Element.addMethods(Effect.Methods);
window.dhtmlHistory={isIE:false,isOpera:false,isSafari:false,isKonquerer:false,isGecko:false,isSupported:false,create:function(_1){var _2=this;var UA=navigator.userAgent.toLowerCase();var _4=navigator.platform.toLowerCase();var _5=navigator.vendor||"";if(_5==="KDE"){this.isKonqueror=true;this.isSupported=false;}else{if(typeof window.opera!=="undefined"){this.isOpera=true;this.isSupported=true;}else{if(typeof document.all!=="undefined"){this.isIE=true;this.isSupported=true;}else{if(_5.indexOf("Apple Computer, Inc.")>-1){this.isSafari=true;this.isSupported=(_4.indexOf("mac")>-1);}else{if(UA.indexOf("gecko")!=-1){this.isGecko=true;this.isSupported=true;}}}}}window.historyStorage.setup(_1);if(this.isSafari){this.createSafari();}else{if(this.isOpera){this.createOpera();}}var _6=this.getCurrentLocation();this.currentLocation=_6;if(this.isIE){this.createIE(_6);}var _7=function(){_2.firstLoad=null;};this.addEventListener(window,"unload",_7);if(this.isIE){this.ignoreLocationChange=true;}else{if(!historyStorage.hasKey(this.PAGELOADEDSTRING)){this.ignoreLocationChange=true;this.firstLoad=true;historyStorage.put(this.PAGELOADEDSTRING,true);}else{this.ignoreLocationChange=false;this.fireOnNewListener=true;}}var _8=function(){_2.checkLocation();};setInterval(_8,100);},initialize:function(){if(this.isIE){if(!historyStorage.hasKey(this.PAGELOADEDSTRING)){this.fireOnNewListener=false;this.firstLoad=true;historyStorage.put(this.PAGELOADEDSTRING,true);}else{this.fireOnNewListener=true;this.firstLoad=false;}}},addListener:function(_9){this.listener=_9;if(this.fireOnNewListener){this.fireHistoryEvent(this.currentLocation);this.fireOnNewListener=false;}},addEventListener:function(o,e,l){if(o.addEventListener){o.addEventListener(e,l,false);}else{if(o.attachEvent){o.attachEvent("on"+e,function(){l(window.event);});}}},add:function(_d,_e){if(this.isSafari){_d=this.removeHash(_d);historyStorage.put(_d,_e);this.currentLocation=_d;window.location.hash=_d;this.putSafariState(_d);}else{var _f=this;var _10=function(){if(_f.currentWaitTime>0){_f.currentWaitTime=_f.currentWaitTime-_f.waitTime;}_d=_f.removeHash(_d);if(document.getElementById(_d)&&_f.debugMode){var e="Exception: History locations can not have the same value as _any_ IDs that might be in the document,"+" due to a bug in IE; please ask the developer to choose a history location that does not match any HTML"+" IDs in this document. The following ID is already taken and cannot be a location: "+_d;throw new Error(e);}historyStorage.put(_d,_e);_f.ignoreLocationChange=true;_f.ieAtomicLocationChange=true;_f.currentLocation=_d;window.location.hash=_d;if(_f.isIE){_f.iframe.src="/blank.html?"+_d;}_f.ieAtomicLocationChange=false;};window.setTimeout(_10,this.currentWaitTime);this.currentWaitTime=this.currentWaitTime+this.waitTime;}},isFirstLoad:function(){return this.firstLoad;},getVersion:function(){return "0.6";},getCurrentLocation:function(){var r=(this.isSafari?this.getSafariState():this.getCurrentHash());return r;},getCurrentHash:function(){var r=window.location.href;var i=r.indexOf("#");return (i>=0?r.substr(i+1):"");},PAGELOADEDSTRING:"DhtmlHistory_pageLoaded",listener:null,waitTime:200,currentWaitTime:0,currentLocation:null,iframe:null,safariHistoryStartPoint:null,safariStack:null,safariLength:null,ignoreLocationChange:null,fireOnNewListener:null,firstLoad:null,ieAtomicLocationChange:null,createIE:function(_15){this.waitTime=400;var _16=(historyStorage.debugMode?"width: 800px;height:80px;border:1px solid black;":historyStorage.hideStyles);var _17="rshHistoryFrame";var _18="<iframe frameborder=\"0\" id=\""+_17+"\" style=\""+_16+"\" src=\"/blank.html?"+_15+"\"></iframe>";document.write(_18);this.iframe=document.getElementById(_17);},createOpera:function(){this.waitTime=400;var _19="<img src=\"javascript:location.href='javascript:dhtmlHistory.checkLocation();';\" style=\""+historyStorage.hideStyles+"\" />";document.write(_19);},createSafari:function(){var _1a="rshSafariForm";var _1b="rshSafariStack";var _1c="rshSafariLength";var _1d=historyStorage.debugMode?historyStorage.showStyles:historyStorage.hideStyles;var _1e=(historyStorage.debugMode?"width:800px;height:20px;border:1px solid black;margin:0;padding:0;":historyStorage.hideStyles);var _1f="<form id=\""+_1a+"\" style=\""+_1d+"\">"+"<input type=\"text\" style=\""+_1e+"\" id=\""+_1b+"\" value=\"[]\"/>"+"<input type=\"text\" style=\""+_1e+"\" id=\""+_1c+"\" value=\"\"/>"+"</form>";document.write(_1f);this.safariStack=document.getElementById(_1b);this.safariLength=document.getElementById(_1c);if(!historyStorage.hasKey(this.PAGELOADEDSTRING)){this.safariHistoryStartPoint=history.length;this.safariLength.value=this.safariHistoryStartPoint;}else{this.safariHistoryStartPoint=this.safariLength.value;}},getSafariStack:function(){var r=this.safariStack.value;return historyStorage.fromJSON(r);},getSafariState:function(){var _21=this.getSafariStack();var _22=_21[history.length-this.safariHistoryStartPoint-1];return _22;},putSafariState:function(_23){var _24=this.getSafariStack();_24[history.length-this.safariHistoryStartPoint]=_23;this.safariStack.value=historyStorage.toJSON(_24);},fireHistoryEvent:function(_25){var _26=historyStorage.get(_25);this.listener.call(null,_25,_26);},checkLocation:function(){if(!this.isIE&&this.ignoreLocationChange){this.ignoreLocationChange=false;return;}if(!this.isIE&&this.ieAtomicLocationChange){return;}var _27=this.getCurrentLocation();if(_27==this.currentLocation){return;}this.ieAtomicLocationChange=true;if(this.isIE&&this.getIframeHash()!=_27){this.iframe.src="/blank.html?"+_27;}else{if(this.isIE){return;}}this.currentLocation=_27;this.ieAtomicLocationChange=false;this.fireHistoryEvent(_27);},getIframeHash:function(){var doc=this.iframe.contentWindow.document;var _29=String(doc.location.search);if(_29.length==1&&_29.charAt(0)=="?"){_29="";}else{if(_29.length>=2&&_29.charAt(0)=="?"){_29=_29.substring(1);}}return _29;},removeHash:function(_2a){var r;if(_2a===null||_2a===undefined){r=null;}else{if(_2a===""){r="";}else{if(_2a.length==1&&_2a.charAt(0)=="#"){r="";}else{if(_2a.length>1&&_2a.charAt(0)=="#"){r=_2a.substring(1);}else{r=_2a;}}}}return r;},iframeLoaded:function(_2c){if(this.ignoreLocationChange){this.ignoreLocationChange=false;return;}var _2d=String(_2c.search);if(_2d.length==1&&_2d.charAt(0)=="?"){_2d="";}else{if(_2d.length>=2&&_2d.charAt(0)=="?"){_2d=_2d.substring(1);}}window.location.hash=_2d;this.fireHistoryEvent(_2d);}};window.historyStorage={setup:function(_2e){if(typeof _2e!=="undefined"){if(_2e.debugMode){this.debugMode=_2e.debugMode;}if(_2e.toJSON){this.toJSON=_2e.toJSON;}if(_2e.fromJSON){this.fromJSON=_2e.fromJSON;}}var _2f="rshStorageForm";var _30="rshStorageField";var _31=this.debugMode?historyStorage.showStyles:historyStorage.hideStyles;var _32=(historyStorage.debugMode?"width: 800px;height:80px;border:1px solid black;":historyStorage.hideStyles);var _33="<form id=\""+_2f+"\" style=\""+_31+"\">"+"<textarea id=\""+_30+"\" style=\""+_32+"\"></textarea>"+"</form>";document.write(_33);this.storageField=document.getElementById(_30);if(typeof window.opera!=="undefined"){this.storageField.focus();}},put:function(key,_35){this.assertValidKey(key);if(this.hasKey(key)){this.remove(key);}this.storageHash[key]=_35;this.saveHashTable();},get:function(key){this.assertValidKey(key);this.loadHashTable();var _37=this.storageHash[key];if(_37===undefined){_37=null;}return _37;},remove:function(key){this.assertValidKey(key);this.loadHashTable();delete this.storageHash[key];this.saveHashTable();},reset:function(){this.storageField.value="";this.storageHash={};},hasKey:function(key){this.assertValidKey(key);this.loadHashTable();return (typeof this.storageHash[key]!=="undefined");},isValidKey:function(key){return (typeof key==="string");},showStyles:"border:0;margin:0;padding:0;",hideStyles:"left:-1000px;top:-1000px;width:1px;height:1px;border:0;position:absolute;",debugMode:false,storageHash:{},hashLoaded:false,storageField:null,assertValidKey:function(key){var _3c=this.isValidKey(key);if(!_3c&&this.debugMode){throw new Error("Please provide a valid key for window.historyStorage. Invalid key = "+key+".");}},loadHashTable:function(){if(!this.hashLoaded){var _3d=this.storageField.value;if(_3d!==""&&_3d!==null){this.storageHash=this.fromJSON(_3d);this.hashLoaded=true;}}},saveHashTable:function(){this.loadHashTable();var _3e=this.toJSON(this.storageHash);this.storageField.value=_3e;},toJSON:function(o){return o.toJSONString();},fromJSON:function(s){return s.parseJSON();}};
function Wsf_Ctrls_View(p) { Wsf_Ctrls_Control.call(this, p); } Wsf.extend(Wsf_Ctrls_View, Wsf_Ctrls_Control);


/*******************************************************************************/
function Wsf_Ctrls_ViewHtml(serverState)
{
   Wsf_Ctrls_View.call(this, serverState);
   
   // TODO: BUGGY
   Event.observe(document.body, 'activate', this._OnFocus.bindAsEventListener(this));
   
   Wsf_Ctrls_ViewHtml.prototype._instance = this;
}

Wsf.extend(Wsf_Ctrls_ViewHtml, Wsf_Ctrls_View);

/*******************************************************************************/
Wsf_Ctrls_ViewHtml.prototype.onCreate = function ()
{
   Wsf_Ctrls_View.prototype.onCreate.call(this);

   if (this._focusedCtrl) 
   {   
      if (!this._focusedCtrl.focus()) this._focusedCtrl = null;
   }
}

Wsf_Ctrls_ViewHtml.prototype.set_focusedCtrl = function (value)
{
   if (this._focusedCtrl = value) 
   {   
      if (!this._focusedCtrl.focus()) this._focusedCtrl = null;
   }
}

/*******************************************************************************/
Wsf_Ctrls_ViewHtml.prototype._getChildrenContainerElem = function ()
{
   return this.elem.down('form');
}

/*******************************************************************************/
Wsf_Ctrls_ViewHtml.prototype._OnFocus = function (ev)
{
   // BUGGY
   
   // NOTE: IE: Do not pass newly focused controls back to improve focus-stealing resistance :-)
   // Optimize when we know how
   if (Prototype.Browser.IE) this._focusedCtrl = g_app.getCtrlById(ev.element().id);
}

/*******************************************************************************/
Wsf_Ctrls_ViewHtml.prototype.getFocus = function()
{
   return this._focusedCtrl;
}

/*******************************************************************************/
function Wsf_Ctrls_Menu(state)
{
   Wsf_Ctrls_Control.call(this, state);

   this.aItemElems = [];
   this._isAnyItemEnabled = false;
   this.isForceUpdateUIWhenInvisible = false;
}

Wsf.extend(Wsf_Ctrls_Menu, Wsf_Ctrls_Control);

Wsf_Ctrls_Menu.EVENT_ITEM_CLICKED = 1;

/*******************************************************************************/
Wsf_Ctrls_Menu.prototype.initFromMarkup = function ()
{
   Wsf_Ctrls_Control.prototype.initFromMarkup.call(this);  
   this.elem.descendants().each(this._decorateElem.bind(this));
}

/*******************************************************************************/
Wsf_Ctrls_Menu.prototype._decorateElem = function (e)
{
   if (e.tagName != "LI") return;

   this.aItemElems.push(e);

   var a = e.id.split('.');
   e.sCmdId = a.length == 2 ? a[1] : null;

   if (this._isClientExpandMode)
   {
      if (e.sCmdId != '' && e.hasClassName('clickable'))
      {
         e.observe('click', this.onClickItem.bindAsEventListener(this, e));
      } 
      else if (e.hasClassName('expandable'))
      {
         e.observe('click', this.onExpandItem.bindAsEventListener(this, e));
      }

      e.observe('mouseover', this.LI_OnMouseOver.bindAsEventListener(this, e));
      e.observe('mouseout', this.LI_OnMouseOut.bindAsEventListener(this, e));   
   }
}

/*******************************************************************************/
Wsf_Ctrls_Menu.prototype._getControlState = function ()
{
   var a = Wsf_Ctrls_Control.prototype._getControlState.apply(this);

   a.aSelIds = [];
   
   for (var i=this.aItemElems.length; i--;)
   {
      var e = this.aItemElems[i];
      if (e.hasClassName('sel')) a.aSelIds.push(e.id.split('.')[1]);
   }
   
   return a;
}

/*******************************************************************************/
Wsf_Ctrls_Menu.prototype.updateUI = function ()
{
   if (!this._isCmdMode || (!this.isForceUpdateUIWhenInvisible && !this.isVisible())) return;

   var CmdUI = 
   { 
      isEnabled : true,
      isChecked : false,
      enable: function (isEnabled) { this.isEnabled = isEnabled; },
      check: function (isChecked) { this.isChecked = isChecked; }
   };
   
   this._isAnyItemEnabled = false;
                                          
   for (var i=this.aItemElems.length; i--;)
   {
      var e = this.aItemElems[i];
      var sCmdId = e.id.split('.')[1];
      if (!sCmdId) continue;
      CmdUI.isEnabled = true;
      CmdUI.isChecked = false;
      
      g_app.invokeUpdateCmd(this.getCmdTarget(), sCmdId, CmdUI);

      if (CmdUI.isEnabled) {
         this._isAnyItemEnabled = true;
         e.removeClassName('disabled'); 
      }
      else
         e.addClassName('disabled'); 
         
      if (CmdUI.isChecked)
         e.addClassName('checked'); 
      else
         e.removeClassName('checked');          
   }
}

/*******************************************************************************/
Wsf_Ctrls_Menu.prototype.isAnyItemEnabled = function()
{
   return this._isAnyItemEnabled;
}

/*****************************************************************************/
/*
/* SELECTION
/*
/*****************************************************************************/

/*****************************************************************************/
Wsf_Ctrls_Menu.prototype.selectItem = function (id, isSelect)
{
   for (var i=this.aItemElems.length; i--;)
   {
      var e = this.aItemElems[i];
      
      if (id == e.id.split('.')[1])
      {
         if (isSelect)
            e.addClassName('sel');
         else
            e.removeClassName('sel');
         break;
      }
   }
}

/*****************************************************************************/
Wsf_Ctrls_Menu.prototype.getSelId = function ()
{
   for (var i=this.aItemElems.length; i--;)
   {
      var e = this.aItemElems[i];
      
      if (e.hasClassName('sel')) return e.id.split('.')[1];
   }
   
   return null;
}

/*****************************************************************************/
/*
/* EVENTS
/*
/*****************************************************************************/

/*****************************************************************************/
Wsf_Ctrls_Menu.prototype.onClickItem = function (ev, LI)
{
   Event.stop(ev);
   
   if ($(LI).hasClassName("disabled")) return;
   if ($(LI).hasClassName("expandable")) return;
   
   if (this.isContextMenu) this.hideContextMenu();
   
   // Notify

   this.raiseEvent(Wsf_Ctrls_Menu.EVENT_ITEM_CLICKED, { sender: this, id: LI.sCmdId });
   
   if (this._isCmdMode) g_app.invokeCmd(this.getCmdTarget(), LI.sCmdId, {});
}

/*****************************************************************************/
/*
/* CONTEXT MENU
/*
/*****************************************************************************/

/*******************************************************************************/
Wsf_Ctrls_Menu.prototype.trackContextMenu = function(nX, nY)
{
   this.isContextMenu = true;
   this.isWithin = false;
   
   this.elem.style.left = nX + "px";
   this.elem.style.top = nY + "px";
   this.show(true);
   this.focus();

   this.fnTCMOnClickDoc = this.TcmOnClickDoc.bindAsEventListener(this);
   this.fnTCMOnClickMenu = this.TcmOnClickMenu.bindAsEventListener(this);
   this.fnTCMOnKeyDown = this._TcmOnKeyDown.bindAsEventListener(this);
   
   Event.observe(document, "mousedown", this.fnTCMOnClickDoc);
   Event.observe(this.elem, "mousedown", this.fnTCMOnClickMenu);         
   Event.observe(this.elem, "keydown", this.fnTCMOnKeyDown);         
   
   this.updateUI();
}

/*******************************************************************************/
Wsf_Ctrls_Menu.prototype.hideContextMenu = function()
{
   Event.stopObserving(document, "mousedown", this.fnTCMOnClickDoc);
   Event.stopObserving(this.elem, "mousedown", this.fnTCMOnClickMenu);
   Event.stopObserving(this.elem, "keydown", this.fnTCMOnKeyDown);
   this.show(false);
   this.isContextMenu = false;
}

/*****************************************************************************/
Wsf_Ctrls_Menu.prototype.TcmOnClickDoc = function (event)
{
   if (!this.isWithin) this.hideContextMenu();
   this.isWithin = false;
}

/*****************************************************************************/
Wsf_Ctrls_Menu.prototype.TcmOnClickMenu = function (event)
{
   this.isWithin = true;
}

/*******************************************************************************/
Wsf_Ctrls_Menu.prototype._TcmOnKeyDown = function (ev)
{
   if (ev.keyCode == Event.KEY_ESC)
   {
      this.hideContextMenu();
   }
}

/*******************************************************************************/
/*
/* Expanding subitems
/*
/*******************************************************************************/

/*****************************************************************************/
function WsfCtrlMenu_DelayedHide(LI)
{
   var subItemsUl = $(LI).down().next('ul');
   var TitleDIV = $(LI).down();
   TitleDIV.removeClassName("exp");
   subItemsUl.hide();

   window.clearTimeout(subItemsUl.nTimeID);
   subItemsUl.nTimeID = null;
}

/*****************************************************************************/
function WsfCtrlMenu_DelayedShow(LI)
{
   var subItemsUl = $(LI).down().next('ul');
   var TitleDIV = $(LI).down();
   TitleDIV.addClassName("exp");
   subItemsUl.show();

   window.clearTimeout(subItemsUl.nTimeID);
   subItemsUl.nTimeID = null;
}

/*****************************************************************************/
Wsf_Ctrls_Menu.prototype.LI_OnMouseOver = function (ev, LI)
{
   if ((!LI.hasClassName("clickable") && !LI.hasClassName("expandable")) || LI.hasClassName("disabled")) return;

   var subItemsUl = LI.down().next('ul');
   if (!subItemsUl) return;
      
   if (subItemsUl.nTimeID)
   {
      window.clearTimeout(subItemsUl.nTimeID);
      subItemsUl.nTimeID = null;
   }   
   else
   {
      subItemsUl.nTimeID = window.setTimeout(function() { WsfCtrlMenu_DelayedShow(LI); }, 150);
   }
}

/*****************************************************************************/
Wsf_Ctrls_Menu.prototype.onExpandItem = function (ev, li)
{
   var subItemsUl = li.down().next();
   if (subItemsUl.nTimeID)
   {
      window.clearTimeout(subItemsUl.nTimeID);
      subItemsUl.nTimeID = null;
   }   

   WsfCtrlMenu_DelayedShow(li);
}

/*****************************************************************************/
Wsf_Ctrls_Menu.prototype.LI_OnMouseOut = function (ev, LI)
{
   if (LI.hasClassName("disabled")) return;

   var subItemsUl = LI.down().next('ul');
   if (!subItemsUl) return;
   
   if (subItemsUl.nTimeID)
   {
      window.clearTimeout(subItemsUl.nTimeID);
      subItemsUl.nTimeID = null;
   }   
   else
   {
      subItemsUl.nTimeID = window.setTimeout(function() { WsfCtrlMenu_DelayedHide(LI); }, 500);
   }
}

/*******************************************************************************/
function Wsf_UIMods_ImageGallery_CtrlGallery(state)
{
   Wsf_Ctrls_Control.call(this, state);
   this._hideState = 1;
   this.thumbsState = 0;
   this.inOnSize = false;

   if (!Wsf_UIMods_ImageGallery_CtrlGallery._instances) {
      Wsf_UIMods_ImageGallery_CtrlGallery._instances = [];
      window.dhtmlHistory.addListener(Wsf_UIMods_ImageGallery_CtrlGallery.onHistoryChanged);      
   }

   Wsf_UIMods_ImageGallery_CtrlGallery._instances.push(this);
}     

Wsf.extend(Wsf_UIMods_ImageGallery_CtrlGallery, Wsf_Ctrls_Control);

/*******************************************************************************/
Wsf_UIMods_ImageGallery_CtrlGallery.onHistoryChanged = function (newLocation, historyData)
{
   if (newLocation != "gallery")
   {
      for (var i=0; i < Wsf_UIMods_ImageGallery_CtrlGallery._instances.length; ++i)
         Wsf_UIMods_ImageGallery_CtrlGallery._instances[i].show(false);
   }
}

/*******************************************************************************/
Wsf_UIMods_ImageGallery_CtrlGallery.prototype.initFromMarkup = function ()
{
   Wsf_Ctrls_Control.prototype.initFromMarkup.call(this);

   this.hdrOverlayElem = this.elem.down('.hdrPaneOverlay');
   this.hdrOverlayElem.setOpacity(0.8);
   this.posElem = this.elem.down('.currentPos');
   this.titleElem = this.elem.down('.imageTitle');
   
   this.prevImageElem = this.elem.down('.prevImage');
   this.imageElem = this.elem.down('.image');
   this.nextImageElem = this.elem.down('.nextImage');

   this.thumbsPaneElem = this.elem.down('.thumbsPane');
   this.thumbsPaneOverlayElem = this.elem.down('.thumbsPaneOverlay');
   this.prevPageElem = this.thumbsPaneElem.down('.prevPage');
   this.thumbsElem = this.thumbsPaneElem.down('.thumbs');
   this.thumbsTapeElem = this.thumbsElem.down();
   this.nextPageElem = this.thumbsPaneElem.down('.nextPage');

   a = this.thumbsTapeElem.childElements();

   for (var i=0; i < a.length; ++i)
   {
      a[i].observe('click', this.onThumbClicked.bindAsEventListener(this));
   }

   this.prevImageElem.observe('click', this.onPrevImage.bindAsEventListener(this));
   this.prevImageElem.observe('dblclick', this.onPrevImage.bindAsEventListener(this));
   this.nextImageElem.observe('click', this.onNextImage.bindAsEventListener(this));
   this.nextImageElem.observe('dblclick', this.onNextImage.bindAsEventListener(this));
      
   this.prevPageElem.observe('click', this.onPrevPage.bindAsEventListener(this));
   this.prevPageElem.observe('dblclick', this.onPrevPage.bindAsEventListener(this));
   this.nextPageElem.observe('click', this.onNextPage.bindAsEventListener(this));
   this.nextPageElem.observe('dblclick', this.onNextPage.bindAsEventListener(this));

   this.elem.observe('mousemove', this.onMouseMove.bindAsEventListener(this));
   
   this.thumbW = (80+3+3*2);
   this.thumbsPaneH = 60 + 3*2 + 10*2;
   this.curImageIdx = null;
   this.firstImageIdx = null;
   this.thumbsTapeElem.setContentSize(this.thumbW * this.images.length, null);
}                                                                         

/*******************************************************************************/
Wsf_UIMods_ImageGallery_CtrlGallery.prototype.showAndSelectImage = function (idx)
{             
   this.showImage(idx);
   this.show(true);
   this.focus();
}

/*******************************************************************************/
Wsf_UIMods_ImageGallery_CtrlGallery.prototype.showImage = function (idx)
{
   idx = parseInt(idx);
   
   if (this.curImageIdx === idx) return;
   
   if (this.curImageIdx !== null)
   {
      $(this.getId()+".thumb."+this.curImageIdx).removeClassName("selected");
   }
   
   $(this.getId()+".thumb."+idx).addClassName("selected");   
   this._LoadImage(idx);
   this.curImageIdx = idx;

   // Title
   
   var s = this.images[idx].title;
   this.posElem.update('('+(this.curImageIdx+1)+'/'+this.getImageCount()+')'+(s?': ':' '));   
   this.titleElem.update(s);
   
   // Ensure selected is visible
   
   if (this.firstImageIdx === null)
      this._setFirstImage(idx);
   else if (idx < this.firstImageIdx) 
      this._setFirstImage(this.firstImageIdx - (this.firstImageIdx - idx));
   else if (idx >= this.firstImageIdx + this.imagesPerPage)
      this._setFirstImage(this.firstImageIdx + (idx - (this.firstImageIdx + this.imagesPerPage - 1)));
      
   this._UpdateUI();
}

/*******************************************************************************/
Wsf_UIMods_ImageGallery_CtrlGallery.prototype.NextImage = function ()
{
   if (this.curImageIdx < this.images.length-1) this.showImage(this.curImageIdx+1);
}

/*******************************************************************************/
Wsf_UIMods_ImageGallery_CtrlGallery.prototype.PreviousImage = function ()
{
   if (this.curImageIdx > 0) this.showImage(this.curImageIdx-1);
}

/*******************************************************************************/
Wsf_UIMods_ImageGallery_CtrlGallery.prototype.getCurrentImageIdx = function ()
{
   return this.curImageIdx;
}

/*******************************************************************************/
Wsf_UIMods_ImageGallery_CtrlGallery.prototype.getImageCount = function ()
{
   return this.images.length;
}

/*******************************************************************************/
Wsf_UIMods_ImageGallery_CtrlGallery.prototype._LoadImage = function (idx)
{
   // TODO: set image size via styles when the original size is known

   var w = Math.max(Math.min(parseInt(this.imageElem.getWidth() / 20)*20, 1024), 320);
   var h = Math.max(Math.min(parseInt(this.imageElem.getHeight() / 20)*20, 768), 240);
   var s = this.images[idx].imageUrl;
   
   this.imageElem.style.background="url("+s.replace("/0x0/", "/"+w+"x"+h+"/")+") center no-repeat";
}

/*******************************************************************************/
Wsf_UIMods_ImageGallery_CtrlGallery.prototype._UpdateUI = function ()
{
   this.prevImageElem.setOpacity(this.curImageIdx == 0 ? .2 : 1);
   this.nextImageElem.setOpacity(this.curImageIdx == this.images.length-1 ? .2:1);

   var b = this.imagesPerPage >= this.images.length;
   this.prevPageElem.style.visibility = b ? "hidden":"visible";
   this.nextPageElem.style.visibility = b ? "hidden":"visible";
   
   if (!Prototype.Browser.IE6)
   {
      this.prevPageElem.setOpacity(this.firstImageIdx == 0 ? .2 : 1);
      this.nextPageElem.setOpacity(this.firstImageIdx + this.imagesPerPage >= this.images.length ? .2:1);
   }
}
                    
/*******************************************************************************/
Wsf_UIMods_ImageGallery_CtrlGallery.prototype._setFirstImage = function (idx)
{
   if (idx >= this.images.length - this.imagesPerPage) idx = this.images.length - this.imagesPerPage;
   if (idx < 0) idx = 0;

   if (this.firstImageIdx !== idx)
   {    
      new Effect.Move(this.thumbsTapeElem, 
         { x: - idx * this.thumbW, y: 0, mode: 'absolute', transition: Effect.Transitions.sinoidal, duration: .5 }
         );
   }
   
   if (this.lastImagesPerPage !== this.imagesPerPage || this.firstImageIdx !== idx)
   {
      var n = Math.min(idx + this.imagesPerPage + 1, this.images.length);
      
      for (var i=idx; i < n; ++i)
      {
         var e = $(this.getId()+".thumb."+i);
         if (e.style.backgroundImage = "none") e.style.backgroundImage = "url("+this.images[i].thumbUrl+")";
      }
   }
   
   this.firstImageIdx = idx;                 
   this.lastImagesPerPage = this.imagesPerPage;
}

/*******************************************************************************/
/*
/* LAYOUT
/*
/*******************************************************************************/

/*******************************************************************************/
Wsf_UIMods_ImageGallery_CtrlGallery.prototype.RecalcLayout = function ()
{
   var size = document.viewport.getDimensions();
   var w = Math.max(size.width, 320);
   var h = Math.max(size.height, 240);
   
   if (Prototype.Browser.IE6) {
      // IE6 does not support position fixed
      var scroll = document.viewport.getScrollOffsets();
      this.move(scroll.left, scroll.top, w, h);
   } else {
      this.setSize(w, h);
   }
   
   var titlePaneH = 20;
   var buttonW = 20;
   
   var thumbsW = w - buttonW*2;
   this.imagesPerPage = Math.floor(thumbsW/this.thumbW);
   
   // Image
   
   this.imageElem.setContentSize(w - buttonW*2, null);
   
   // Thumbs
   
   var y = h - this.thumbsPaneH;
   
   this.thumbsPaneOverlayElem.move(0, y, null, null);
   this.thumbsPaneElem.move(0, y, null, null);
   this.thumbsElem.setContentSize(thumbsW, null);
 
   // Update stuff
   
   this._LoadImage(this.curImageIdx);
   this._setFirstImage(this.firstImageIdx);   
   this._UpdateUI();   
}

/*******************************************************************************/
Wsf_UIMods_ImageGallery_CtrlGallery.prototype.show = function (show)
{
   if (this.isVisible() == show) return;
   
   if (show)
   {
      this._fadeInCompleted = false;
      
      this.setOpacity(0);
      this.thumbsPaneOverlayElem.setOpacity(0);
      this.thumbsPaneElem.setOpacity(0);
      if (Prototype.Browser.IE)
      {
         // IE bug: hide DIVs with opacite set
         this.thumbsPaneOverlayElem.hide();
         this.thumbsPaneElem.hide();
      }
      this.thumbsState = false;
   
      Wsf_Ctrls_Control.prototype.show.apply(this, [show]);
   
      new Effect.Opacity(this.elem, { duration:0.5, to:1, afterFinish: this._OnAfterShow.bind(this) });
      
      // NOTE: We must observer the document and the the actual gallery DIV because FF does not support focus on DIVs
      this._keyDownCallback = this._OnKeyDown.bindAsEventListener(this);
      document.observe('keydown', this._keyDownCallback);
                                                     
      window.dhtmlHistory.add("gallery", { } );
   }
   else
   {
//      window.dhtmlHistory.add("thumbs", {});
      
      if (this._hideState == 1)
      {             
         if (Prototype.Browser.IE)
         {
            // IE bug: hide DIVs with opacite set
            this.thumbsPaneOverlayElem.hide();
            this.thumbsPaneElem.hide();
         }
                  
         new Effect.Opacity(this.elem, { duration:0.5, from:1, to:0, afterFinish: this._HideAfterFade.bind(this) });

         document.stopObserving('keydown', this._keyDownCallback);
         this._keyDownCallback = null;

         this._hideState = 2;
      }
      else
      {
         
      }
   }   
}

/*******************************************************************************/
Wsf_UIMods_ImageGallery_CtrlGallery.prototype.showThumbs = function (show)
{
   if (show)
   {
      if (this.thumbsState == 0)
      {
         new Effect.Opacity(this.thumbsPaneOverlayElem, { duration:0.5, to:.8 });
         new Effect.Opacity(this.thumbsPaneElem, { duration:0.5, to:1,afterFinish: this._OnThumbsFadeInDone.bind(this) });
         if (Prototype.Browser.IE8) new Effect.Opacity(this.thumbsTapeElem, { duration:0.5, to:1 });
         this.thumbsState = 1;
      }
   }
   else
   {
      if (this.thumbsState == 2)
      {
         new Effect.Opacity(this.thumbsPaneOverlayElem, { duration:0.5, to:0 });
         new Effect.Opacity(this.thumbsPaneElem, { duration:0.5, to:0 });
         if (Prototype.Browser.IE8) new Effect.Opacity(this.thumbsTapeElem, { duration:0.5, to:0 });
         this.thumbsState = 0;
      }
   }
}

/*******************************************************************************/
Wsf_UIMods_ImageGallery_CtrlGallery.prototype._OnThumbsFadeInDone = function (o)
{
   this.thumbsState = 2;
}

/*******************************************************************************/
Wsf_UIMods_ImageGallery_CtrlGallery.prototype._HideAfterFade = function (o)
{
   Wsf_Ctrls_Control.prototype.show.apply(this, [false]);
   this._hideState = 1;           
}

/*******************************************************************************/
Wsf_UIMods_ImageGallery_CtrlGallery.prototype._OnAfterShow = function (o)
{
   if (Prototype.Browser.IE)
   {
      this.thumbsPaneOverlayElem.show();
      this.thumbsPaneElem.show();
   }
   
   this.showThumbs(true);
}

/*******************************************************************************/
/*
/* EVENTS
/*
/*******************************************************************************/
         
/*******************************************************************************/
Wsf_UIMods_ImageGallery_CtrlGallery.prototype.onThumbClicked = function (event)
{
   var idx = Event.findElement(event, 'div').id.split('.')[2];
   this.showImage(parseInt(idx));
   Event.stop(event);
}

/*******************************************************************************/
Wsf_UIMods_ImageGallery_CtrlGallery.prototype.onPrevImage = function (event)
{
   this.PreviousImage();
   Event.stop(event);
}

/*******************************************************************************/
Wsf_UIMods_ImageGallery_CtrlGallery.prototype.onNextImage = function (event)
{
   this.NextImage();
   Event.stop(event);
}

/*******************************************************************************/
Wsf_UIMods_ImageGallery_CtrlGallery.prototype.onPrevPage = function (event)
{                      
   this._setFirstImage(this.firstImageIdx - Math.ceil(this.imagesPerPage/2));
   this._UpdateUI();
   Event.stop(event);
}
                                                 
/*******************************************************************************/
Wsf_UIMods_ImageGallery_CtrlGallery.prototype.onNextPage = function (event)
{
   this._setFirstImage(this.firstImageIdx + Math.ceil(this.imagesPerPage/2));
   this._UpdateUI();
   Event.stop(event);
   
}

/*******************************************************************************/
Wsf_UIMods_ImageGallery_CtrlGallery.prototype.onMouseMove = function (event)
{      
   var a = document.viewport.getScrollOffsets();
   this.showThumbs(Event.pointerY(event)-a.top > this.getOffsetHeight()-this.thumbsPaneH-10);
}

/*******************************************************************************/
Wsf_UIMods_ImageGallery_CtrlGallery.prototype.onSize = function ()
{
   if (!this.isVisible()) return;
   if (this.inOnSize) return;
   this.inOnSize = true;
   this.RecalcLayout();
   this.inOnSize = false;
}


/*******************************************************************************/
Wsf_UIMods_ImageGallery_CtrlGallery.prototype.onCloseWindow = function ()
{
   window.history.back();
   return true;
}

/*******************************************************************************/
Wsf_UIMods_ImageGallery_CtrlGallery.prototype._OnKeyDown = function (ev)
{
   switch (ev.keyCode)
   {
   case Event.KEY_ESC:
      this.onCloseWindow();
      this.show(false);
      Event.stop(ev);
      break;

   case Event.KEY_RIGHT:
   case 32:
      this.NextImage();
      Event.stop(ev);
      break;

   case Event.KEY_LEFT:
      this.PreviousImage();
      Event.stop(ev);
      break;
   }
}

/*******************************************************************************/
function Wsf_Forms_CtrlButton(state)
{
   Wsf_Ctrls_Control.call(this, state);
}

Wsf.extend(Wsf_Forms_CtrlButton, Wsf_Ctrls_Control);

/*******************************************************************************/
Wsf_Forms_CtrlButton.prototype.check = function (isChecked)
{
   if (isChecked)
      this.elem.addClassName('checked');
   else
      this.elem.removeClassName('checked');
}

/*******************************************************************************/
function Wsf_Ctrls_CmdButton(state)
{
    Wsf_Forms_CtrlButton.call(this, state);
}

Wsf.extend(Wsf_Ctrls_CmdButton, Wsf_Forms_CtrlButton);

/*******************************************************************************/
Wsf_Ctrls_CmdButton.prototype.initFromMarkup = function ()
{
    Wsf_Forms_CtrlButton.prototype.initFromMarkup.call(this);

    this.getElem().observe('click', this._onClick.bindAsEventListener(this));
}

/*******************************************************************************/
Wsf_Ctrls_CmdButton.prototype._onClick = function (ev)
{
    ev.stop();
    g_app.invokeCmd(
        this._cmdTarget ? this._cmdTarget : this,
        this.cmdId,
        this.cmdParams ? this.cmdParams : { });
}

/*******************************************************************************/
Wsf_Ctrls_CmdButton.prototype.updateUI = function ()
{
    if (!this.isVisible()) return;

    var cmdui = {
        ctrl : this,
        isEnabled : true,
        isChecked : false,
        enable: function (enable) { this.ctrl.enable(enable); },
        check: function (check) { this.ctrl.check(check); }
    };

    g_app.invokeUpdateCmd(this._cmdTarget ? this._cmdTarget : this, this.cmdId, cmdui);
}

Wsf_Web_Vocabulary.add({"Web" : {"REFERENCE_TITLE" : "Název akce","REFERENCE_PLACE" : "Lokalita","REFERENCE_INVESTOR" : "Investor","REFERENCE_PHOTO" : "Fotografie"},"Wsf_Ctrls" : {"WSF_ROWS_PER_PAGE" : "Řádků na stránce","WSF_PAGE" : "Stránka","WSF_PREV_PAGE" : "« Předchozí","WSF_NEXT_PAGE" : "Další »","WSF_THUMBS" : "Náhledy","SORT_BY" : "Seřadit podle","WSF_LOCATION" : "Umístění","RESULTS" : "Výsledky","OF" : "z","CLOSE_WINDOW" : "Zavřít okno","LOADING" : "Pracuji"},"Wsf" : {"MONTH_1" : "leden","MONTH_2" : "únor","MONTH_3" : "březen","MONTH_4" : "duben","MONTH_5" : "květen","MONTH_6" : "červen","MONTH_7" : "červenec","MONTH_8" : "srpen","MONTH_9" : "září","MONTH_10" : "říjen","MONTH_11" : "listopad","MONTH_12" : "prosinec","MONTH_3LTRS_1" : "Led","MONTH_3LTRS_2" : "Úno","MONTH_3LTRS_3" : "Bře","MONTH_3LTRS_4" : "Dub","MONTH_3LTRS_5" : "Kvě","MONTH_3LTRS_6" : "Čer","MONTH_3LTRS_7" : "Čev","MONTH_3LTRS_8" : "Srp","MONTH_3LTRS_9" : "Zář","MONTH_3LTRS_10" : "Říj","MONTH_3LTRS_11" : "Lis","MONTH_3LTRS_12" : "Pro","DAY2_0" : "Po","DAY2_1" : "Út","DAY2_2" : "St","DAY2_3" : "Čt","DAY2_4" : "Pá","DAY2_5" : "So","DAY2_6" : "Ne","CANCEL" : "Storno","APPLY" : "Použít","INVALID_ARGUMENT" : "Neplatný parametr","INVALID_OPERATION" : "Neplatná operace: v aktuálním stavu nelze operaci provést.","OPERATION_FAILED" : "Akce se nezdařila. Zkuste jí zopakovat.","YES" : "Ano","NO" : "Ne"},"Wsf_UIMods_ImageGallery" : {"CLOSE" : "Zavřít (Esc)","PREV_PHOTO" : "Předchozí fotka (Šipka doleva)","NEXT_PHOTO" : "Následující fotka (Šipka doprava nebo mezerník)","PREV_PAGE" : "Předchozí stránka","NEXT_PAGE" : "Následující stránka"},"Wsf_Forms" : {"WSF_FORM_CAPTCHA" : "Opište kód: <b>%s</b>","WSF_FORM_CAPTCHA_HELP" : "<a href=\"http://cs.wikipedia.org/wiki/CAPTCHA\" target=\"_blank\">Co je to?: Opatření proti automatickým robotům</a>","WSF_FORM_CAPTCHA_INVALID" : "Špatně opsaný kód","WSF_FILEUPLOAD_ERROR" : "Chyba #%d při přijímaní souboru","WSF_FILEUPLOAD_TOO_BIG" : "Soubor je příliš veliký. Maximální velikost je %d KB.","WSF_FILEUPLOAD_BAD_TYPE" : "Nepovolený typ souboru. Povolené jsou pouze tyto typy: %s","WSF_FORM_REQUIRED_FIELDS" : "Pole označená <span class=\"required\">*</span> jsou povinná","WSF_FORM_INVALID_NUMBER" : "Zadejte číslo od <b>%d</b> do <b>%d</b>","WSF_FORM_REQUIRED_FIELD_EMPTY" : "Pole je povinné","WSF_FORM_INCORRECT_SYNTAX" : "Špatný tvar","WSF_FORM_PWD_NOT_SAME" : "Kontrolní heslo není stejné jako nové heslo","WSF_FORM_PWD_UNSAFE" : "Heslo musí mít alespoň 4 znaky","WSF_EMAIL_SENT_CLICK_BACK_TO_RETURN" : "E-mail odeslán. Vraťte se zpět na stránku:<br/><br/><a href=%s>%s</a>","WSF_YOUR_NAME" : "Vaše jméno","WSF_RECEPIENT_EMAIL" : "E-mail příjemce","WSF_MSG" : "Zpráva","WSF_SEND_EMAIL" : "Odeslat odkaz","WSF_PAGE" : "Stránka","INVALID_DATETIME_FORMAT" : "Neplatný formát data (DD.MM.RRRR H:M:S)","INVALID_DATE_FORMAT" : "Neplatný formát data (DD.MM.RRRR)","INVALID_PHONE_NUMBER" : "Neplatné mezinárodní telefonní číslo (+420 123 456 789)","COMBO_NO_SELECTION" : "[žádný výběr]"},"Wsf_Web" : {"OLD_IE_NOTICE" : "Používáte <strong>zastaralý prohlížeč</strong> Internet Explorer %s. Zobrazená <strong>stránka nemusí fungovat správně</strong>.<br/>Požádejte vašeho administrátora o aktualizaci prohlížeče nebo si ji <a href=\"http://www.microsoft.com/cze/windows/internet-explorer/default.aspx\">stáhněte sami</a>.","OLD_FF_NOTICE" : "Používáte <strong>zastaralý prohlížeč</strong> Mozilla Firefox %s. Zobrazená <strong>stránka nemusí fungovat správně</strong>.<br/>Požádejte vašeho administrátora o aktualizaci prohlížeče nebo si ji <a href=\"http://www.getfirefox.com\">stáhněte sami</a>.","AJAX_SERVER_ERROR" : "Chyba při komunikaci se serverem. Zkuste akci opakovat nebo obnovit stránku (klávesa F5).","AJAX_FATAL_ERROR" : "Chyba webové aplikace. Aplikace bude znovu načtena."}});


