/*
 * 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;
}

// From the book Pro Javascript Design Patterns, page 43.
var Wsf = 
{
   Extend: function (subClass, superClass) {
      var F = function() {};
      F.prototype = superClass.prototype;
      subClass.prototype = new F();
      subClass.prototype.constructor = subClass;
   }
}

var WsfElement = 
{
   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";
   },

   SetSize: function(element,w,h) {
     if (w !== null) element.style.width = w + "px";
     if (h !== null) element.style.height = h + "px";
   }
}

Element.addMethods(WsfElement);
                                                         
/*****************************************************************************/
function CWsf_App(lang)
{
   this.DetectBrowser()

   this.aCtrls = [];
   this.RootCtrl = null;
   this.lang = lang;
}

/*****************************************************************************/
CWsf_App.prototype.OnBodyLoad = function()
{
   this.ImplementMouseCapture();

   // Init controls
   
   this.RootCtrl.Init();
   this.RootCtrl.UpdateUI();   
   
   // Resize
   
   Event.observe(window, 'resize', this._OnResize.bindAsEventListener(this));
   this._OnResize();            
   
   // Start automatic UI update

   new PeriodicalExecuter(this.RootCtrl.UpdateUI.bind(this.RootCtrl), .25);
}

/*****************************************************************************/
CWsf_App.prototype.DetectBrowser = function()
{
   this.IsIE = false;
   this.IsGecko = false;
   this.fBrowserVersion = 0;
   var s = navigator.userAgent.toLowerCase();
   var body = $(document.body);
   
   this.features = { };
   this.features.modalDialog = window.showModalDialog != undefined;
   
   if (s.indexOf("opera")!=-1)
   {
   	// dummy
   }
   else if (s.indexOf("gecko")!=-1)
   {
   	this.IsGecko = true;
      body.addClassName("gecko");
   }
   else if (s.indexOf("msie")!=-1)
   {
      this.IsIE = true;
		var aVersion = navigator.appVersion.split("MSIE")
		this.fBrowserVersion = parseFloat(aVersion[1]);
      body.addClassName("ie");
      body.addClassName("ie"+this.fBrowserVersion);
      this.isIE6 = this.IsIE && this.fBrowserVersion == 6;
   }
}

/*****************************************************************************/
CWsf_App.prototype.ImplementMouseCapture = function()
{
	if (this.IsIE) return;
	
 	var capture = ["click", "mousedown", "mouseup", "mousemove", "mouseover", "mouseout" ]; 

   HTMLElement.prototype.setCapture = function()
   { 
   	if (this._capture != null) return;
   	
		var self = this; 
		var flag = false; 
		
		this._capture = function(e)
		{ 
		   if (flag) return;
		   flag = true; 
		   
		   var event = document.createEvent("MouseEvents"); 
		   
		   event.initMouseEvent(e.type, 
		       e.bubbles, e.cancelable, e.view, e.detail, 
		       e.screenX, e.screenY, e.clientX, e.clientY, 
		       e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, 
		       e.button, e.relatedTarget); 

		   self.dispatchEvent(event); 
		   flag = false; 
		}; 
		
		for (var i=0; i<capture.length; i++) 
		{ 
		   window.addEventListener(capture[i], this._capture, true); 
		} 
	}

   HTMLElement.prototype.releaseCapture = function()
   { 
   	if (this._capture == null) return;
   	
   	for (var i=0; i<capture.length; i++) 
   	{ 
      	window.removeEventListener(capture[i], this._capture, true); 
      } 
      
      this._capture = null; 
   }
}

/*****************************************************************************/
CWsf_App.prototype.SetCookie = function(sName, sValue, IsGlobal)
{
  //date = new Date();
  
  var s = sName + "=" + escape(sValue);
  if (IsGlobal) s += "; path" + "=/";      // to make HTTRACK dizzy ;-)
  // + "; expires=" + date.toGMTString();
  
  document.cookie = s;
}

/*****************************************************************************/
CWsf_App.prototype.GetCookie = function (sName, DefValue)
{
  // cookies are separated by semicolons
  // IE bug: cookies in total can have up-to 4096KB
  var aCookie = document.cookie.split("; ");
  
  for (var i=0; i < aCookie.length; i++)
  {
    // a name/value pair (a crumb) is separated by an equal sign
    var aCrumb = aCookie[i].split("=");
    
    if (sName == aCrumb[0]) return unescape(aCrumb[1]);
  }

  // a cookie with the requested name does not exist
  return DefValue;
}

/*****************************************************************************/
CWsf_App.prototype.GetCookieInt = function (sName, DefValue)
{
	return parseInt(this.GetCookie(sName, DefValue));
}

/*****************************************************************************/
CWsf_App.prototype.Refresh = function ()
{
   window.location.reload();
}

/*****************************************************************************/
CWsf_App.prototype.Navigate = function (url)
{
   window.location.href = url;
}

/*******************************************************************************/
/*
/* WINDOWS
/*
/*******************************************************************************/
    
/*****************************************************************************/
CWsf_App.prototype.OpenPopup = function(url, sName, w, h)
{
  w += 32;
  h += 96;
  var wleft = (screen.width - w) / 2;
  var wtop = (screen.height - h) / 2;
  
  var win = window.open(url,
    sName,
    'width=' + w + ', height=' + h + ', ' +
    'left=' + wleft + ', top=' + wtop + ', ' +
    'location=no, menubar=no, ' +
    'status=no, toolbar=no, scrollbars=yes, resizable=yes');
  
  // Just in case width and height are ignored
  //win.resizeTo(w, h);
  
  // Just in case left and top are ignored
  //win.moveTo(wleft, wtop);
  win.focus();

  return win;
}

/*****************************************************************************/
CWsf_App.prototype.OpenDialog = function(url, mapParams, CallbackCtrl, sCallbackName)
{
   var w = 500;
   var h = 400;
//   w += 32;
//   h += 96;
   var wleft = (screen.width - w) / 2;
   var wtop = (screen.height - h) / 2;

   var winName = hex_md5(url);
   var query = '';
   
   for (k in mapParams)
   {
      query += "&"+k+"="+encodeURIComponent(mapParams[k]);
   }

   if (CallbackCtrl)
   {
      query += "&_callbackCtrlId="+CallbackCtrl.GetId()+"&_callbackName="+sCallbackName;
   }
   
   if (query)
   {
      if (url.indexOf("?") != -1) 
         url += query;
      else {
         url += '?';
         url += query.substring(1);
      }
   }
   
//   if (this.features.modalDialog)
//   {
//      window.showModalDialog(url, null, 
//        'dialogWidth: ' + w + 'px; dialogHeight: ' + h + 'px; ' +
//        'resizable: yes; center: yes'
//        );
//      return null;
//   }
//   else
   {
      var win = window.open(url,
        winName,
        'width=' + w + ', height=' + h + ', ' +
        'left=' + wleft + ', top=' + wtop + ', ' +
        'location=no, menubar=no, status=no, toolbar=no, scrollbars=yes, resizable=yes');

      win.focus();
      return win;
   }
}

/*****************************************************************************/
CWsf_App.prototype.ShowErrorBox = function(sHtml)
{
//   var o = this.GetCtrlById(this.sMsgBoxId);
//   
//   if (o) 
//      o.ShowBox(sHtml, CWsf_Ctrls_MsgBox.TYPE_ERROR, 300);   
//   else
      alert(sHtml);
}

/*****************************************************************************/
CWsf_App.prototype.ShowInfoBox = function(sHtml)
{
//   var o = this.GetCtrlById(this.sMsgBoxId);
//   
//   if (o) 
//      o.ShowBox(sHtml, CWsf_Ctrls_MsgBox.TYPE_INFO, 300);   
//   else
      alert(sHtml);
}

/*******************************************************************************/
/*
/* CONTROLS
/*
/*******************************************************************************/
           
/*****************************************************************************/
CWsf_App.prototype.RegisterCtrl = function (Ctrl)
{
   this.aCtrls[Ctrl.GetId()] = Ctrl;
}

/*****************************************************************************/
CWsf_App.prototype.GetCtrlById = function (sId)
{
   return sId ? this.aCtrls[sId] : null;
}

/*******************************************************************************/
/*
/* CMDS
/*
/*******************************************************************************/

/*******************************************************************************/
CWsf_App.prototype.UpdateUI = function ()
{
   this.RootCtrl.UpdateUI();
}

/*******************************************************************************/
CWsf_App.prototype.InvokeUpdateCmd = function (SourceCtrl, sCmdId, CmdUI)
{
   var sMethod = "OnUpdate"+sCmdId;
   
   var a = SourceCtrl.CmdTarget ? SourceCtrl.CmdTarget : SourceCtrl;
   
   for (; a; a=a.GetParent())
   {
      if (a[sMethod] != undefined)
      {
         a[sMethod](CmdUI);
         break;
      }
      else if (a.OnUpdateCmdUI(sCmdId, CmdUI))
         break;
   }
}

/*******************************************************************************/
CWsf_App.prototype.InvokeCmd = function (SourceCtrl, sCmdId, Params)
{
   // Ensure cmd is enabled

   var CmdUI = { 
      IsEnable: true,
      Enable: function (IsEnable) { this.IsEnable = IsEnable; },
      Check: function (IsChecked) { }
   };
   
   this.InvokeUpdateCmd(SourceCtrl, sCmdId, CmdUI);
   if (!CmdUI.IsEnable) return;
   
   // Handle on client
   
   var sMethod = "On"+sCmdId;
   var cmdTarget = SourceCtrl.CmdTarget ? SourceCtrl.CmdTarget : SourceCtrl;
   
   for (var a=cmdTarget; a; a=a.GetParent())
   {
      if (a[sMethod] != undefined)
      {
         if (a[sMethod](Params)) return;
         break;
      }
      else 
      {
         switch (a.OnCommand(sCmdId, Params))
         {
         case CWsf_Ctrls_Control.CMD_HANDLE_ON_SERVER: a = null; break;
         case CWsf_Ctrls_Control.CMD_HANDLED: return;
         }
      }
      
      if (!a) break;
   }
   
   // Handle on server
   
   var ctrlsState = {};
   g_App.RootCtrl.SaveState(ctrlsState);
   
   var a = { sCmd: sCmdId, Params: Params, sSrcCtrlId: cmdTarget.GetId(), ctrlsState: ctrlsState };
      
   var e = $('wsfFormState');
   e.value = Object.toJSON(a); 
   
   $('wsfForm').submit();         
}

/*******************************************************************************/
/*
/* RPC
/*
/*******************************************************************************/

/*******************************************************************************/
CWsf_App.prototype.DoRPC = function(sClassMethod, Params, fnOnSuccess, fnOnFailure)
{
   var self = this;
   
   var url = '/rpc.php?m='+encodeURIComponent(sClassMethod);
   
   if (Params)
   {
      url += "&p="+encodeURIComponent(Object.toJSON(Params));
      url += "&l="+this.lang;
   }
   
   new Ajax.Request(url,
      {
         method: 'get',
         
         onSuccess: function (transport) { if (fnOnSuccess) fnOnSuccess(transport.responseText ? transport.responseText.evalJSON() : null); },
         
         onFailure: 
            fnOnFailure ? 
               function (transport) { fnOnFailure(transport.responseText ? transport.responseText.evalJSON() : null); } :
               function (transport) { self.ShowErrorBox(transport.responseText ? transport.responseText.evalJSON() : null); }   
      });         
}

/*******************************************************************************/
CWsf_App.prototype.CallServerMethod = function(ctrl, method, params, fnOnSuccess, fnOnFailure)
{
   new Ajax.Request(document.location.href,
      {
         method: 'post',
         
         parameters: { rpc: true, ctrlId: ctrl.GetId(), method: method, params: Object.toJSON(params) },
         
         onSuccess: function (transport) { if (fnOnSuccess) fnOnSuccess(transport.responseText ? transport.responseText.evalJSON() : null); },
         
         onFailure: 
            fnOnFailure ? 
               function (transport) { fnOnFailure(transport.responseText ? transport.responseText.evalJSON() : null); } :
               function (transport) { g_App.ShowErrorBox(transport.responseText ? transport.responseText.evalJSON() : null); }   
      });         
}

/*******************************************************************************/
/*
/* EVENTS
/*
/*******************************************************************************/

CWsf_App.prototype._OnResize = function (ev)
{
   if (this._inResize) return;   // IE calls resize multiple times
   this._inResize = true;
   
   this.RootCtrl.OnSize();

   this._inResize = false;
}

/*******************************************************************************/
function CWsf_Ctrls_Control(svrProp)
{
   // Wake up non-control properties from the server
   
   if (svrProp)
   {
      for (k in svrProp)
      {
         var p = svrProp[k];
         
         if (p !== null && typeof(p) == "object")
         {
            if (p[1] == false) 
            {
               this[k] = p[0];
               p[0] = null;    // free memory, because we do not detect changes in objects/arrays on post back
            }
         }
         else this[k] = p;
      }
   }
   
   // ...
   
   this._parent = g_App.GetCtrlById(svrProp['_parent'] !== null ? svrProp['_parent'][0] : null);
   
   if (!this._parent) 
      g_App.RootCtrl = this;  
   else 
      this._parent.children.push(this);

   // ...
   
   this._svrProp = svrProp;
   this.aClasses = ['CWsf_Ctrls_Control'];
   g_App.RegisterCtrl(this);
   this._width = this._w;
   this._height = this._h;
   this._clientWidth = this._clientHeight = null;
   this._events = [];
   this.children = [];

   this.Elem = $(this._id);
   if (this.Elem) this.Elem.WsfCtrl = this;
   
   if (this._childrenLayout)
      this.Layout = eval('new '+this._childrenLayout+'(this);');
   else
      this.Layout = null;
      
   if (!this._enabled) this.Enable(false);
}

CWsf_Ctrls_Control.prototype.CMD_HANDLED = 1;
CWsf_Ctrls_Control.prototype.CMD_HANDLE_ON_SERVER = 2;
CWsf_Ctrls_Control.prototype.CMD_NOT_HANDLED = 3;

/*******************************************************************************/
CWsf_Ctrls_Control.prototype.Init = function ()
{
   // Wake up control properties from the server
   
   if (this._svrProp)
   {
      for (k in this._svrProp)
      {
         var p = this._svrProp[k];

         if (p !== null && typeof(p) == "object")
         {
            if (p[1] == true) this[k] = g_App.GetCtrlById(p[0]);
         }
      }
   }
   
   // ...
   
   for (var i=0; i < this.children.length; ++i)
   {
      this.children[i].Init();    
   }
}
 
/*******************************************************************************/
CWsf_Ctrls_Control.prototype._AddClass = function (sClass)
{
   this.aClasses.push(sClass);
}

/*******************************************************************************/
CWsf_Ctrls_Control.prototype.IsA = function (sClass)
{
   return this.aClasses.indexOf(sClass) != -1;
}

/*******************************************************************************/
CWsf_Ctrls_Control.prototype.GetAncestorByClass = function (sClass)
{
   for (var o=this; o; o=o._parent)
   {
      if (o.IsA(sClass)) return o;
   }
   return null;
}

/*******************************************************************************/
CWsf_Ctrls_Control.prototype.GetView = function ()
{
   return CWsf_Ctrls_ViewHtml.prototype._instance;
}

/*******************************************************************************/
CWsf_Ctrls_Control.prototype.GetTitle = function ()
{
   return this._title;
}

/*******************************************************************************/
CWsf_Ctrls_Control.prototype.GetId = function ()
{
   return this._id;
}

/*******************************************************************************/
CWsf_Ctrls_Control.prototype.GetParent = function ()
{
   return this._parent;
}

/*******************************************************************************/
CWsf_Ctrls_Control.prototype.GetChildByIdx = function (nIdx)
{
   return this.children[nIdx];
}

/*******************************************************************************/
CWsf_Ctrls_Control.prototype.GetChildCount = function ()
{
   return this.children.length;
}


/*******************************************************************************/
CWsf_Ctrls_Control.prototype.UpdateUI = function ()
{
   for (var i=0; i < this.children.length; ++i)
   {
      this.children[i].UpdateUI();    
   }
}

/*******************************************************************************/
CWsf_Ctrls_Control.prototype.GetForm = function ()
{
   for (var p = this; p && !p.IsA("CWsf_Forms_CtrlForm"); p=p._parent) /* dummy */;
   
   return p;
}

/*******************************************************************************/
CWsf_Ctrls_Control.prototype.Focus = function ()
{
   if (!this.IsVisible() || !this.IsEnabled()) return false;
   
   this.Elem.focus();   
   return true;
}

/*******************************************************************************/
CWsf_Ctrls_Control.prototype.HasFocus = function ()
{
   return this.GetView().GetFocus() == this;
}

/*******************************************************************************/
CWsf_Ctrls_Control.prototype.Enable = function (isEnabled)
{
   if (isEnabled) 
      this.Elem.removeClassName('disabled'); 
   else 
      this.Elem.addClassName('disabled'); 
   
   this.Elem.disabled = !isEnabled;
   this._enabled = isEnabled;
}

/*******************************************************************************/
CWsf_Ctrls_Control.prototype.IsEnabled = function ()
{
   return this._enabled;
}

/*******************************************************************************/
CWsf_Ctrls_Control.prototype.IsEnabled = function ()
{
   return this._enabled;
}

/*******************************************************************************/
CWsf_Ctrls_Control.prototype.IsReadOnly = function ()
{
   return this._readOnly;
}

/*******************************************************************************/
CWsf_Ctrls_Control.prototype.GetStyle = function (style, defValue)
{
   return this._styles && this._styles[style] ? this._styles[style] : defValue;
}

/*******************************************************************************/
CWsf_Ctrls_Control.prototype.GetComputedStyle = function (style, defValue)
{
   switch (style)
   {
   case 'marginTop':
      var n = this.GetStyle('marginTop', 0);
      if (!n) n = this.GetStyle('margin', 0);
      return n;

   case 'marginBottom':
      var n = this.GetStyle('marginBottom', 0);
      if (!n) n = this.GetStyle('margin', 0);
      return n;
      
   default:
      return this.GetStyle(style, defValue);
   }
}

/*******************************************************************************/
CWsf_Ctrls_Control.prototype.CallServerMethod = function (method, params, fnOnSuccess, fnOnFailure)
{
   g_App.CallServerMethod(this, method, params, fnOnSuccess, fnOnFailure); 
}

/*******************************************************************************/
/* 
/* STATE
/*
/*******************************************************************************/

/*******************************************************************************/
CWsf_Ctrls_Control.prototype.SaveState = function (ctrlsState)
{
   var a = this._GetControlState();
   for (k in a) { ctrlsState[this.GetId()] = a; break; }
   
   for (var i=0; i < this.children.length; ++i)
   {
      this.children[i].SaveState(ctrlsState);
   }
}

/*******************************************************************************/
CWsf_Ctrls_Control.prototype._GetControlState = function ()
{
   var a = {};
   
   if (!this._svrProp) return a;

   for (k in this._svrProp)
   {
      var value = this[k];
      
      if (value !== null && typeof(value) == "object")
      {
         if (value.IsA) 
         {
            var ctrlId = value.GetId();
            if (this._svrProp[k] == null || ctrlId != this._svrProp[k][0]) a[k] = [ctrlId, true];
         }
         else a[k] = [value, false];
      }
      else
      {
         if (value != this._svrProp[k]) a[k] = value;
      }
   }

   return a;
}

/*******************************************************************************/
/* 
/* VISIBILITY
/*
/*******************************************************************************/

/*******************************************************************************/
CWsf_Ctrls_Control.prototype.IsVisible = function ()
{
   return this._visible;
}

/*******************************************************************************/
CWsf_Ctrls_Control.prototype.Show = function (isShow)
{
   if (isShow || isShow == undefined) 
   {
      if (!this.IsVisible())
      {
         this.Elem.removeClassName("hidden");
         this._visible = true;
      }
   }
   else 
   {
      this.Elem.addClassName("hidden");
      this._visible = false;
   }

   if (this._parent) this._parent.OnSize();
}

/*******************************************************************************/
CWsf_Ctrls_Control.prototype.Hide = function ()
{
   this.Show(false);
}

/*******************************************************************************/
/* 
/* POSITION & SIZE
/*
/*******************************************************************************/

/*******************************************************************************/
CWsf_Ctrls_Control.prototype._ClearSizeCache = function ()
{
   this._width = null;
   this._height = null;
   this._clientWidth = null;
}

/*******************************************************************************/
// Slow!
CWsf_Ctrls_Control.prototype._CacheWidth = function ()
{
   this._width = this.Elem.offsetWidth;
}

/*******************************************************************************/
// Slow!
CWsf_Ctrls_Control.prototype._CacheHeight = function ()
{
   this._height = this.Elem.offsetHeight;
}

/*******************************************************************************/
CWsf_Ctrls_Control.prototype._CacheClientSize = function ()
{
   this._clientWidth = this.Elem.clientWidth;
   this._clientHeight = this.Elem.clientHeight;
}

/*******************************************************************************/
// content+padding+border
CWsf_Ctrls_Control.prototype.GetSize = function ()
{
   if (this._width === null) this._CacheWidth();
   if (this._height === null) this._CacheHeight();
   return { width: this._width, height: this._height };
}

/*******************************************************************************/
// content+padding+border
CWsf_Ctrls_Control.prototype.GetWidth = function () 
{
   if (this._width === null) this._CacheWidth();
   return this._width;
}

/*******************************************************************************/
// content+padding+border
CWsf_Ctrls_Control.prototype.GetHeight = function () 
{
   if (this._height === null) this._CacheHeight();
   return this._height;
}

/*******************************************************************************/
// content+padding 
CWsf_Ctrls_Control.prototype.GetClientSize = function ()
{
   if (this._clientWidth === null) this._CacheClientSize();
   return { width: this._clientWidth, height: this._clientHeight };
}

/*******************************************************************************/
CWsf_Ctrls_Control.prototype.Move = function (nX,nY,nW,nH)
{
   var s = this.Elem.style;
   
   if (nX < 0) nX = 0;
   if (nY < 0) nY = 0;
   
   s.position = "absolute";
   s.left = nX+"px";
   s.top = nY+"px";
   
   if (nW !== null || nH !==null)
   {
      this.SetSize(nW, nH);
   }
}

/*******************************************************************************/
CWsf_Ctrls_Control.prototype._GetPaddingBorderSizes = function() 
{
   return this._border;

   // NOTE: getComputedStyle() is very slow
//   var s = window.getComputedStyle ? window.getComputedStyle(this.Elem,null) : this.Elem.currentStyle;

//   var 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;
//   
//   var b = [];
//   b[0] = parseInt(s.paddingTop);
//   b[1] = parseInt(s.paddingRight);
//   b[2] = parseInt(s.paddingBottom);
//   b[3] = parseInt(s.paddingLeft);

//   if (isNaN(b[0])) b[0] = 0;
//   if (isNaN(b[1])) b[1] = 0;
//   if (isNaN(b[2])) b[2] = 0;
//   if (isNaN(b[3])) b[3] = 0;
//   
//   a[0] += b[0];
//   a[1] += b[1];
//   a[2] += b[2];
//   a[3] += b[3];

//   return a;
}

/*******************************************************************************/
CWsf_Ctrls_Control.prototype.SetSize = function (nW,nH)
{
   var s = this.Elem.style;
   s.overflow = "auto";
   var b = this._GetPaddingBorderSizes();
   
   if (nW !== null) 
   {
      this._width = nW;
      nW -= b[1] + b[3];
      if (nW < 0) nW = 0;
      s.width = nW + "px";
      this._clientWidth = nW;
   }
   
   if (nH !== null) 
   {
      this._height = nH;
      nH -= b[0] + b[2];
      if (nH < 0) nH = 0;
      s.height = nH  + "px";
      this._clientHeight = nH;
   }

   this.OnSize();
}

/*******************************************************************************/
CWsf_Ctrls_Control.prototype.OnSize = function ()
{
   if (!this.IsVisible()) return;
   
   if (this.Layout) 
   {
      // Custom layout
      this.Elem.style.overflow = "hidden";
      this.Layout.LayoutChildren(this);
   }
   else
   {
      // Default HTML layout of children
      for (var i=0,n=this.children.length; i < n; ++i)
      {
         var o = this.children[i];
         o._ClearSizeCache();
         o.OnSize();
      }   
   }   
}

/*******************************************************************************/
CWsf_Ctrls_Control.prototype.SetZIndex = function (zIndex)
{
   this.Elem.style.zIndex = zIndex;
}

/*******************************************************************************/
CWsf_Ctrls_Control.prototype.MoveBelow = function (ctrl)
{
   this.Elem.style.zIndex = ctrl.Elem.style.zIndex - 1;
}

/*******************************************************************************/
CWsf_Ctrls_Control.prototype.SetOpacity = function (opacityFloat)
{
   this.Elem.setOpacity(opacityFloat);
}

/*******************************************************************************/
/* 
/* CUSTOM EVENTS
/*
/*******************************************************************************/

/*******************************************************************************/
CWsf_Ctrls_Control.prototype.AttachEventHandler = function (eventName, fnCallback)
{
   if (this._events[eventName] == undefined) { this._events[eventName] = []; }
   
   this._events[eventName].push(fnCallback);
}

/*******************************************************************************/
CWsf_Ctrls_Control.prototype.RaiseEvent = function (eventName, eventData)
{
   if (this._events[eventName] == undefined) return;
   
   for (var i=this._events[eventName].length; i--;)
   {
      this._events[eventName][i](eventData);
   }
}

/*******************************************************************************/
/* 
/* COMMANDS
/*
/*******************************************************************************/

/*******************************************************************************/
CWsf_Ctrls_Control.prototype._GetCmdTarget = function ()
{
   return this._cmdTarget ? this._cmdTarget : this;
}

/*******************************************************************************/
CWsf_Ctrls_Control.prototype.OnCommand = function (sCmdId, Params)
{
   return this.CMD_NOT_HANDLED;
}

/*******************************************************************************/
CWsf_Ctrls_Control.prototype.OnUpdateCmdUI = function (sCmdId, CmdUI)
{
   return false;
}

/*******************************************************************************/
function CWsf_Ctrls_LayoutStack(ctrl)
{
   ctrl.Elem.style.overflow = "auto";
   if (g_App.IsIE) ctrl.Elem.style.overflowX = "hidden";
}

/*******************************************************************************/
CWsf_Ctrls_LayoutStack.prototype.LayoutChildren = function (ctrl)
{  
   // FireFox fix: it remembers the largest size of content and shows scrollbars
   // even if the content is shrinked
   
   if (g_App.IsGecko)
   {
      var sOldOverflow = ctrl.Elem.style.overflow;
      ctrl.Elem.style.overflow = 'hidden';
   }
   
   // Get the height of all fixed controls and # of stretch controls
   
   var nFixedH = 0;
   var nStretchCount = 0;
   
   for (var i=ctrl.children.length; i--;)
   {
      var o = ctrl.children[i];
      if (!o.IsVisible()) continue;
      
      if (o.GetStyle('stretch',false))
      {
         ++nStretchCount;
         o.Elem.style.overflow = "auto";
      }
      else
         nFixedH += o.GetHeight() + o.GetComputedStyle('marginTop',0) + o.GetComputedStyle('marginBottom',0);
   }
   
   // Layout

   var padding = ctrl.GetStyle('padding',0);
   var a = ctrl.GetClientSize();
   var w = a['width'] - padding*2;
   var h = a['height'] - padding*2;
   var y = padding;
   var x = padding;
   var nStretchH = Math.max(h - nFixedH, 0);
   
   if (nStretchCount > 0)
   {
      var nAvgStretchH = Math.floor(nStretchH / nStretchCount);
      var nLastStretchH = h - nAvgStretchH*(nStretchCount-1) - nFixedH;
   }
   
   var lastBottomMargin = 0;
   
   for (var i=0,n=ctrl.children.length; i < n; ++i)
   {
      var o = ctrl.children[i];
      if (!o.IsVisible()) continue;
      
      var margin = o.GetStyle('margin',0);
      var topMargin = Math.max(o.GetComputedStyle('marginTop',0) - lastBottomMargin, 0);
      var bottomMargin = o.GetComputedStyle('marginBottom',0);
      lastBottomMargin = bottomMargin;
      
      if (o.GetStyle('stretch',false))
      {
         h = (--nStretchCount ? nAvgStretchH : nLastStretchH);
         o.Move(x+margin, y+topMargin, w-2*margin, h-topMargin-bottomMargin);
      }
      else
      {
         h = o.GetHeight() + topMargin + bottomMargin;
         o.Move(x+margin, y+topMargin, w-2*margin, null);
      }
      
      y += h;
   }   
   
   // Firefox fix: restore overflow
   
   if (g_App.IsGecko)
   {
      ctrl.Elem.style.overflow = sOldOverflow;
   }
}

/*******************************************************************************/
function CWsf_Ctrls_LayoutFullsize(ctrl)
{
}

/*******************************************************************************/
CWsf_Ctrls_LayoutFullsize.prototype.LayoutChildren = function (ctrl)
{  
   // Get control to layout and pass the event to other controls
   
   var o = null;
   
   for (var i=0,n=ctrl.GetChildCount(); i<n; ++i)
   {
      var t = ctrl.GetChildByIdx(i);
      if (t.IsVisible())
      {
         if (t.GetStyle('stretch', false))
         {
            o = t;
         }
         else
         {
            t._ClearSizeCache();
            t.OnSize();
         }
      }
   }
   
   // Make the control fullsize
   
   if (o)
   {
      // FireFox fix: it remembers the largest size of content and shows scrollbars
      // even if the content is shrinked
      
      if (g_App.IsGecko && ctrl.Elem)
      {
         var sOldOverflow = ctrl.Elem.style.overflow;
         ctrl.Elem.style.overflow = 'hidden';
      }     

      // Resize
      
      var m = ctrl.GetStyle('padding',0);
      var a = ctrl.GetClientSize();
      o.Move(m,m, a['width']-m*2, a['height']-m*2);

      // Firefox fix: restore overflow
      
      if (g_App.IsGecko && ctrl.Elem)
      {
         ctrl.Elem.style.overflow = sOldOverflow;
      }
   }
}
/*****************************************************************************/
function CWsf_Web_Vocabulary(vocabulary)
{
   this.vocabulary = vocabulary;
}

/*****************************************************************************/
function W(id)
{
   a = id.split('.');
   return wsfVocabulary.vocabulary[a[0]][a[1]];
}
function CWsf_Ctrls_View(p) { CWsf_Ctrls_Control.apply(this, [p]); } Object.extend(CWsf_Ctrls_View.prototype, CWsf_Ctrls_Control.prototype);

/*******************************************************************************/
function CWsf_Ctrls_ViewHtml(Params)
{
   CWsf_Ctrls_Control.apply(this, [Params]);

   Event.observe(document.body, 'activate', this._OnFocus.bindAsEventListener(this));
   
   CWsf_Ctrls_ViewHtml.prototype._instance = this;
}

Object.extend(CWsf_Ctrls_ViewHtml.prototype, CWsf_Ctrls_Control.prototype);

/*******************************************************************************/
CWsf_Ctrls_ViewHtml.prototype.Init = function ()
{
   CWsf_Ctrls_Control.prototype.Init.apply(this);

   if (this._focusedCtrl) 
   {   
      if (!this._focusedCtrl.Focus()) this._focusedCtrl = null;
   }
}

/*******************************************************************************/
CWsf_Ctrls_ViewHtml.prototype._OnFocus = function (ev)
{
   // NOTE: IE: Do not pass newly focused controls back to improve focus-stealing resistance :-)
   // Optimize when we know how
   if (!g_App.IsIE) this._focusedCtrl = g_App.GetCtrlById(Event.element(ev).id);
}

/*******************************************************************************/
CWsf_Ctrls_ViewHtml.prototype.GetFocus = function()
{
   return this._focusedCtrl;
}

/*******************************************************************************/
function CWsf_Ctrls_Menu(Params)
{
   CWsf_Ctrls_Control.apply(this, [Params]);

   this.aItemElems = [];
   this.Elem.descendants().each(this._DecorateElem.bind(this));
   this.isAnyItemEnabled = false;
   this.isForceUpdateUIWhenInvisible = false;
}

Object.extend(CWsf_Ctrls_Menu.prototype, CWsf_Ctrls_Control.prototype);

/*******************************************************************************/
CWsf_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));
      } 

      e.observe('mouseover', this.LI_OnMouseOver.bindAsEventListener(this, e));
      e.observe('mouseout', this.LI_OnMouseOut.bindAsEventListener(this, e));   
   }
}

/*******************************************************************************/
CWsf_Ctrls_Menu.prototype._GetControlState = function ()
{
   var a = CWsf_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;
}

/*******************************************************************************/
CWsf_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];
      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');          
   }
}

/*******************************************************************************/
CWsf_Ctrls_Menu.prototype.IsAnyItemEnabled = function()
{
   return this.isAnyItemEnabled;
}

/*****************************************************************************/
/*
/* SELECTION
/*
/*****************************************************************************/

/*****************************************************************************/
CWsf_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;
      }
   }
}

/*****************************************************************************/
CWsf_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
/*
/*****************************************************************************/

/*****************************************************************************/
CWsf_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

   if (this.fnOnClickCallback) {
      this.fnOnClickCallback(LI.sCmdId);
   }
   else if (this._isCmdMode) {
      g_App.InvokeCmd(this._GetCmdTarget(), LI.sCmdId, {});
   }
}

/*****************************************************************************/
/*
/* CONTEXT MENU
/*
/*****************************************************************************/

/*******************************************************************************/
CWsf_Ctrls_Menu.prototype.TrackContextMenu = function(nX, nY, fnOnClickCallback)
{
   this.fnOnClickCallback = fnOnClickCallback;
   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.TCM_OnClickDoc.bindAsEventListener(this);
   this.fnTCMOnClickMenu = this.TCM_OnClickMenu.bindAsEventListener(this);
   this.fnTCMOnKeyDown = this._TCM_OnKeyDown.bindAsEventListener(this);
   
   Event.observe(document, "mousedown", this.fnTCMOnClickDoc);
   Event.observe(this.Elem, "mousedown", this.fnTCMOnClickMenu);         
   Event.observe(this.Elem, "keydown", this.fnTCMOnKeyDown);         
   
   this.UpdateUI();
}

/*******************************************************************************/
CWsf_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;
}

/*****************************************************************************/
CWsf_Ctrls_Menu.prototype.TCM_OnClickDoc = function (event)
{
   if (!this.IsWithin) this.HideContextMenu();
   this.IsWithin = false;
}

/*****************************************************************************/
CWsf_Ctrls_Menu.prototype.TCM_OnClickMenu = function (event)
{
   this.IsWithin = true;
}

/*******************************************************************************/
CWsf_Ctrls_Menu.prototype._TCM_OnKeyDown = 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.addClassName("hidden");

   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.removeClassName("hidden");

   window.clearTimeout(SubItemsUL.nTimeID);
   SubItemsUL.nTimeID = null;
}

/*****************************************************************************/
CWsf_Ctrls_Menu.prototype.LI_OnMouseOver = function (ev, LI)
{
   if (!LI.hasClassName("clickable") || LI.hasClassName("disabled")) return;
   LI.addClassName("hover");

   var SubItemsUL = LI.down('ul');
   if (!SubItemsUL) return;
   
   if (SubItemsUL.nTimeID)
   {
      window.clearTimeout(SubItemsUL.nTimeID);
      SubItemsUL.nTimeID = null;
   }   
   else
   {
      SubItemsUL.nTimeID = window.setTimeout(function() { WsfCtrlMenu_DelayedShow(LI); }, 250);
   }
}

/*****************************************************************************/
CWsf_Ctrls_Menu.prototype.LI_OnMouseOut = function (ev, LI)
{
   if (LI.hasClassName("disabled")) return;
   LI.removeClassName("hover");

   var SubItemsUL = LI.down('ul');
   if (!SubItemsUL) return;
   
   if (SubItemsUL.nTimeID)
   {
      window.clearTimeout(SubItemsUL.nTimeID);
      SubItemsUL.nTimeID = null;
   }   
   else
   {
      SubItemsUL.nTimeID = window.setTimeout(function() { WsfCtrlMenu_DelayedHide(LI); }, 500);
   }
}
var wsfVocabulary = new CWsf_Web_Vocabulary({});

