/*
 * JavaScript Virtual Keyboard (popup variant), version 2.3,
 * modified for the StudyLight.org.
 *
 * Copyright (C) 2006-2007 Dmitriy Khudorozhkov
 *
 * This software is provided "as-is", without any express or implied warranty.
 * In no event will the author be held liable for any damages arising from the
 * use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
 *    appreciated but is not required.
 *
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 *
 * 3. This notice may not be removed or altered from any source distribution.
 *
 *  - Dmitriy Khudorozhkov, kh_dmitry2001@mail.ru
 */

function VKeyboard(container_id, callback_ref, font_size, font_color,
                   dead_color, bg_color, key_color, sel_item_color,
                   border_color, inactive_border_color, inactive_key_color,
                   lang_sel_brd_color, show_click, click_font_color,
                   click_bg_color, click_border_color, init_layout)
{
  return this._construct(container_id, callback_ref, font_size,
                         font_color, dead_color, bg_color, key_color,
                         sel_item_color, border_color, inactive_border_color,
                         inactive_key_color, lang_sel_brd_color, show_click,
                         click_font_color, click_bg_color, click_border_color,
                         init_layout);
}

VKeyboard.prototype = {

  kbArray: [],

  _get_event_source: function(event)
  {
    var e = event || window.event;
    return e.srcElement || e.target;
  },

  _setup_event: function(elem, eventType, handler)
  {
    return (elem.attachEvent ? elem.attachEvent("on" + eventType, handler) : ((elem.addEventListener) ? elem.addEventListener(eventType, handler, false) : null));
  },

  _detach_event: function(elem, eventType, handler)
  {
    return (elem.detachEvent ? elem.detachEvent("on" + eventType, handler) : ((elem.removeEventListener) ? elem.removeEventListener(eventType, handler, false) : null));
  },

  _start_flash: function(in_el)
  {
    function getColor(str, posOne, posTwo)
    {
      if(/rgb\((\d+),\s(\d+),\s(\d+)\)/.exec(str)) // try to detect Mozilla-style rgb value.
      {
        switch(posOne)
        {
          case 1: return parseInt(RegExp.$1, 10);
          case 2: return parseInt(RegExp.$2, 10);
          case 3: return parseInt(RegExp.$3, 10);
          default: return 0;
        }
      }
      else // standard (#xxxxxx or #xxx) way
        return str.length == 4 ? parseInt(str.substr(posOne, 1) + str.substr(posOne, 1), 16) : parseInt(str.substr(posTwo, 2), 16);
    }

    function getR(color_string)
    { return getColor(color_string, 1, 1); }

    function getG(color_string)
    { return getColor(color_string, 2, 3); }

    function getB(color_string)
    { return getColor(color_string, 3, 5); }

    var el = in_el.time ? in_el : (in_el.company && in_el.company.time ? in_el.company : null);
    if(el)
    {
      el.time = 0;
      clearInterval(el.timer);
    }

    var vkb = this;
    var ftc = vkb.fontcolor, bgc = vkb.keycolor, brc = vkb.bordercolor;

    // Special fixes for simple/dead/modifier keys:

    if(in_el.dead)
      ftc = vkb.deadcolor;

    if(((in_el.innerHTML == "Shift") && vkb.Shift) || ((in_el.innerHTML == "Caps") && vkb.Caps) ||
       ((in_el.innerHTML == "AltGr") && vkb.AltGr) || ((in_el.innerHTML == "Ctrl") && vkb.Ctrl))
      bgc = vkb.lic;

    // Extract base color values:
    var fr = getR(ftc), fg = getG(ftc), fb = getB(ftc);
    var kr = getR(bgc), kg = getG(bgc), kb = getB(bgc);
    var br = getR(brc), bg = getG(brc), bb = getB(brc);

    // Extract flash color values:
    var f_r = getR(vkb.cfc), f_g = getG(vkb.cfc), f_b = getB(vkb.cfc);
    var k_r = getR(vkb.cbg), k_g = getG(vkb.cbg), k_b = getB(vkb.cbg);
    var b_r = getR(vkb.cbr), b_g = getG(vkb.cbr), b_b = getB(vkb.cbr);

    var _shift_colors = function()
    {
      function dec2hex(dec)
      {
        var hexChars = "0123456789ABCDEF";
        var a = dec % 16;
        var b = (dec - a) / 16;

        return hexChars.charAt(b) + hexChars.charAt(a) + "";
      }

      in_el.time = !in_el.time ? 10 : (in_el.time - 1);

      function calc_color(start, end)
      { return (end - (in_el.time / 10) * (end - start)); }

      var t_f_r = calc_color(f_r, fr), t_f_g = calc_color(f_g, fg), t_f_b = calc_color(f_b, fb);
      var t_k_r = calc_color(k_r, kr), t_k_g = calc_color(k_g, kg), t_k_b = calc_color(k_b, kb);
      var t_b_r = calc_color(b_r, br), t_b_g = calc_color(b_g, bg), t_b_b = calc_color(b_b, bb);

      function setStyles(style)
      {
        style.color = "#" + dec2hex(t_f_r) + dec2hex(t_f_g) + dec2hex(t_f_b);
        style.borderColor = "#" + dec2hex(t_b_r) + dec2hex(t_b_g) + dec2hex(t_b_b);
        style.backgroundColor = "#" + dec2hex(t_k_r) + dec2hex(t_k_g) + dec2hex(t_k_b);
      }

      var first = (in_el == vkb.mod[4]) ? false : true, is = in_el.style, cs = in_el.company ? in_el.company.style : null;

      if(cs && first)
        setStyles(cs);

      setStyles(is);

      if(cs)
      {
        if(!first)
        {
          setStyles(cs);
          is.borderBottomColor = "#" + dec2hex(t_k_r) + dec2hex(t_k_g) + dec2hex(t_k_b);
        }
        else
          cs.borderBottomColor = "#" + dec2hex(t_k_r) + dec2hex(t_k_g) + dec2hex(t_k_b);
      }

      if(!in_el.time)
      {
        clearInterval(in_el.timer);
        return;
      }
    };

    _shift_colors();

    in_el.timer = window.setInterval(_shift_colors, 50);
  },

  _setup_style: function(obj, top, left, width, height, position, text_align, line_height, font_size, font_weight, padding_left, padding_right)
  {
    var os = obj.style;

    if(top)    os.top = top;
    if(left)   os.left = left;
    if(width)  os.width = width;
    if(height) os.height = height;

    if(position) os.position = position;

    if(text_align)  os.textAlign  = text_align;
    if(line_height) os.lineHeight = line_height;
    if(font_size)   os.fontSize   = font_size;

    os.fontWeight = font_weight || "400";

    if(padding_left)  os.paddingLeft  = padding_left;
    if(padding_right) os.paddingRight = padding_right;
  },

  _setup_key: function(parent, id, top, left, width, height, text_align, line_height, font_size, font_weight, padding_left, padding_right)
  {
    var _id = this.Cntr.id + id;
    var exists = document.getElementById(_id);

    var key = exists ? exists.parentNode : document.createElement("DIV");
    this._setup_style(key, top, left, width, height, "absolute");

    var key_sub = exists || document.createElement("DIV");
    key.appendChild(key_sub); parent.appendChild(key);

    this._setup_style(key_sub, "", "", "", line_height, "relative", text_align, line_height, font_size, font_weight, padding_left, padding_right);
    key_sub.id = _id;

    return key_sub;
  },

  _findX: function(obj)
  { return (obj && obj.parentNode) ? parseFloat(obj.parentNode.offsetLeft) : 0; },

  _findY: function(obj)
  { return (obj && obj.parentNode) ? parseFloat(obj.parentNode.offsetTop) : 0; },

  _findW: function(obj)
  { return (obj && obj.parentNode) ? parseFloat(obj.parentNode.offsetWidth) : 0; },

  _findH: function(obj)
  { return (obj && obj.parentNode) ? parseFloat(obj.parentNode.offsetHeight) : 0; },

  _construct: function(container_id, callback_ref, font_size, font_color, dead_color, bg_color, key_color,
                       sel_item_color, border_color, inactive_border_color, inactive_key_color, lang_sel_brd_color,
                       show_click, click_font_color, click_bg_color, click_border_color, init_layout)
  {
    var exists  = (this.Cntr != undefined), ct = exists ? this.Cntr : document.getElementById(container_id);
    var changed = (font_size && (font_size != this.fontsize));

    this._Callback = ((typeof(callback_ref) == "function") && ((callback_ref.length == 1) || (callback_ref.length == 2))) ? callback_ref : (this._Callback || null);

    var ff = "";
    switch(parseInt(init_layout))
    {
      case 2: ff = "Estrangelo Quenneshrin"; break;
      case 1: ff = "Galatia SIL"; break;
      default:
      case 0: ff = "Ezra SIL"; break;
    }

    var fs = font_size || this.fontsize || "14px";

    var fc = font_color   || this.fontcolor   || "#000";
    var dc = dead_color   || this.deadcolor   || "#F00";
    var bg = bg_color     || this.bgcolor     || "#FFF";
    var kc = key_color    || this.keycolor    || "#FFF";
    var bc = border_color || this.bordercolor || "#777";

    this.lic = sel_item_color        || this.lic || "#DDD";
    this.ibc = inactive_border_color || this.ibc || "#CCC";
    this.ikc = inactive_key_color    || this.ikc || "#FFF";
    this.lsc = lang_sel_brd_color    || this.lsc || "#F77";

    this.cfc = click_font_color   || this.cfc || "#CC3300";
    this.cbg = click_bg_color     || this.cbg || "#FF9966";
    this.cbr = click_border_color || this.cbr || "#CC3300";

    this.sc = (show_click == undefined) ? ((this.sc == undefined) ? false : this.sc) : show_click;

    this.fontname = ff, this.fontsize = fs, this.fontcolor = fc;
    this.bgcolor = bg,  this.keycolor = kc, this.deadcolor = dc, this.bordercolor = bc;

    if(!exists)
    {
      this.Cntr = ct;
      this.Caps = this.Shift = this.AltGr = this.Ctrl = false;

      this.DeadAction = []; this.DeadAction[0] = this.DeadAction[1] = null;
      this.keys = [], this.mod = [], this.pad = [];

      VKeyboard.prototype.kbArray[container_id] = this;
    }

    var kb = exists ? ct.childNodes[0] : document.createElement("DIV");

    if(!exists)
    {
      ct.appendChild(kb);
      ct.style.display = "block";
      ct.style.opacity = 0;
      ct.style.filter  = "alpha(opacity=0)";
      ct.style.zIndex  = 1;

      ct.style.position = "absolute";

      // Many thanks to Peter-Paul Koch (www.quirksmode.org) for the find-pos-X/find-pos-Y code.
      var initX = 0, ct_ = ct;
      if(ct_.offsetParent)
      {
        while(ct_.offsetParent)
        {
          initX += ct_.offsetLeft;
          ct_ = ct_.offsetParent;
        }
      }
      else if(ct_.x)
        initX += ct_.x;

      var initY = 0; ct_ = ct;
      if(ct_.offsetParent)
      {
        while(ct_.offsetParent)
        {
          initY += ct_.offsetTop;
          ct_ = ct_.offsetParent;
        }
      }
      else if(ct_.y)
        initY += ct_.y;

      ct.style.top = initY + "px", ct.style.left = initX +"px";

      kb.style.position = "relative";
      kb.style.top      = "0px", kb.style.left = "0px";
    }

    kb.style.border = "1px solid " + bc;

    var kb_main = exists ? kb.childNodes[0] : document.createElement("DIV"), ks = kb_main.style;
    if(!exists)
    {
      kb.appendChild(kb_main);

      ks.position = "relative";
      ks.width    = "1px";
      ks.cursor   = "default";
    }

    // Disable content selection:
    this._setup_event(kb_main, "selectstart", function(event) { return false; });
    this._setup_event(kb_main, "mousedown",   function(event) { if(event.preventDefault) event.preventDefault(); return false; });

    ks.fontFamily = ff, ks.backgroundColor = bg;

    if(!exists || changed)
    {
      var mag = parseFloat(fs) / 16.0, cell = Math.floor(25.0 * mag), dcell = 2 * cell;
      var cp = String(cell) + "px", lh = String(cell - 2.0) + "px";

      var prevX = 0, prevY = 1, prevW = 0, prevH = 0;

      // Convenience strings:
      var c = "center", n = "normal", r = "right", l = "left", e = "&nbsp;", pad = String(4 * mag) + "px";

      // Drag bar:
      var bar = this._setup_key(kb_main, "___drag_bar", "1px", "1px", String(cell * 16.4) + "px", cp, c, lh, parseFloat(fs) * 0.786, n);
      bar.innerHTML = "Virtual Language Keyboard";
      bar.style.fontFamily="Arial";
      bar.style.fontSize="16px";
      bar.style.fontWeight="600";
      bar.style.backgroundColor="#666193";
      bar.style.color="#FFF";
      bar.width="100%";
      prevY = cell + 1;
      bar.style.cursor = "move";
      this._setup_event(bar, "mousedown", function(event) { dragStart(event, container_id); });

      // Number row:

      var key;
      for(var i = 0; i < 13; i++)
      {
        this.keys[i] = key = this._setup_key(kb_main, "___key" + String(i), prevY + "px", (prevX + prevW + 1) + "px", cp, cp, c, lh, fs);

        prevX = this._findX(key), prevW = this._findW(key);
      }

      prevY = this._findY(key);
      prevH = this._findH(key); // universal key height

      var kb_kbp = this._setup_key(kb_main, "___kbp", prevY + "px", (prevX + prevW + 1) + "px", (2.96 * cell) + "px", cp, r, lh, fs, n, "", pad);
      kb_kbp.innerHTML = "BackSpace";
      kb_kbp.style.fontFamily="Arial";
      kb_kbp.style.fontSize="12px";
      this.mod[0] = kb_kbp;

      // Top row:

      var kb_tab = this._setup_key(kb_main, "___tab", (prevY + prevH + 1) + "px", "1px", (1.48 * cell + 1) + "px", cp, l, lh, fs, n, pad);
      kb_tab.innerHTML = "Tab";
      kb_tab.style.fontFamily="Arial";
      kb_tab.style.fontSize="12px";
      this.mod[1] = kb_tab;

      prevX = this._findX(kb_tab), prevW = this._findW(kb_tab), prevY = this._findY(kb_tab);

      for(; i < 26; i++)
      {
        this.keys[i] = key = this._setup_key(kb_main, "___key" + String(i), prevY + "px", (prevX + prevW + 1) + "px", cp, cp, c, lh, fs);

        prevX = this._findX(key), prevW = this._findW(key);
      }

      this.kbpH = this._findX(kb_kbp) + this._findW(kb_kbp);

      // Home row:

      var kb_caps = this._setup_key(kb_main, "___caps", (prevY + prevH + 1) + "px", "1px", dcell + "px", cp, l, lh, fs, n, pad);
      kb_caps.innerHTML = "Caps";
      kb_caps.style.fontFamily="Arial";
      kb_caps.style.fontSize="12px";
      this.mod[2] = kb_caps;

      prevX = this._findX(kb_caps), prevW = this._findW(kb_caps), prevY = this._findY(kb_caps);

      for(; i < 38; i++)
      {
        this.keys[i] = key = this._setup_key(kb_main, "___key" + String(i), prevY + "px", (prevX + prevW + 1) + "px", cp, cp, c, lh, fs);

        prevX = this._findX(key), prevW = this._findW(key);
      }

      prevY = this._findY(key);
      var s = prevX + prevW + 1;

      var kb_enter = this._setup_key(kb_main, "___enter_l", prevY + "px", s + "px", (this.kbpH - s) + "px", cp, r, lh, fs, n, "", pad);
      kb_enter.innerHTML = "Enter";
      kb_enter.style.fontFamily="Arial";
      kb_enter.style.fontSize="12px";
      this.mod[3] = kb_enter;

      s = this._findX(this.keys[25]) + this._findW(this.keys[25]) + 1;

      var kb_enter_top = this._setup_key(kb_main, "___enter_top", this._findY(kb_tab) + "px", s + "px", (this.kbpH - s) + "px", cp, c, cp, fs);
      kb_enter_top.innerHTML = e;
      kb_enter_top.subst = "Enter";
      this.mod[4] = kb_enter_top;

      kb_enter_top.company = kb_enter;
      kb_enter.company = kb_enter_top;

      // Bottom row:

      var kb_shift = this._setup_key(kb_main, "___shift", (prevY + prevH + 1) + "px", "1px", (2.52 * cell) + "px", cp, l, lh, fs, n, pad);
      kb_shift.innerHTML = "Shift";
      kb_shift.style.fontFamily="Arial";
      kb_shift.style.fontSize="12px";
      this.mod[5] = kb_shift;

      prevX = this._findX(kb_shift), prevW = this._findW(kb_shift), prevY = this._findY(kb_shift);

      for(; i < 48; i++)
      {
        this.keys[i] = key = this._setup_key(kb_main, "___key" + String(i), prevY + "px", (prevX + prevW + 1) + "px", cp, cp, c, lh, fs);

        prevX = this._findX(key), prevW = this._findW(key);
      }

      prevY = this._findY(key);

      var kb_shift_r = this._setup_key(kb_main, "___shift_r", prevY + "px", (prevX + prevW + 1) + "px", (this._findX(kb_kbp) + this._findW(kb_kbp) - prevX - prevW - 1) + "px", cp, r, lh, fs, n, "", pad);
      kb_shift_r.innerHTML = "Shift";
      kb_shift_r.style.fontFamily="Arial";
      kb_shift_r.style.fontSize="12px";
      this.mod[6] = kb_shift_r;

      // Language selector:

      var vcell = String(1.5 * cell) + "px", vcell2 = String(1.6 * cell) + "px";

      var kb_lang = this._setup_key(kb_main, "___lang", (prevY + prevH + 1) + "px", "1px", String(3 * 1.29 * cell) + "px", cp, l, lh, fs, n, pad);
      this.mod[7] = kb_lang;
      kb_lang.style.fontFamily="Arial";
      kb_lang.style.fontSize="12px";

      prevY = this._findY(kb_lang);

      ks.height = (prevY + prevH + 1) + "px";

      prevY += "px";

      var kb_space = this._setup_key(kb_main, "___space", prevY, (this._findX(kb_lang) + this._findW(kb_lang) + 1) + "px", (6.28 * cell) + "px", cp, c, lh, fs);
      this.mod[8] = kb_space;

      var kb_alt_gr = this._setup_key(kb_main, "___alt_gr", prevY, (this._findX(kb_space) + this._findW(kb_space) + 1) + "px", vcell, cp, c, lh, parseFloat(fs) * 0.786, n);
      kb_alt_gr.innerHTML = "AltGr";
      kb_alt_gr.style.fontFamily="Arial";
      kb_alt_gr.style.fontSize="12px";
      this.mod[9] = kb_alt_gr;

      var kb_res_3 = this._setup_key(kb_main, "___res_3", prevY, (this._findX(kb_alt_gr) + this._findW(kb_alt_gr) + 1) + "px", vcell, cp, c, lh, fs);
      kb_res_3.innerHTML = e;
      this.mod[10] = kb_res_3;

      var kb_res_4 = this._setup_key(kb_main, "___res_4", prevY, (this._findX(kb_res_3) + this._findW(kb_res_3) + 1) + "px", vcell, cp, c, lh, fs);
      kb_res_4.innerHTML = e;
      this.mod[11] = kb_res_4;

      var kb_ctrl = this._setup_key(kb_main, "___ctrl", prevY, (this._findX(kb_res_4) + this._findW(kb_res_3) + 1) + "px", vcell2, cp, c, lh, fs, n);
      kb_ctrl.innerHTML = "Ctrl";
      kb_ctrl.style.fontFamily="Arial";
      kb_ctrl.style.fontSize="12px";
      this.mod[12] = kb_ctrl;

      kb.style.width = ks.width = this.kbpH + 1 + "px";
    }

    this._refresh_layout(this.layout_ = this.avail_langs[init_layout || 0][0]);

    this._fade(ct.id, 100, 50, 20);

    return this;
  },

  _set_key_state: function(key, on, textcolor, bordercolor, bgcolor)
  {
    if(key)
    {
      var ks = key.style;
      if(ks)
      {
        if(textcolor) ks.color = textcolor;
        if(bordercolor) ks.border = "1px solid " + bordercolor;
        if(bgcolor) ks.backgroundColor = bgcolor;
      }

      this._detach_event(key, 'mousedown', this._generic_callback_proc);

      if(on)
        this._setup_event(key, 'mousedown', this._generic_callback_proc);
    }
  },

  _findLangNameByLangId: function(lang_id)
  {
    for(var l = 0; l < this.avail_langs.length; l++)
    {
      if(this.avail_langs[l][0] == lang_id)
      {
        return this.avail_langs[l][1];
      }
    }

    return "";
  },

  _refresh_layout: function(layout)
  {
    if(!layout) layout = this.layout_;

    var fc = this.fontcolor, kc = this.keycolor, ikc = this.ikc;
    var ibc = this.ibc, bc = this.bordercolor, lic = this.lic;

    var arr_type = this.AltGr ? (this.Shift ? "alt_gr_shift" : "alt_gr") : (this.Shift ? (this.Ctrl ? "shift_ctrl" : "shift") : (this.Caps ? "caps" : "normal"));

    var nkeys = this.keys.length;
    var proto = VKeyboard.prototype;

    var norm_arr  = proto[layout + "_normal"];
    var caps_arr  = proto[layout + "_caps"];
    var shift_arr = proto[layout + "_shift"];
    var alt_arr   = proto[layout + "_alt_gr"];

    var shift_ctrl_arr = proto[layout + "_shift_ctrl"];
    var alt_shift_arr = proto[layout + "_alt_gr_shift"];

    var dead_arr = proto[this.DeadAction[1]] || null;

    var bcaps  = (caps_arr  && (caps_arr.length  == nkeys));
    var bshift = (shift_arr && (shift_arr.length == nkeys));
    var bshct  = (bshift    && shift_ctrl_arr && (shift_ctrl_arr.length == nkeys));
    var balt   = (alt_arr   && (alt_arr.length   == nkeys));
    var baltsh = (balt      && alt_shift_arr && (alt_shift_arr.length == nkeys));

    var caps = this.mod[2], shift = this.mod[5], shift_r = this.mod[6], alt_gr = this.mod[9], ctrl = this.mod[12];

    this._set_key_state(ctrl, false, ibc, ibc, ikc);

    if(bshift)
    {
      this._set_key_state(shift, true, fc, bc, this.Shift ? lic : kc);
      this._set_key_state(shift_r, true, fc, bc, this.Shift ? lic : kc);
    }
    else
    {
      this._set_key_state(shift, false, ibc, ibc, ikc);
      this._set_key_state(shift_r, false, ibc, ibc, ikc);

      if(arr_type == "shift")
      {
        arr_type = "normal";
        this.Shift = false;
      }
    }

    if(balt)
    {
      this._set_key_state(alt_gr, true, fc, bc, this.AltGr ? lic : kc);

      if(this.AltGr)
      {
        if(baltsh)
        {
          this._set_key_state(shift, true, fc, bc);
          this._set_key_state(shift_r, true, fc, bc);
        }
        else
        {
          this._set_key_state(shift, false, ibc, ibc, ikc);
          this._set_key_state(shift_r, false, ibc, ibc, ikc);

          arr_type = "alt_gr";
          this.Shift = false;
        }
      }
    }
    else
    {
      this._set_key_state(alt_gr, false, ibc, ibc, ikc);

      if(arr_type == "alt_gr")
      {
        arr_type = "normal";
        this.AltGr = false;
      }
      else if(arr_type == "alt_gr_shift")
      {
        arr_type = "normal";
        this.AltGr = false, this.Shift = false;

        shift.style.backgroundColor = kc, shift_r.style.backgroundColor = kc;
      }
    }

    if(this.Shift)
    {
      if(!baltsh) this._set_key_state(alt_gr, false, ibc, ibc, ikc);

      if(!bshct || this.AltGr)
        this._set_key_state(ctrl, false, ibc, ibc, ikc);
      else
        this._set_key_state(ctrl, true, fc, bc, this.Ctrl ? lic : kc);

      if(this.Ctrl)
        this._set_key_state(alt_gr, false, ibc, ibc, ikc);
    }


    if(bcaps && !this.AltGr)
      this._set_key_state(caps, true, fc, bc, this.Caps ? lic : kc);
    else
    {
      this._set_key_state(caps, false, ibc, ibc, ikc);

      this.Caps = false;
      if(arr_type == "caps") arr_type = "normal";
    }

    var arr_cur = proto[layout + "_" + arr_type];

    var i = nkeys;
    while(--i >= 0)
    {
      var key = this.keys[i], key_val = arr_cur[i]; if(!key_val) key_val = "";

      if(this.Shift && this.Caps)
      {
        var key_nrm = norm_arr[i], key_cps = caps_arr[i], key_shf = shift_arr[i];

        if((key_cps == key_shf) && (key_nrm != key_cps)) key_val = key_nrm;
      }

      if(typeof(key_val) == "object")
      {
        key.innerHTML = key_val[0], key.dead = key_val[1];

        this._set_key_state(key, true, this.deadcolor, bc, (this.DeadAction[0] == key_val[0] ? lic : kc));
      }
      else
      {
        key.dead = null;

        var block = false;

        if(key_val != "")
        {
          if(dead_arr)
          {
            for(var j = 0, l = dead_arr.length; j < l; j++) { var dk = dead_arr[j]; if(dk[0] == key_val) { key_val = dk[1]; break;}};

            if(j == l) block = true;
          }

          key.innerHTML = key_val;

          if(block)
            this._set_key_state(key, false, ibc, ibc, ikc);
          else
            this._set_key_state(key, true, fc, bc, kc);
        }
        else
        {
          key.innerHTML = "&nbsp;";
          this._set_key_state(key, false, ibc, ibc, ikc);
        }
      }
    }

    i = this.mod.length;
    while(--i >= 0)
    {
      var key = this.mod[i];

      switch(i)
      {
        case 2: case 5: case 6: case 9: case 12:
          break;

        case 7:
          key.innerHTML = this._findLangNameByLangId(layout);

          this._detach_event(key, 'mousedown', this._handle_lang_menu);

          if(this.DeadAction[1])
            this._set_key_state(key, false, ibc, ibc, ikc);
          else
          {
            var many = (this.avail_langs.length > 1);

            this._set_key_state(key, false, fc, many ? this.lsc : ibc, many ? kc : ikc);
            if(many)
              this._setup_event(key, 'mousedown', this._handle_lang_menu);
          }
          break;

        case 8:
          key.innerHTML = this.DeadAction[1] ? this.DeadAction[0] : "&nbsp;";

        default:
          if((this.DeadAction[1] && (i != 8)) || ((i == 10) || (i == 11)))
            this._set_key_state(key, false, ibc, ibc, ikc);
          else
            this._set_key_state(key, true, fc, bc, kc);

          var ks = key.style;
          switch(i)
          {
            case 4: ks.borderBottomColor = kc; break;

            case 10: case 11: ks.borderColor = ibc; break;
          }
      }
    }
  },

  _handle_lang_menu: function(event)
  {
    var pr = VKeyboard.prototype;

    var in_el = pr._get_event_source(event);
    var container_id = in_el.id.substring(0, in_el.id.indexOf("___"));
    var vkboard = pr.kbArray[container_id];

    var ct = vkboard.Cntr, menu = vkboard.menu;

    if(menu)
    { ct.removeChild(menu); vkboard.menu = null; }
    else
    {
      var fs = vkboard.fontsize, kc = vkboard.keycolor, bc = "1px solid " + vkboard.bordercolor;

      var langs = pr.avail_langs.length;

      var mag = parseFloat(fs) / 14.0, cell = Math.floor(25.0 * mag), cp = cell + "px", lh = (cell - 2) + "px";
      var h1 = Math.floor(cell + 1), wd = String(cell * 16.4 / langs);

      menu = document.createElement("DIV"); var ms = menu.style;
      ms.display  = "block";
      ms.position = "relative";

      ms.top = "1px", ms.left = "0px";
      ms.width = wd * langs + "px";
      ms.border = bc;
      ms.backgroundColor = vkboard.bgcolor;

      vkboard.menu = ct.appendChild(menu);

      var menu_main = document.createElement("DIV"); ms = menu_main.style;
      ms.fontFamily = "Arial";
      ms.position   = "relative";

      ms.color  = vkboard.fontcolor;
      ms.width  = wd * langs + langs + "px";
      ms.height = String(h1 + 1) + "px";
      ms.cursor = "default";

      ct.style.height = parseInt(ct.offsetHeight) + h1 + 1 + "px";

      menu.appendChild(menu_main);

      function setcolor(obj, c) { return function() { obj.style.backgroundColor = c; } };

      for(var j = 0; j < langs; j++)
      {
        var item = vkboard._setup_key(menu_main, "___lang_" + String(j), "1px", String(wd * j + j + 1) + "px", wd + "px", cp, "center", lh, fs, "normal");
        item.style.backgroundColor = kc;
        item.style.border = bc;
        item.innerHTML = pr.avail_langs[j][1];

        vkboard._setup_event(item, 'mousedown', vkboard._handle_lang_item);
        vkboard._setup_event(item, 'mouseover', setcolor(item, vkboard.lic));
        vkboard._setup_event(item, 'mouseout',  setcolor(item, kc));
      }
    }
  },

  _handle_lang_item: function(event)
  {
    var pr = VKeyboard.prototype;

    var in_el = pr._get_event_source(event);
    var container_id = in_el.id.substring(0, in_el.id.indexOf("___"));
    var vkboard = pr.kbArray[container_id];

    var ndx = in_el.id.indexOf("___lang_");
    var lng = in_el.id.substring(ndx + 8, in_el.id.length);
    var newl = pr.avail_langs[lng][0];

    var ff = "";
    switch(parseInt(lng))
    {
      case 2: ff = "Estrangelo Quenneshrin"; break;
      case 1: ff = "Galatia SIL"; break;
      default:
      case 0: ff = "Ezra SIL"; break;
    }

    this.fontname = vkboard.Cntr.childNodes[0].childNodes[0].style.fontFamily = ff;

    if(vkboard.layout_ != newl)
      vkboard._refresh_layout(vkboard.layout_ = newl);

    vkboard.Cntr.removeChild(vkboard.menu);
    vkboard.menu = null;
  },

  _generic_callback_proc: function(event)
  {
    var pr = VKeyboard.prototype;

    var in_el = pr._get_event_source(event);
    var container_id = in_el.id.substring(0, in_el.id.indexOf("___"));
    var vkboard = pr.kbArray[container_id];

    var val = in_el.subst || in_el.innerHTML;
    if(!val) return;

    switch(val)
    {
      case "Caps": case "Shift": case "AltGr": case "Ctrl":

        vkboard[val] = !vkboard[val];
        vkboard._refresh_layout();

        if(vkboard.sc) vkboard._start_flash(in_el);
        return;

      case "Tab":    val = "\t"; break;
      case "&nbsp;": val = " ";  break;
      case "&quot;": val = "\""; break;
      case "&lt;":   val = "<";  break;
      case "&gt;":   val = ">";  break;
      case "&amp;":  val = "&";  break;
    }

    if(vkboard.sc) vkboard._start_flash(in_el);

    if(in_el.dead)
    {
      if(in_el.dead == vkboard.DeadAction[1])
      { val = ""; vkboard.DeadAction[0] = vkboard.DeadAction[1] = null; }
      else
      { vkboard.DeadAction[0] = val; vkboard.DeadAction[1] = in_el.dead; }

      vkboard._refresh_layout();
      return;
    }
    else
    { var r;
      if(vkboard.DeadAction[1]) { vkboard.DeadAction[0] = vkboard.DeadAction[1] = null; r = true; }

      if(vkboard.AltGr || vkboard.Shift || r)
      {
        vkboard.AltGr = false; vkboard.Shift = false;
        vkboard._refresh_layout();
      }
    }

    if(vkboard._Callback) vkboard._Callback(val, vkboard.Cntr.id);
  },

  _fade: function(id, destOp, rate, delta)
  {
    var obj = document.getElementById(id);

    if(obj.timer) clearTimeout(obj.timer);

    var curOp = obj.filters ? obj.filters.alpha.opacity : (obj.style.opacity * 100.0);
    var direction = (curOp <= destOp) ? 1 : -1;

    if((destOp < curOp) && (curOp == 100))
    {
      obj.style.zIndex  = 0;
    }
    else if((destOp > curOp) && (curOp == 0))
    {
      obj.style.display = "block";
      obj.style.zIndex  = 1;
    }

    delta  = Math.min(direction * (destOp - curOp), delta);
    curOp += direction * delta;

    if(obj.filters)
      obj.filters.alpha.opacity = curOp;
    else
      obj.style.opacity = curOp / 100.0;

    if(curOp != destOp)
      obj.timer = setTimeout(function() { VKeyboard.prototype._fade(id, destOp, rate, delta); }, rate);
    else
    {
      if(curOp == 0)
        obj.style.display = "none";
    }
  },

  Show: function(value)
  {
     this._fade(this.Cntr.id, ((value == undefined) || (value == true)) ? 100 : 0, 50, 20);
  },

  // Layout info:

  avail_langs: [["He", "Hebrew"], ["El", "Greek"], ["Sy", "Aramaic"]],

  // Hebrew:

  He_normal:       ["&#x05E2;","&#x0031;","&#x0032;","&#x0033;","&#x0034;","&#x0035;","&#x0036;","&#x0037;","&#x0038;","&#x0039;","&#x0030;","&#x05BE;","&#x003D;",
                    "&#x05E7;","&#x05D5;","&#x05B6;","&#x05E8;","&#x05EA;","&#x05D9;","&#x05BB;","&#x05B4;","&#x05B8;","&#x05E4;","&#xFB2B;","&#xFB2A;","&#x05C0;",
                    "&#x05B7;","&#x05E1;","&#x05D3;","&#x05D8;","&#x05D2;","&#x05D4;","&#x05D7;","&#x05DB;","&#x05DC;","&#x05C3;","&#x05D0;",          ,
                    "&#x05D6;","&#x05E9;","&#x05E6;","&#x05D5;","&#x05D1;","&#x05E0;","&#x05DE;","&#x05B0;","&#x05BC;","&#x002F;"],

   He_shift:       ["&#x05BF;","&#x200D;","&#x200C;","&#x034F;","&#x200E;","&#x200F;","&#x202C;","&#x202E;",          ,"&#x0028;","&#x0029;",          ,"&#x05AF;",
                              ,          ,"&#x05B5;",          ,"&#x05D8;",          ,"&#xFB35;","&#x05B4;","&#x05C1;","&#x05E3;","&#x05BD;","&#x05BD;","&#x05C0;",
                    "&#x05C7;","&#x05E9;","&#x05B1;",          ,          ,"&#x05D7;",          ,"&#x05D3;","&#x05B3;","&#x05C3;","&#x05E2;",          ,
                    "&#x05B2;",          ,"&#x05E5;",          ,          ,"&#x05DF;","&#x05DD;","&#x05B0;","&#x05BC;","&#x002E;"],

  He_alt_gr:       ["&#x05BD;","&#x05BD;","&#x0591;","&#x05AD;","&#x059B;","&#x05A3;","&#x05A4;","&#x05A5;","&#x05A6;","&#x05A7;","&#x05A2;","&#x05C5;","&#x05A2;",
                    "&#x059A;","&#x05AD;","&#x05B1;","&#x0593;","&#x0594;","&#x0595;","&#x0597;","&#x0598;","&#x05B3;","&#x059F;","&#x059E;","&#x059D;","&#x059C;",
                    "&#x05B2;","&#x0592;","&#x05A0;","&#x05AC;","&#x05A8;","&#x05AB;","&#x05A1;","&#x0307;","&#x0308;","&#x0597;","&#x05AF;",          ,
                    "&#x05AF;",          ,          ,          ,          ,"&#x05C6;","&#x05F3;","&#x05F4;","&#x05C3;","&#x05C0;"],

  He_alt_gr_shift: ["&#xFB20;",          ,          ,          ,          ,          ,          ,          ,          ,"&#x0028;","&#x0029;","&#xFB1E;","&#xFB29;",
                              ,"&#x05F0;",          ,"&#xFB27;","&#xFB28;","&#x05F2;","&#x05F1;","&#xFB1F;",          ,          ,"&#x005B;","&#x005D;",          ,
                    "&#x05C7;",          ,"&#xFB22;",          ,          ,"&#xFB23;",          ,"&#xFB24;","&#xFB25;","&#xFB4F;","&#xFB21;",          ,
                              ,          ,          ,          ,          ,"&#x05C6;","&#xFB26;","&#x002C;","&#x002E;","&#xFB1D;"],

  He_shift_ctrl:   [          ,"&#x0021;","&#x2013;","&#x2014;",          ,"&#x00F7;",          ,          ,"&#x002A;",          ,          ,"&#x002D;","&#x002B;",
                              ,          ,          ,"&#x200F;",          ,          ,          ,          ,          ,"&#x202C;","&#x05C2;","&#x05C1;",          ,
                              ,          ,          ,          ,"&#x034F;",          ,"&#x200D;",          ,"&#x200E;","&#x003B;","&#x0022;",          ,
                              ,"&#x00D7;",          ,"&#x202E;",          ,"&#x200C;",          ,"&#x003C;","&#x003E;","&#x0085;"],

  // Greek:

  El_normal:       ["&#x0308;","&#x0031;","&#x0032;","&#x0033;","&#x0034;","&#x0035;","&#x0036;","&#x0037;","&#x0038;","&#x0039;","&#x0030;","&#x0387;","&#x0342;",
                    "&#x03B8;","&#x03C9;","&#x03B5;","&#x03C1;","&#x03C4;","&#x03C8;","&#x03C5;","&#x03B9;","&#x03BF;","&#x03C0;","&#x0314;","&#x0313;","&#x0300;",
                    "&#x03B1;","&#x03C3;","&#x03B4;","&#x03C6;","&#x03B3;","&#x03B7;","&#x0345;","&#x03BA;","&#x03BB;","&#x037E;","&#x0313;",          ,
                    "&#x03B6;","&#x03BE;","&#x03C7;","&#x03C2;","&#x03B2;","&#x03BD;","&#x03BC;","&#x002C;","&#x002E;","&#x0301;"],

    El_caps:       ["&#x0308;","&#x0031;","&#x0032;","&#x0033;","&#x0034;","&#x0035;","&#x0036;","&#x0037;","&#x0038;","&#x0039;","&#x0030;","&#x0387;","&#x0342;",
                    "&#x0398;","&#x03A9;","&#x0395;","&#x03A1;","&#x03A4;","&#x03A8;","&#x03A5;","&#x0399;","&#x039F;","&#x03A0;","&#x0314;","&#x0313;","&#x0300;",
                    "&#x0391;","&#x03A3;","&#x0394;","&#x03A6;","&#x0393;","&#x0397;","&#x0345;","&#x039A;","&#x039B;","&#x037E;","&#x0313;",          ,
                    "&#x0396;","&#x039E;","&#x03A7;","&#x03A3;","&#x0392;","&#x039D;","&#x039C;","&#x002C;","&#x002E;","&#x0301;"],

   El_shift:       ["&#x0342;","&#x0304;","&#x0306;","&#x0301;","&#x0317;","&#x0307;","&#x0323;","&#x03D7;","&#x002A;","&#x0028;","&#x0029;","&#x002D;","&#x0305;",
                    "&#x0398;","&#x03A9;","&#x0395;","&#x03A1;","&#x03A4;","&#x03A8;","&#x03A5;","&#x0399;","&#x039F;","&#x03A0;","&#x005B;","&#x005D;","&#x005C;",
                    "&#x0391;","&#x03A3;","&#x0394;","&#x03A6;","&#x0393;","&#x0397;",          ,"&#x039A;","&#x039B;","&#x003A;","&#x0022;",          ,
                    "&#x0396;","&#x039E;","&#x03A7;","&#x03A3;","&#x0392;","&#x039D;","&#x039C;","&#x003C;","&#x003E;","&#x003B;"],

  El_alt_gr:       [          ,          ,          ,          ,          ,          ,"&#x03DB;",          ,"&#x2026;","&#x201C;","&#x201D;","&#x2013;","&#x003D;",
                    "&#x03D9;","&#x03DD;",          ,          ,          ,          ,          ,          ,          ,"&#x03E1;","&#x007B;","&#x007D;","&#x005C;",
                    "&#x03D9;","&#x03DB;","&#x03DD;","&#x03DD;","&#x03DD;",          ,"&#x006A;","&#x03DE;",          ,"&#x2018;","&#x2019;",          ,
                              ,          ,"&#x03F2;",          ,          ,          ,          ,"&#x00AB;","&#x00BB;","&#x002F;"],

  El_alt_gr_shift: [          ,          ,          ,          ,          ,          ,"&#x03DA;",          ,          ,"&#x201E;","&#x201D;","&#x2014;","&#x002B;",
                    "&#x03D8;","&#x03DC;",          ,          ,          ,          ,          ,          ,          ,"&#x03E0;",          ,          ,"&#x007C;",
                    "&#x03D8;","&#x03DA;","&#x03DC;","&#x03DC;","&#x03DC;",          ,          ,"&#x03DF;",          ,"&#x201A;","&#x201B;",          ,
                              ,          ,          ,          ,          ,          ,          ,"&#x2039;","&#x203A;","&#x003F;"],

  // Syriac (Aramaic):

  Sy_normal:       ["&#x0725;","&#x0031;","&#x0032;","&#x0033;","&#x0034;","&#x0035;","&#x0036;","&#x0037;","&#x0038;","&#x0039;","&#x0030;","&#x0304;","&#x0308;",
                    "&#x0729;","&#x0718;","&#x0736;","&#x072A;","&#x072C;","&#x071D;","&#x073D;","&#x073A;","&#x0733;","&#x0726;","&#x072B;","&#x072B;","&#x0724;",
                    "&#x0730;","&#x0723;","&#x0715;","&#x071B;","&#x0713;","&#x0717;","&#x071A;","&#x071F;","&#x0720;","&#x0703;","&#x0710;",          ,
                    "&#x0719;","&#x072B;","&#x0728;","&#x0718;","&#x0712;","&#x0722;","&#x0721;","&#x0701;","&#x0702;","&#x002F;"],

   Sy_shift:       ["&#x070F;","&#x200D;","&#x200C;","&#x034F;","&#x200E;","&#x200F;","&#x202C;","&#x202E;","&#x070D;","&#x0028;","&#x0029;","&#x0331;","&#x0308;",
                    "&#x0738;","&#x0739;","&#x0737;","&#x0716;","&#x071B;","&#x073C;","&#x073E;","&#x073B;","&#x0734;","&#x0726;","&#x005B;","&#x005D;","&#x0748;",
                    "&#x0731;","&#x072B;","&#x0716;","&#x0701;","&#x0714;","&#x071A;","&#x073C;","&#x071F;","&#x0739;","&#x0744;","&#x0725;",          ,
                    "&#x0704;","&#x0700;","&#x0728;",          ,"&#x064B;","&#x0722;","&#x0721;","&#x0702;","&#x0702;","&#x0747;"],

  Sy_alt_gr:       ["&#x0303;","&#x0330;","&#x030A;","&#x0325;","&#x0738;","&#x032D;","&#x0745;","&#x0746;","&#x074A;","&#x0743;","&#x0744;","&#x070A;","&#x070B;",
                              ,          ,"&#x0705;",          ,          ,          ,"&#x073C;","&#x073C;","&#x0739;","&#x032E;","&#x0741;","&#x0702;","&#x0706;",
                    "&#x0744;","&#x0723;",          ,          ,          ,          ,          ,          ,          ,"&#x0708;","&#x0709;",          ,
                              ,          ,          ,          ,          ,          ,"&#x070F;","&#x070F;","&#x0700;","&#x0707;"],

  Sy_alt_gr_shift: ["&#x070C;",          ,          ,"&#x0749;",          ,          ,"&#x2018;","&#x2019;","&#x201C;","&#x201D;",          ,"&#x2010;","&#x005F;",
                    "&#x00BF;","&#x2022;","&#x0739;",          ,"&#x071C;","&#x071E;",          ,          ,"&#x073C;","&#x0727;",          ,          ,"&#x007C;",
                              ,"&#x0724;","&#x072F;","&#x074F;","&#x072E;",          ,          ,"&#x074E;",          ,"&#x0703;","&#x0711;",          ,
                    "&#x074D;","&#x2670;","&#x2671;",          ,"&#x072D;",          ,          ,"&#x002C;","&#x002E;","&#x003F;"],

  Sy_shift_ctrl:   ["&#x02BB;","&#x0661;","&#x0662;","&#x0663;","&#x0664;","&#x0665;","&#x0666;","&#x0667;","&#x0668;","&#x0669;","&#x0660;","&#x061B;","&#x061F;",
                    "&#x0670;","&#x06E4;","&#x0654;","&#x200F;",          ,          ,"&#x064F;","&#x0650;","&#x0652;","&#x202C;","&laquo;" ,"&raquo;" ,"&#x005C;",
                    "&#x064E;","&#x0651;","&#x0621;",          ,"&#x034F;","&#x064C;","&#x200D;","&#x064D;","&#x200E;","&#x003B;","&#x0022;",          ,
                    "&#x064B;",          ,"&#x0655;","&#x202E;",          ,"&#x200C;",          ,"&#x003C;","&#x003E;","&#x2026;"]
};

//**********************************************
// Do not remove this notice.
//
// Copyright 2001 by Mike Hall.
// See http://www.brainjar.com for terms of use.
//**********************************************

// Global object to hold drag information:

var dragObj = new Object();
dragObj.zIndex = 0;

function dragStart(event, id)
{
  var e   = event || window.event;
  var src = e.srcElement || e.target;

  var el = null;
  var x = 0, y = 0;

  // If an element id was given, find it. Otherwise use the element being
  // clicked on.
  dragObj.elNode = id ? document.getElementById(id) : src;

  // Get cursor position with respect to the page.

  if(document.all && !window.opera)
  {
    x = event.clientX + document.documentElement.scrollLeft
      + document.body.scrollLeft;
    y = event.clientY + document.documentElement.scrollTop
      + document.body.scrollTop;
  }
  else
  {
    x = event.clientX + (window.scrollX ? window.scrollX : 0);
    y = event.clientY + (window.scrollY ? window.scrollY : 0);
  }

  // Save starting positions of cursor and element.

  dragObj.cursorStartX = x;
  dragObj.cursorStartY = y;

  dragObj.elStartLeft  = parseInt(dragObj.elNode.style.left, 10);
  dragObj.elStartTop   = parseInt(dragObj.elNode.style.top,  10);

  if (isNaN(dragObj.elStartLeft)) dragObj.elStartLeft = 0;
  if (isNaN(dragObj.elStartTop))  dragObj.elStartTop  = 0;

  // Update element's z-index.

  dragObj.elNode.style.zIndex = ++dragObj.zIndex;

  // Capture mousemove and mouseup events on the page.

  if(document.all && !window.opera)
  {
    document.attachEvent("onmousemove", dragGo);
    document.attachEvent("onmouseup",   dragStop);

    event.cancelBubble = true;
    event.returnValue = false;
  }
  else
  {
    document.addEventListener("mousemove", dragGo,   true);
    document.addEventListener("mouseup",   dragStop, true);
    event.preventDefault();
  }
}

function dragGo(event)
{
  var x = 0, y = 0;
  if(!event) event = window.event;

  // Get cursor position with respect to the page.

  if(document.all && !window.opera)
  {
    x = event.clientX + document.documentElement.scrollLeft
      + document.body.scrollLeft;
    y = event.clientY + document.documentElement.scrollTop
      + document.body.scrollTop;
  }
  else
  {
    x = event.clientX + (window.scrollX ? window.scrollX : 0);
    y = event.clientY + (window.scrollY ? window.scrollY : 0);
  }

  // Move drag element by the same amount the cursor has moved.

  dragObj.elNode.style.left = (dragObj.elStartLeft + x - dragObj.cursorStartX) + "px";
  dragObj.elNode.style.top  = (dragObj.elStartTop  + y - dragObj.cursorStartY) + "px";

  if(document.all && !window.opera)
  {
    event.cancelBubble = true;
    event.returnValue = false;
  }
  else
    event.preventDefault();
}

function dragStop(event)
{

  // Stop capturing mousemove and mouseup events.

  if(document.all && !window.opera)
  {
    document.detachEvent("onmousemove", dragGo);
    document.detachEvent("onmouseup",   dragStop);
  }
  else
  {
    document.removeEventListener("mousemove", dragGo,   true);
    document.removeEventListener("mouseup",   dragStop, true);
  }
}

