//
//                Copyright (c) 2004 Smartlink Corp.
//                       All rights reserved.
//

var VK_SPACE = 50, VK_BACK_SPACE = 51, VK_TAB = 52, VK_DELETE = 53, VK_ENTER = 54;
var VK_CAPS_LOCK = 55, VK_LEFT_SHIFT = 56, VK_RIGHT_SHIFT = 57, VK_ALTGR = 58;
var STATE_NORMAL = 0x0, STATE_SHIFT = 0x1, STATE_CAPS = 0x2, STATE_ALTGR = 0x4;

var KEY_STATES = [
   { key: VK_LEFT_SHIFT,  state: STATE_SHIFT, img: "imgLShift" },
   { key: VK_RIGHT_SHIFT, state: STATE_SHIFT, img: "imgRShift" },
   { key: VK_CAPS_LOCK,   state: STATE_CAPS,  img: "imgCaps"   },
   { key: VK_ALTGR,       state: STATE_ALTGR, img: "imgAltGr"  }
];

var KEY_CHARS = [
   { key: VK_SPACE,      ch: ' '     },
   { key: VK_ENTER,      ch: '\n'    },
   { key: VK_BACK_SPACE, ch: "<bs>"  },
   { key: VK_DELETE,     ch: "<del>" }
];

var KEY_CODES_WIN = [
   192, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 189, 187, 220,
         81, 87, 69, 82, 84, 89, 85, 73, 79, 80, 219, 221,
         65, 83, 68, 70, 71, 72, 74, 75, 76, 186, 222,
        0, 90, 88, 67, 86, 66, 78, 77, 188, 190, 191, 0, 0,
   32 /*space*/, 8 /*bs*/, 9 /*tab*/, 46 /*del*/, 13 /*enter*/,
   20 /*caps*/, 16 /*lshift*/, 16 /*rshift*/, 18 /*alt*/
];
var KEY_CODES_MAC = [
   96, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 45, 61, 0,
         113, 119, 101, 114, 116, 121, 117, 105, 111, 112, 91, 93,
         97, 115, 100, 102, 103, 104, 106, 107, 108, 59, 39,
        0, 122, 120, 99, 118, 98, 110, 109, 44,  46, 47, 0, 0,
   32 /*space*/, 8 /*bs*/, 9 /*tab*/, 127 /*del*/, 13 /*enter*/,
   20 /*caps*/, 16 /*lshift*/, 16 /*rshift*/, 18 /*alt*/
];
var KEY_CODES = navigator.userAgent.indexOf ("Mac") >= 0 ? KEY_CODES_MAC : KEY_CODES_WIN;
var KEY_IDS = null;

var DEAD_CHARS = "\u2500\u2501\u2502\u2503\u2504\u2505\u2506"; // tonos / .. ^ \ ~ dialytika_tonos
var DEAD_TRANS = [
   ":\u2500\u0384:\u03b1\u03ac:\u03b5\u03ad:\u03b9\u03af:\u03bf\u03cc:\u03b7\u03ae:\u03c5\u03cd:\u03c9\u03ce"
   + ":\u0391\u0386:\u0395\u0388:\u0399\u038a:\u039f\u038c:\u0397\u0389:\u03a5\u038e:\u03a9\u038f",
   ":\u2501\xb4:a\xe1:e\xe9:i\xed:o\xf3:u\xfa:y\xfd:A\xc1:E\xc9:I\xcd:O\xd3:U\xda:Y\xdd",
   ":\u2502\xa8:a\xe4:e\xeb:i\xef:o\xf6:u\xfc:y\xff:A\xc4:E\xcb:I\xcf:O\xd6:U\xdc"
   + ":\u03c5\u03cb:\u03b9\u03ca:\u03a5\u03ab:\u0399\u03aa",
   ":\u2503\x5e:a\xe2:e\xea:i\xee:o\xf4:u\xfb:A\xc2:E\xca:I\xce:O\xd4:U\xdb"
   + ":\u0131\xee:\u0130\xce",
   ":\u2504\x60:a\xe0:e\xe8:i\xec:o\xf2:u\xf9:A\xc0:E\xc8:I\xcc:O\xd2:U\xd9",
   ":\u2505\x7e:a\xe3:n\xf1:o\xf5:A\xc3:N\xd1:O\xd5",
   ":\u2506\u0385:\u03c5\u03b0:\u03b9\u0390"
];

var LIGATURES = [ // started from 0xe000
   "\u0638\u064b", "\u0644\u0627", "\u0644\u0625", 
   "\u0644\u0623", "\u0644\u0622", "\u0631\u064a\u0627\u0644"
];

/*layouts*/
var LAYOUTS = [
   { id: "ar", name: "Arabic",
     normal: "\u06301234567890-=\\\u0636\u0635\u062b\u0642\u0641\u063a\u0639\u0647\u062e\u062d\u062c\u062f\u0634\u0633\u064a\u0628\u0644\u0627\u062a\u0646\u0645\u0643\u0637 \u0626\u0621\u0624\u0631\ue001\u0649\u0629\u0648\u0631\u0638",
     shift: "\u0651!@#$%^&*)(_+|\u064e\u064b\u064f\u064c\ue002\u0625\u2018\xf7\xd7\u061b<>\u0650\u064d][\ue003\u0623\u0640\u060c/:\" ~\u0652}{\ue004\u0622\u2019,.\u061f" },
   { id: "bl", name: "Baltic",
     normal: "`\xa9\xae\"$\u20ac\xa3\xb2\xb3\xa7\\-=\xb5\u0105\xe6\u0101\xe4\xe5\u0107\u010d\u0119\u0113\xe9\u0117\u0123\u012f\u012b\u0137\u013c\u0142\u0144\u0146\xf3\u014d\xf5\xf6 \xf8\u0157\u015b\u0161\u0173\u016b\xfc\u017a\u017c\u017e",
     shift: "`\xa9\xae\"$\u20ac\xa3\xb2\xb3\xa7\\-=\xb5\u0104\xc6\u0100\xc4\xc5\u0106\u010c\u0118\u0112\xc9\u0116\u0122\u012e\u012a\u0136\u013b\u0141\u0143\u0145\xd3\u014c\xd5\xd6 \xd8\u0156\u015a\u0160\u0172\u016a\xdc\u0179\u017b\u017d" },
   { id: "ca", name: "Canadian",
     normal: "#1234567890-=<qwertyuiop\u2503\u2505asdfghjkl;\u2504 zxcvbnm,.\xe9",
     shift: "|!\"/$%?&*()_+>QWERTYUIOP\u2503\u2502ASDFGHJKL:\u2504 ZXCVBNM'.\xc9",
     caps: "#1234567890-=<QWERTYUIOP\u2503\u2505ASDFGHJKL;\u2504 ZXCVBNM,.\xc9" },
   { id: "ce", name: "Centr.Eur.",
     normal: "`\xa9\xae\"$\u20ac\xa3\xb2\xb3\xa7\u0105\xe1\xe2\u0103\xe4\u0107\xe7\u010d\u010f\u0111\xe9\u0119\xeb\u011b\xed\xee\u013a\u013e\u0142\u0144\u0148\xf3\xf4\u0151\xf6\u0159\u015b \u0161\u015f\u0165\u0163\u016f\xfa\u0171\xfc\xfd\u017c",
     shift: "`\xa9\xae\"$\u20ac\xa3\xb2\xb3\xa7\u0104\xc1\xc2\u0102\xc4\u0106\xc7\u010c\u010e\u0110\xc9\u0118\xcb\u011a\xcd\xce\u0139\u013d\u0141\u0143\u0147\xd3\xd4\u0150\xd6\u0158\u015a \u0160\u015e\u0164\u0162\u016e\xda\u0170\xdc\xdd\u017b" },
   { id: "du", name: "Dutch",
     normal: "@1234567890/\xb0<qwertyuiop\u2502*asdfghjkl+\u2501 zxcvbnm,.-",
     shift: "\xa7!\"#$%&_()'?\u2505>QWERTYUIOP\u2503|ASDFGHJKL\xb1\u2504 ZXCVBNM;:=",
     caps: "@1234567890/\xb0<QWERTYUIOP\u2502*ASDFGHJKL+\u2501 ZXCVBNM,.-" },
   { id: "fa", name: "Farsi",
     normal: "\xf71234567890-=\u067e\u0636\u0635\u062b\u0642\u0641\u063a\u0639\u0647\u062e\u062d\u062c\u0686\u0634\u0633\u06cc\u0628\u0644\u0627\u062a\u0646\u0645\u06a9\u06af \u0638\u0637\u0632\u0631\u0630\u062f\u0626\u0648./",
     shift: "\xd7!@#$%^&*)(_+|\u064b\u064c\u064d\ue005\u060c\u061b,][\\}{\u064e\u064f\u0650\u0651\u06c0\u0622\u0640\xab\xbb:\" \u0629\u064a\u0698\u0624\u0625\u0623\u0621<>\u061f" },
   { id: "fr", name: "French",
     normal: "\xb2&\xe9\"'(-\xe8_\xe7\xe0)=*azertyuiop\u2503$qsdfghjklm\xf9 wxcvbn,;:!",
     shift: " 1234567890\xb0+\xb5AZERTYUIOP\u2502\xa3QSDFGHJKLM% WXCVBN?./\xa7",
     caps: "\xb21234567890\xb0+\xb5AZERTYUIOP\u2502\xa3QSDFGHJKLM% WXCVBN?./\xa7" },
   { id: "gr", name: "German",
     normal: "\u25031234567890\xdf\u2501#qwertzuiop\xfc+asdfghjkl\xf6\xe4 yxcvbnm,.-",
     shift: "\xb0!\"\xa7$%&/()=?\u2504'QWERTZUIOP\xdc*ASDFGHJKL\xd6\xc4 YXCVBNM;:_",
     caps: "\u2502!\"\xa7$%&/()=?\u2501'QWERTZUIOP\xdc*ASDFGHJKL\xd6\xc4 YXCVBNM;:-" },
   { id: "gk", name: "Greek",
     normal: "`1234567890-=\\;\u03c2\u03b5\u03c1\u03c4\u03c5\u03b8\u03b9\u03bf\u03c0[]\u03b1\u03c3\u03b4\u03c6\u03b3\u03b7\u03be\u03ba\u03bb\u2500'<\u03b6\u03c7\u03c8\u03c9\u03b2\u03bd\u03bc,./",
     shift: "~!@#$%^&*()_+|:\u2506\u0395\u03a1\u03a4\u03a5\u0398\u0399\u039f\u03a0{}\u0391\u03a3\u0394\u03a6\u0393\u0397\u039e\u039a\u039b\u2502\">\u0396\u03a7\u03a8\u03a9\u0392\u039d\u039c<>?",
     caps: "`1234567890-=\\;\u03c2\u0395\u03a1\u03a4\u03a5\u0398\u0399\u039f\u03a0[]\u0391\u03a3\u0394\u03a6\u0393\u0397\u039e\u039a\u039b\u2501'<\u0396\u03a7\u03a8\u03a9\u0392\u039d\u039c,./" },
   { id: "he", name: "Hebrew",
     normal: ";1234567890-=\\/'\u05e7\u05e8\u05d0\u05d8\u05d5\u05df\u05dd\u05e4][\u05e9\u05d3\u05d2\u05db\u05e2\u05d9\u05d7\u05dc\u05da\u05e3,\\\u05d6\u05e1\u05d1\u05d4\u05e0\u05de\u05e6\u05ea\u05e5.",
     shift: "~!@#$%^&*()+_|QWERTYUIOP}{ASDFGHJKL:\"|ZXCVBNM><?",
     caps: "\u05b0\u05b1\u05b2\u05b3\u05b4\u05b5\u05b6\u05b7\u05b8\u05c2\u05c1\u05b9\u05bc\u05bb/'\u05e7\u05e8\u05d0\u05d8\u05d5\u05df\u05dd\u05e4][\u05e9\u05d3\u05d2\u05db\u05e2\u05d9\u05d7\u05dc\u05da\u05e3,\\\u05d6\u05e1\u05d1\u05d4\u05e0\u05de\u05e6\u05ea\u05e5." },
   { id: "it", name: "Italian",
     normal: "\\1234567890'\xec\xf9qwertyuiop\xe8+asdfghjkl\xf2\xe0 zxcvbnm,.-",
     shift: "|!\"\xa3$%&/()=?^\xa7QWERTYUIOP\xe9*ASDFGHJKL\xe7\xb0 ZXCVBNM;:_",
     caps: "\\1234567890'\xec\xf9QWERTYUIOP\xe8+ASDFGHJKL\xf2\xe0 ZXCVBNM,.-" },
   { id: "no", name: "Norwegian",
     normal: "|1234567890+\\'qwertyuiop\xe5\u2502asdfghjkl\xf8\xe6<zxcvbnm,.-",
     shift: "\xa7!\"#\xa4%&/()=?\u2504*QWERTYUIOP\xc5\u2503ASDFGHJKL\xd8\xc6>ZXCVBNM;:_",
     caps: "|1234567890+\\'QWERTYUIOP\xc5\u2502ASDFGHJKL\xd8\xc6<ZXCVBNM,.-",
     altgr: "  @\xa3$\u20ac {[]} \u2501   \u20ac        \u2505                   \xb5  " },
   { id: "po", name: "Portuguese",
     normal: "\\1234567890'\xab\u2505qwertyuiop+\u2501asdfghjkl\xe7\xba zxcvbnm,.-",
     shift: "|!\"#$%&/()=?\xbb\u2503QWERTYUIOP*\u2504ASDFGHJKL\xc7\xaa ZXCVBNM;:_",
     caps: "\\1234567890'\xab\u2502QWERTYUIOP+\u2501ASDFGHJKL\xc7\xba ZXCVBNM,.-" },
   { id: "ru", name: "Russian",
     normal: "\u04511234567890-=\\\u0439\u0446\u0443\u043a\u0435\u043d\u0433\u0448\u0449\u0437\u0445\u044a\u0444\u044b\u0432\u0430\u043f\u0440\u043e\u043b\u0434\u0436\u044d \u044f\u0447\u0441\u043c\u0438\u0442\u044c\u0431\u044e.",
     shift: "\u0401!\"\u2116;%:?*()_+/\u0419\u0426\u0423\u041a\u0415\u041d\u0413\u0428\u0429\u0417\u0425\u042a\u0424\u042b\u0412\u0410\u041f\u0420\u041e\u041b\u0414\u0416\u042d \u042f\u0427\u0421\u041c\u0418\u0422\u042c\u0411\u042e,",
     caps: "\u04011234567890-=\\\u0419\u0426\u0423\u041a\u0415\u041d\u0413\u0428\u0429\u0417\u0425\u042a\u0424\u042b\u0412\u0410\u041f\u0420\u041e\u041b\u0414\u0416\u042d \u042f\u0427\u0421\u041c\u0418\u0422\u042c\u0411\u042e." },
   { id: "rt", name: "Russian TR",
     normal: "\u04511234567890-\u044a\u044d\u044f\u0448\u0435\u0440\u0442\u044b\u0443\u0438\u043e\u043f\u044e\u0449\u0430\u0441\u0434\u0444\u0433\u0447\u0439\u043a\u043b\u044c\u0436 \u0437\u0445\u0446\u0432\u0431\u043d\u043c;.=",
     shift: "\u0401\u2116!/\":\xab\xbb?()_\u042a\u042d\u042f\u0428\u0415\u0420\u0422\u042b\u0423\u0418\u041e\u041f\u042e\u0429\u0410\u0421\u0414\u0424\u0413\u0427\u0419\u041a\u041b\u042c\u0416 \u0417\u0425\u0426\u0412\u0411\u041d\u041c',%",
     caps: "\u04011234567890-\u042a\u042d\u042f\u0428\u0415\u0420\u0422\u042b\u0423\u0418\u041e\u041f\u042e\u0429\u0410\u0421\u0414\u0424\u0413\u0427\u0419\u041a\u041b\u042c\u0416 \u0417\u0425\u0426\u0412\u0411\u041d\u041c;.=" },
   { id: "sp", name: "Spanish",
     normal: "|1234567890'\xbf}qwertyuiop\u2501+asdfghjkl\xf1{<zxcvbnm,.-",
     shift: "\xb0!\"#$%&/()=?\xa1]QWERTYUIOP\u2502*ASDFGHJKL\xd1[>ZXCVBNM;:_",
     caps: "|1234567890'\xbf}QWERTYUIOP\u2502+ASDFGHJKL\xd1{<ZXCVBNM,.-",
     altgr: "\\|@#\u2505\u20ac\xac         \u20ac       []         {}" },
   { id: "tr", name: "Turkish",
     normal: "\"1234567890*-\\qwertyu\u0131op\u011f\xfcasdfghjkl\u015fi<zxcvbnm\xf6\xe7.",
     shift: "\xe9!'\u2503+%&/()=?_;QWERTYUIOP\u011e\xdcASDFGHJKL\u015e\u0130>ZXCVBNM\xd6\xc7:",
     caps: "\xe91234567890-=\\QWERTYUIOP\u011e\xdcASDFGHJKL\u015e\u0130<ZXCVBNM\xd6\xc7." },
   { id: "ur", name: "Ukrainian",
     normal: "\u04511234567890-=\u0491\u0439\u0446\u0443\u043a\u0435\u043d\u0433\u0448\u0449\u0437\u0445\u0457\u0444\u0456\u0432\u0430\u043f\u0440\u043e\u043b\u0434\u0436\u0454 \u044f\u0447\u0441\u043c\u0438\u0442\u044c\u0431\u044e.",
     shift: "\u0401!\"\u2116;%:?*()_+\u0490\u0419\u0426\u0423\u041a\u0415\u041d\u0413\u0428\u0429\u0417\u0425\u0407\u0424\u0406\u0412\u0410\u041f\u0420\u041e\u041b\u0414\u0416\u0404 \u042f\u0427\u0421\u041c\u0418\u0422\u042c\u0411\u042e," },
   { id: "ut", name: "Ukrainian TR",
     normal: "\u04511234567890-\u0491\u0454\u044f\u0448\u0435\u0440\u0442\u0456\u0443\u0438\u043e\u043f\u044e\u0449\u0430\u0441\u0434\u0444\u0433\u0447\u0439\u043a\u043b\u044c\u0436 \u0437\u0445\u0446\u0432\u0431\u043d\u043c\u0457.=",
     shift: "\u0401!\"\u2116;%:?*()_\u0490\u0404\u042f\u0428\u0415\u0420\u0422\u0406\u0423\u0418\u041e\u041f\u042e\u0429\u0410\u0421\u0414\u0424\u0413\u0427\u0419\u041a\u041b\u042c\u0416 \u0417\u0425\u0426\u0412\u0411\u041d\u041c\u0407,=" },
   { id: "us", name: "US",
     normal: "`1234567890-=\\qwertyuiop[]asdfghjkl;' zxcvbnm,./",
     shift: "~!@#$%^&*()_+|QWERTYUIOP{}ASDFGHJKL:\" ZXCVBNM<>?",
     caps: "`1234567890-=\\QWERTYUIOP[]ASDFGHJKL;' ZXCVBNM,./" },
   { id: "dv", name: "US Dvorak",
     normal: "`1234567890[]\\',.pyfgcrl/=aoeuidhtns- ;qjkxbmwvz",
     shift: "~!@#$%^&*(){}|\"<>PYFGCRL?+AOEUIDHTNS_ :QJKXBMWVZ",
     caps: "`1234567890[]\\',.PYFGCRL/=AOEUIDHTNS- ;QJKXBMWVZ" },
   { id: "ws", name: "Western",
     normal: "`\xbf\xa1\xa2$\u20ac\xa3\xa5\xaa\xba0\xdf\xb5\\\xe0\xe1\xe2\xe3\xe4\xe5\xe6\u0153\xe7\xf0\xf1\xfe\xe8\xe9\xea\xeb\xec\xed\xee\xef\xfd\xff\xdf \xf2\xf3\xf4\xf5\xf6\xf8\xf9\xfa\xfb\xfc",
     shift: "`\xbf\xa1\xa2$\u20ac\xa3\xa5\xaa\xba0\xdf\xb5\\\xc0\xc1\xc2\xc3\xc4\xc5\xc6\u0152\xc7\xd0\xd1\xfe\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xdd\u0178\xdf \xd2\xd3\xd4\xd5\xd6\xd8\xd9\xda\xdb\xdc" }
];
/*layouts*/

function Keyboard (listener, layout, noKbd) {
   this.listener = listener;
   this.imgLayout = document.getElementById ("layout");
   this.layoutsCtrl = new LayoutsCtrl (document.getElementById ("layouts"), this);
   this.elem = document.getElementById ("virk");
   this.elem.ref = this;
   this.elem.ondragstart = function (e) { return false; }
   if (!noKbd) {
      this.elem.onkeydown = function (e) { this.ref.onKeyDown (e); }
      this.elem.onkeyup   = function (e) { this.ref.onKeyUp (e); }
   }
   this.elem.onmousedown = function (e) { this.ref.onMouseDown (e); }
   this.elem.onmouseup   = function (e) { this.ref.onMouseUp (e); }
   
   // Methods
   this.loadImages = kbdLoadImages;
   this.setLayout = kbdSetLayout;
   this.getLayout = function () { return this.layoutsCtrl.getValue (); };
   this.updateState = kbdUpdateState;
   this.resetDeadChar = kbdResetDeadChar;
   this.translateKey = kbdTranslateKey;
   this.createKey = kbdCreateKey;
   this.adjustSize = kbdAdjustSize;
   
   // Properties
   this.state = STATE_NORMAL;
   this.stateMask = 0;
   this.keys = "";
   this.deadChar = "";
   this.layout = null;
   
   // Events
   this.onKeyDown   = kbdOnKeyDown;
   this.onKeyUp     = kbdOnKeyUp;
   this.onMouseDown = kbdOnMouseDown;
   this.onMouseUp   = kbdOnMouseUp;
   this.onLayoutChanged = function () { this.setLayout (this.layoutsCtrl.getValue ()) }

   this.adjustSize ();
   this.loadImages (); // imgLShift, imgRShift, ...
   this.setLayout (layout);
}

function kbdSetLayout (layout) {
   if (this.layout && this.layout.id == layout) // not changed?
      return;
      
   this.layout = null;
   for (var i = 0; i != LAYOUTS.length; ++i) {
      if (LAYOUTS [i].id == layout) {
         this.layout = LAYOUTS [i];
         break;
      }
   }
   if (this.layout == null)
      this.layout = LAYOUTS [0];
   if (this.layoutsCtrl.getValue () != this.layout.id)
      this.layoutsCtrl.setValue (this.layout.id);
   
   this.deadChar = "";
   this.updateState ();
}

function kbdUpdateState (key, value) {
   for (var i = 0; i != KEY_STATES.length; ++i) {
      if (key == KEY_STATES [i].key) {
         var state = KEY_STATES [i].state;
         if (value > 0)
            this.state |= state;
         else if (value == 0)
            this.state &= ~state;
         else
            this.state ^= state;
         break;
      }
   }

   this.state &= this.stateMask;
   for (var i = 0; i != KEY_STATES.length; ++i) {
      var state = KEY_STATES [i].state;
      var img = KEY_STATES [i].img;
      if (this [img])
         this [img].style.display = (this.state & state ? "block" : "none");
   }

   var state = "normal";
   if (this.layout ["altgr"] && (this.state & STATE_ALTGR))
      state = "altgr";
   else if ((this.state & STATE_SHIFT) && (this.state & STATE_CAPS))
      state = "normal";
   else if (this.state & STATE_CAPS)
      state = (this.layout ["caps"] ? "caps" : "shift");
   else if (this.state & STATE_SHIFT)
      state = "shift";
   if (!this.layout [state])
      state = "normal";
   
   this.keys = this.layout [state];

   // Update images...
   var src = this.layout.id + "/" + state;
   if (this.deadChar)
      src += "-" + DEAD_CHARS.indexOf (this.deadChar);
   src += ".gif";
   if (this.imgLayout.src != src)
      this.imgLayout.src = src;
}

function kbdOnKeyUp (e) {
   if (!e)
      e = event;

   this.imgKey.style.display = "none";
   if (e.type == "mouseup")
      return;
   var key = getKeyIDByCode (e.keyCode);
   if (key <= VK_CAPS_LOCK)
      return;
   // state key was pressed (not VK_CAPS_LOCK)
   this.updateState (key, 0 /* turn off */);
}

function kbdOnKeyDown (e) {
   if (!e)
      e = event;

   var key = -1, isMouse = false;
   if (e.type == "mousedown") {
      isMouse = true;
      var pos = getEventPos (e, this.elem);
      key = getKeyID (pos.x, pos.y);
   }
   else {
      key = getKeyIDByCode (e.keyCode);
      if (e.keyCode == 16 /* DOM_VK_SHIFT */ && (e.altKey || e.ctrlKey)) {
         this.layoutsCtrl.nextLayout ();
         return;
      }
   }
   var keyInfo = getKeyInfo (key);
   if (!keyInfo)
      return;

   var img = this.imgKey;
   img.src        = keyInfo.src;
   img.style.left = keyInfo.x;
   img.style.top  = keyInfo.y;
   img.width      = keyInfo.width;
   img.height     = keyInfo.height;
   img.style.display = "block";

   if (key >= VK_CAPS_LOCK) { // it's a state-key?
      var value = -1; // -1 (change), 0 (off), 1 (on)
      if (key != VK_CAPS_LOCK && !isMouse)
         value = 1;
      this.updateState (key, value);
      this.isMouse = isMouse;
      return;
   }

   var deadChar = this.resetDeadChar ();
   if (deadChar)
      this.updateState ();

   var ch = "";
   if (key < this.keys.length) {
      ch = this.translateKey (deadChar, key);
   }
   else {
      for (var i = 0; i < KEY_CHARS.length; ++i) {
         if (KEY_CHARS [i].key == key) {
            ch = KEY_CHARS [i].ch;
            break;
         }
      }
   }   
   if (ch && this.listener)
      this.listener.onKeyPress (ch);
      
   if ((this.state & (STATE_SHIFT | STATE_ALTGR)) && this.isMouse) {
      this.state &= ~(STATE_SHIFT | STATE_ALTGR);
      this.updateState ();
   }
}

function getEventPos (e, ctrl) {
   return { x: e.clientX, y: e.clientY };
}

function kbdOnMouseDown (e) {
   if (navigator.gecko)
      eventPreventDefault (e);
   var target = getEventTarget (e ? e : event);
   if (target && target != this.layoutsCtrl.ctrl
       && target.parentNode != this.layoutsCtrl.ctrl)
      this.onKeyDown (e);
}

function kbdOnMouseUp (e) {
   if (navigator.gecko)
      eventPreventDefault ();
   var target = getEventTarget (e ? e : event);
   if (target != this.layoutsCtrl.ctrl)
      this.onKeyUp (e);
}

/*keys*/
var KEYS = [
   { key: 0, c: 14, src: "btn", x: 0, y: 0, width: 280, height: 20 },
   { key: VK_BACK_SPACE, c: 1, src: "bs", x: 280, y: 0, width: 20, height: 20 },
   { key: VK_TAB, c: 1, src: "tab", x: 0, y: 20, width: 30, height: 20 },
   { key: 14, c: 12, src: "btn", x: 30, y: 20, width: 240, height: 20 },
   { key: VK_DELETE, c: 1, src: "del", x: 270, y: 20, width: 30, height: 20 },

   { key: 26, c: 11, src: "btn", x: 37, y: 40, width: 220, height: 20 },
   { key: VK_ENTER, c: 1, src: "enter", x: 257, y: 40, width: 43, height: 20 },

   { key: 38, c: 10, src: "btn", x: 46, y: 60, width: 200, height: 20 },

   { key: VK_SPACE, c: 1, src: "space", x: 82, y: 80, width: 114, height: 20 }
];
/*keys*/

function getKeyID (x, y) {
   var keyID = -1;
   for (var i = 0; i < KEYS.length; ++i) {
      var key = KEYS [i];
      if (x >= key.x && x < key.x + key.width &&
          y >= key.y && y < key.y + key.height) {
         keyID = key.key;
         if (key.c > 1)
            keyID += Math.floor ((x - key.x) * key.c / key.width);
         break;
      }
   }
   return keyID;
}

function getKeyInfo (keyID) {
   if (keyID < 0 || keyID > VK_ALTGR)
      return null;
   
   var keyInfo = null;
   for (var i = 0; i < KEYS.length; ++i) {
      var key = KEYS [i];
      if (keyID >= key.key && keyID < key.key + key.c && key.src) {
         keyInfo = { src: key.src, x: key.x, y: key.y,
                     width: key.width, height: key.height };
         keyInfo.width /= key.c;
         keyInfo.x += (keyID - key.key) * keyInfo.width;
         keyInfo.src = keyInfo.src + ".gif";
         break;
      }
   }
   return keyInfo;
}

function getKeyIDByCode (keyCode) {
   if (!KEY_IDS) {
      var keyIDs = new Array (250);
      for (var i = 0; i < keyIDs.length; ++i)
         keyIDs [i] = -1;
      for (var keyID = 0; keyID < KEY_CODES.length; ++keyID)
         keyIDs [KEY_CODES [keyID]] = keyID;
      KEY_IDS = keyIDs;
   }

   if (keyCode < 0 || keyCode >= KEY_IDS.length)
      return -1;
   return KEY_IDS [keyCode];
}

function kbdLoadImages () {
   this.imgKey = document.createElement ("IMG");
   this.imgKey.style.position = "absolute";
   this.imgKey.style.display = "none";
   this.elem.appendChild (this.imgKey);
   
   for (var i = 0; i != KEY_STATES.length; ++i) {
      var keyInfo = getKeyInfo (KEY_STATES [i].key);
      if (!keyInfo)
         continue;
      this.stateMask |= KEY_STATES [i].state;
      this [KEY_STATES [i].img] = this.createKey (keyInfo);
   }
}

function kbdCreateKey (keyInfo) {
   var img = this.imgKey.cloneNode (false);
   img.src        = keyInfo.src;
   img.style.left = keyInfo.x;
   img.style.top  = keyInfo.y;
   img.width      = keyInfo.width;
   img.height     = keyInfo.height;
   this.elem.appendChild (img);
   return img;
}

function kbdResetDeadChar () {
   var deadChar = this.deadChar; this.deadChar = "";
   return deadChar;
}

function kbdTranslateKey (deadChar, key) {
   var ch = this.keys.charAt (key);
   if (ch == ' ')
      return "";

   var isDeadCh = DEAD_CHARS.indexOf (ch);
   if (deadChar) {
      if (isDeadCh >= 0)
         ch = DEAD_TRANS [isDeadCh].charAt (2);
      var index = DEAD_CHARS.indexOf (deadChar);
      var i = DEAD_TRANS [index].indexOf (":" + ch);
      if (i >= 0)
         ch = DEAD_TRANS [index].charAt (i + 2);
      if (i <= 0)
         ch = DEAD_TRANS [index].charAt (2) + ch;
   }
   else {
      var chCode = ch.charCodeAt (0);
      if (isDeadCh >= 0) {
         this.deadChar = ch; ch = "";
         this.updateState ();
      }
      if (chCode >= 0xe000 && chCode < 0xe000 + LIGATURES.length)
         ch = LIGATURES [chCode - 0xe000];
   }
   return ch;
}

function kbdAdjustSize () {
   var cx = this.elem.offsetWidth;
   var cy = this.elem.offsetHeight;
   if (!cx || !cy)
      return;
   if (window.innerWidth) {
      cx -= window.innerWidth;
      cy -= window.innerHeight;
   }
   else if (document.body.clientWidth) {
      cx -= document.body.clientWidth;
      cy -= document.body.clientHeight;
   }
   else
      return;
   if (cx > 0 || cy > 0) {
      // alert ("cx = " + cx + "; cy = " + cy);
      cx = Math.max (cx, 0);
      cy = Math.max (cy, 0);
      if (window.dialogWidth) { // it's dialog?
         window.dialogWidth  = (parseInt (window.dialogWidth)  + cx) + "px";
         window.dialogHeight = (parseInt (window.dialogHeight) + cy) + "px";
      }
      else {
         window.resizeBy (cx, cy);
      }
   }
}

//////////////////////////////////////////////////////////////////////////////
//
// LayoutsCtrl
//

function LayoutsCtrl (ctrl, listener) {
   if (!ctrl)
      ctrl = { value: "", selectedIndex: 0, options: new Array () }
   this.ctrl     = ctrl;
   this.listener = listener;
   this.getValue = function () { return this.ctrl.value; }
   this.setValue = function (value) { this.ctrl.value = value; }
   this.nextLayout = layoutsNext;
   ctrl.listener = listener;
   ctrl.onchange = function () { 
      if (this.listener)
         this.listener.onLayoutChanged ();
      this.parentNode.focus ();
   }
   
   var options = ctrl.options;
   for (var i = 0; i != LAYOUTS.length; ++i) {
      options [i] = new Option;
      options [i].value = LAYOUTS [i].id;
      options [i].text  = LAYOUTS [i].name;
   }
}

function layoutsNext () {
   var index = this.ctrl.selectedIndex + 1;
   if (index >= this.ctrl.options.length)
      index = 0;
   this.ctrl.selectedIndex = index;
   if (!this.ctrl.tagName) // it is not a real control
      this.ctrl.value = this.ctrl.options [index].value;
   if (this.listener)
      this.listener.onLayoutChanged ();
}
