*** empty log message ***
[m17n/m17n-lib-js.git] / xex.js
diff --git a/xex.js b/xex.js
index 78b1223..a7ed176 100644 (file)
--- a/xex.js
+++ b/xex.js
@@ -2,15 +2,27 @@
 
 var Xex = {
   LogNode: null,
-  Log: function (arg, indent)
+  Log: function (arg, indent, cont)
   {
     if (! Xex.LogNode)
       return;
-    var str = '';
-    if (indent != undefined)
-      for (var i = 0; i <= indent; i++)
-       str += '  ';
-    Xex.LogNode.value = str + arg + "\n" + Xex.LogNode.value;
+    if (typeof indent == 'number')
+      return;
+    if (! arg)
+      Xex.LogNode.value = '';
+    else
+      {
+       var str = '';
+       if (! cont)
+         {
+           str = "\n";
+           if (indent != undefined)
+             for (var i = 0; i <= indent; i++)
+               str += '  ';
+         }
+       Xex.LogNode.value += str + arg;
+       Xex.LogNode.scrollTop = Xex.LogNode.scrollHeight;
+      }
   }
 };
 
@@ -244,7 +256,11 @@ Xex.Domain = function (name, parent, context)
       for (elt in parent.functions)
        this.functions[elt] = parent.functions[elt];
       for (elt in parent.variables)
-       this.variables[elt] = parent.variables[elt];
+       {
+         var vari = parent.variables[elt];
+         this.variables[elt] = new Xex.Variable (this, vari.name, vari.desc,
+                                                 vari.val, vari.range);
+       }
     }
 
   this.call_stack = new Array ();
@@ -461,7 +477,7 @@ Node.prototype.nextElement = function ()
     if (! name)
       throw new Xex.ErrTerm (Xex.Error.NoVariableName, node, '');
     var vari = domain.variables[name];
-    var desc, val, range;
+    var desc, val = null, range;
     if (vari)
       {
        desc = vari.description;
@@ -509,7 +525,7 @@ Node.prototype.nextElement = function ()
              range.push (pval);
          }
       }
-    if (! val)
+    if (val == null)
       val = Xex.Zero;
     domain.Defvar (name, desc, val, range);
     return name;
@@ -619,10 +635,9 @@ Xex.Varref = function (vname)
   proto.Clone = function () { return new Xex.Varref (this.val); }
   proto.Eval = function (domain)
   {
-    if (! this.vari || this.vari.domain != domain)
-      this.vari = domain.GetVarCreate (this.val);
-    Xex.Log (this.ToString () + '=>' + this.vari.val, domain.depth);
-    return this.vari.val;
+    var vari = domain.GetVarCreate (this.val);
+    Xex.Log (this.ToString () + '=>' + vari.val, domain.depth);
+    return vari.val;
   }
 
   proto.Parser = function (domain, node)
@@ -640,10 +655,10 @@ Xex.Varref = function (vname)
 
 var null_args = new Array ();
   
-Xex.Funcall = function (func, vari, args)
+Xex.Funcall = function (func, vname, args)
 {
   this.func = func;
-  this.vari = vari;
+  this.vname = vname;
   this.args = args || null_args;
 };
 
@@ -658,18 +673,17 @@ Xex.Funcall = function (func, vari, args)
     if (fname == 'funcall')
       fname = node.attributes['fname'].nodeValue;
     var func = domain.GetFunc (fname);
-    var vari;
+    var vname;
     attr = node.attributes['vname'];
-    vari = attr != undefined ? domain.GetVarCreate (attr.nodeValue) : false;
+    vname = attr != undefined ? attr.nodeValue : null;
     var args = Xex.Term.Parse (domain, node.firstElement (), null);
-    return new Xex.Funcall (func, vari, args);
+    return new Xex.Funcall (func, vname, args);
   }
 
   proto.New = function (domain, fname, vname, args)
   {
     var func = domain.GetFunc (fname);
-    var vari = vname ? domain.GetVarCreate (vname) : null;
-    var funcall = new Xex.Funcall (func, vari, args);
+    var funcall = new Xex.Funcall (func, vname, args);
     if (func instanceof Xex.Macro)
       funcall = funcall.Eval (domain);
     return funcall;
@@ -677,14 +691,17 @@ Xex.Funcall = function (func, vari, args)
 
   proto.Eval = function (domain)
   {
-    if (! (this.func instanceof Xex.Subrountine))
-      Xex.Log (this, domain.depth);
+    Xex.Log (this, domain.depth);
+    var vari;
+    if (this.vname)
+      vari = domain.GetVarCreate (this.vname);
     domain.depth++;
     var result;
     try {
-      result = this.func.Call (domain, this.vari, this.args);
+      result = this.func.Call (domain, vari, this.args);
     } finally {
-      Xex.Log (this + ' => ' + result, --domain.depth);
+      Xex.Log (' => ' + result, --domain.depth,
+              this.func instanceof Xex.Subrountine);
     }
     return result;
   }
@@ -798,7 +815,7 @@ Xex.StrTerm = function (str) { this.val = str; };
   proto.Clone = function () { return new Xex.StrTerm (this.val); }
   proto.Parser = function (domain, node)
   {
-    return new Xex.StrTerm (node.firstChild.nodeValue);
+    return new Xex.StrTerm (node.firstChild ? node.firstChild.nodeValue : '');
   }
   Xex.StrTerm.prototype = proto;
 }) ();
@@ -866,6 +883,11 @@ Xex.LstTerm = function (list) { this.val = list; };
     return args[0];
   }
 
+  function Fnot (domain, vari, args)
+  {
+    return (args[0].IsTrue () ? Xex.Zero : Xex.One);
+  }
+
   function maybe_set_intvar (vari, n)
   {
     var term = new Xex.IntTerm (n);
@@ -943,6 +965,39 @@ Xex.LstTerm = function (list) { this.val = list; };
     return maybe_set_intvar (vari, n);
   }
 
+  function Flogand (domain, vari, args)
+  {
+    var n, i;
+    if (vari == null)
+      {
+       Xex.Log ("logand arg args[0]" + args[0]);
+       n = args[0].Intval ()
+       i = 1;
+      }
+    else
+      {
+       Xex.Log ("logand arg var " + vari);
+       n = vari.val.Intval ();
+       i = 0;
+      }
+    while (n > 0 && i < args.length)
+      {
+       Xex.Log ("logand arg " + args[i]);
+       n &= args[i++].val;
+      }
+    return maybe_set_intvar (vari, n);
+  }
+
+  function Flsh (domain, vari, args)
+  {
+    return maybe_set_intvar (vari, args[0].Intval () << args[1].Intval ());
+  }
+
+  function Frsh (domain, vari, args)
+  {
+    return maybe_set_intvar (vari, args[0].Intval () >> args[1].Intval ());
+  }
+
   function Fand (domain, vari, args)
   {
     var len = args.length;
@@ -979,13 +1034,18 @@ Xex.LstTerm = function (list) { this.val = list; };
     return Xex.One;
   }
 
+  function Fnoteq (domain, vari, args)
+  {
+    return (Feq (domain, vari, args) == Xex.One ? Xex.Zero : Xex.One);
+  }
+
   function Flt (domain, vari, args)
   {
-    var n = args[0].Intval;
+    var n = args[0].Intval ();
 
     for (var i = 1; i < args.length; i++)
       {
-       var n1 = args[i].Intval;
+       var n1 = args[i].Intval ();
        if (n >= n1)
          return Xex.Zero;
        n = n1;
@@ -995,10 +1055,10 @@ Xex.LstTerm = function (list) { this.val = list; };
 
   function Fle (domain, vari, args)
   {
-    var n = args[0].Intval;
+    var n = args[0].Intval ();
     for (var i = 1; i < args.length; i++)
       {
-       var n1 = args[i].Intval;
+       var n1 = args[i].Intval ();
        if (n > n1)
          return Xex.Zero;
        n = n1;
@@ -1008,10 +1068,10 @@ Xex.LstTerm = function (list) { this.val = list; };
 
   function Fgt (domain, vari, args)
   {
-    var n = args[0].Intval;
+    var n = args[0].Intval ();
     for (var i = 1; i < args.length; i++)
       {
-       var n1 = args[i].Intval;
+       var n1 = args[i].Intval ();
        if (n <= n1)
          return Xex.Zero;
        n = n1;
@@ -1021,10 +1081,10 @@ Xex.LstTerm = function (list) { this.val = list; };
 
   function Fge (domain, vari, args)
   {
-    var n = args[0].Intval;
+    var n = args[0].Intval ();
     for (var i = 1; i < args.Length; i++)
       {
-       var n1 = args[i].Intval;
+       var n1 = args[i].Intval ();
        if (n < n1)
          return Xex.Zero;
        n = n1;
@@ -1153,8 +1213,8 @@ Xex.LstTerm = function (list) { this.val = list; };
 
   basic.DefSubr (Fset, "set", true, 1, 1);
   basic.DefAlias ("=", "set");
-  //basic.DefSubr (Fnot, "not", false, 1, 1);
-  //basic.DefAlias ("!", "not");
+  basic.DefSubr (Fnot, "not", false, 1, 1);
+  basic.DefAlias ("!", "not");
   basic.DefSubr (Fadd, "add", true, 1, -1);
   basic.DefSubr (Fmul, "mul", true, 1, -1);
   basic.DefAlias ("*", "mul");
@@ -1166,16 +1226,16 @@ Xex.LstTerm = function (list) { this.val = list; };
   basic.DefAlias ("%", "mod");
   basic.DefSubr (Flogior, "logior", true, 1, -1);
   basic.DefAlias ('|', "logior");
-  //basic.DefSubr (Flogand, "logand", true, 1, -1);
-  //basic.DefAlias ("&", "logand");
-  //basic.DefSubr (Flsh, "lsh", true, 1, 2);
-  //basic.DefAlias ("<<", "lsh");
-  //basic.DefSubr (Frsh, "rsh", true, 1, 2);
-  //basic.DefAlias (">>", "rsh");
+  basic.DefSubr (Flogand, "logand", true, 1, -1);
+  basic.DefAlias ("&", "logand");
+  basic.DefSubr (Flsh, "lsh", true, 1, 2);
+  basic.DefAlias ("<<", "lsh");
+  basic.DefSubr (Frsh, "rsh", true, 1, 2);
+  basic.DefAlias (">>", "rsh");
   basic.DefSubr (Feq, "eq", false, 2, -1);
   basic.DefAlias ("==", "eq");
-  //basic.DefSubr (Fnoteq, "noteq", false, 2, 2);
-  //basic.DefAlias ("!=", "noteq");
+  basic.DefSubr (Fnoteq, "noteq", false, 2, 2);
+  basic.DefAlias ("!=", "noteq");
   basic.DefSubr (Flt, "lt", false, 2, -1);
   basic.DefAlias ("<", "lt");
   basic.DefSubr (Fle, "le", false, 2, -1);
@@ -1300,6 +1360,8 @@ var MIM = {
   keysyms["del"] = "delete";
 
   function decode_keysym (str) {
+    if (str.length == 1)
+      return str;
     var parts = str.split ("-");
     var len = parts.length, i;
     var has_modifier = len > 1;
@@ -1336,8 +1398,9 @@ var MIM = {
   MIM.Key = function (val)
   {
     this.key;
-    this.has_modifier = false;
-    if (typeof val == 'string' || val instanceof String)
+    if (val instanceof Xex.Term)
+      this.key = val.val;
+    else if (typeof val == 'string' || val instanceof String)
       {
        this.key = decode_keysym (val);
        if (! this.key)
@@ -1355,13 +1418,16 @@ var MIM = {
   }
 
   MIM.Key.prototype.toString = function () { return this.key; };
+
+  MIM.Key.FocusIn = new MIM.Key (new Xex.StrTerm ('input-focus-in'));
+  MIM.Key.FocusOut = new MIM.Key (new Xex.StrTerm ('input-focus-out'));
+  MIM.Key.FocusMove = new MIM.Key (new Xex.StrTerm ('input-focus-move'));
 }) ();
 
 (function () {
   MIM.KeySeq = function (seq)
   {
     this.val = new Array ();
-    this.has_modifier = false;
 
     if (seq)
       {
@@ -1370,12 +1436,14 @@ var MIM = {
            var len = seq.val.length;
            for (var i = 0; i < len; i++)
              {
-               var v = seq.val[i];
-               if (v.type != 'string' && v.type != 'integer'
-                   && v.type != 'symbol')
+               var v = seq.val[i], key;
+               if (v.type == 'symbol' || v.type == 'string')
+                 key = new MIM.Key (v);
+               else if (v.type == 'integer')
+                 key = new MIM.Key (v.val);
+               else
                  throw new Xex.ErrTerm (MIM.Error.ParseError,
                                         "Invalid key: " + v);
-               var key = new MIM.Key (v.val);
                this.val.push (key);
                if (key.has_modifier)
                  this.has_modifier = true;
@@ -1521,7 +1589,7 @@ var MIM = {
        var n = predefined[name];
        if (n)
          return n;
-       if (name.charAt (1) == '-')
+       if (name.charAt (1) == '-' || name.charAt (1) == '+')
          return new MIM.SurroundMarker (name);
        throw new Xex.ErrTerm (MIM.Error.ParseError,
                               "Invalid marker: " + name);
@@ -1543,10 +1611,8 @@ MIM.Selector.prototype = new Xex.Term ('selector');
   selectors["@>"] = selectors["@last"] = new MIM.Selector ('@>');
   selectors["@-"] = selectors["@previous"] = new MIM.Selector ('@-');
   selectors["@+"] = selectors["@next"] = new MIM.Selector ('@+');
-  selectors["@["] = selectors["@previous-candidate-change"]
-    = new MIM.Selector ('@[');
-  selectors["@]"] = selectors["@next-candidate-change"]
-    = new MIM.Selector ('@]');
+  selectors["@["] = selectors["@previous-group"] = new MIM.Selector ('@[');
+  selectors["@]"] = selectors["@next-group"] = new MIM.Selector ('@]');
 
   MIM.Selector.prototype.Parser = function (domain, node)
   {
@@ -1562,7 +1628,8 @@ MIM.Selector.prototype = new Xex.Term ('selector');
 MIM.Rule = function (keyseq, actions)
 {
   this.keyseq = keyseq;
-  this.actions = actions;
+  if (actions)
+    this.actions = actions;
 }
 MIM.Rule.prototype = new Xex.Term ('rule');
 MIM.Rule.prototype.Parser = function (domain, node)
@@ -1574,7 +1641,10 @@ MIM.Rule.prototype.Parser = function (domain, node)
   var keyseq = Xex.Term.Parse (domain, n);
   if (keyseq.type != 'keyseq')
     throw new Xex.ErrTerm (MIM.Error.ParseError, "invalid rule:" + node);
-  var actions = Xex.Term.Parse (domain, n.nextElement (), null);
+  var actions = null;
+  n = n.nextElement ();
+  if (n)
+    actions = Xex.Term.Parse (domain, n, null);
   return new MIM.Rule (keyseq, actions);
 }
 MIM.Rule.prototype.toString = function ()
@@ -1668,7 +1738,8 @@ MIM.Keymap = function ()
        keymap.name = name;
       }
     keymap.map_actions = rule.actions;
-    keymap.branch_actions = branch_actions;
+    if (branch_actions)
+      keymap.branch_actions = branch_actions;
   }
 
   proto.Add = function (map, branch_actions)
@@ -1684,6 +1755,13 @@ MIM.Keymap = function ()
     var sub;
 
     if (index < keys.val.length && this.submaps
+       && ! keys.val[index])
+      {
+       Xex.Log ('invalid key at ' + index);
+       throw 'invalid key';
+      }
+
+    if (index < keys.val.length && this.submaps
        && (sub = this.submaps[keys.val[index].key]))
       {
        index++;
@@ -1718,13 +1796,14 @@ MIM.State = function (name)
        else
          {
            var n = node.firstElement ();
+           var branch_actions = n ? Xex.Term.Parse (domain, n, null) : null;
            if (node.nodeName == 'branch')
              state.keymap.Add (map_list[node.attributes['mname'].nodeValue],
-                               Xex.Term.Parse (domain, n, null));
+                               branch_actions);
            else if (node.nodeName == 'state-hook')
-             state.enter_actions = Xex.Term.Parse (domain, n, null);
+             state.enter_actions = branch_actions;
            else if (node.nodeName == 'catch-all-branch')
-             state.fallback_actions = Xex.Term.Parse (domain, n, null);
+             state.fallback_actions = branch_actions;
          }
       }
     return state;
@@ -1758,8 +1837,9 @@ MIM.State = function (name)
     return (this.Data instanceof Array ? this.Data[i] : this.Data.charAt (i));
   }
 
-  MIM.Candidates = function (candidates, column)
+  MIM.Candidates = function (ic, candidates, column)
   {
+    this.ic = ic;
     this.column = column;
     this.row = 0;
     this.index = 0;
@@ -1835,7 +1915,8 @@ MIM.State = function (name)
            this.index = col;
            this.row = 0;
          }
-       while (this.blocks[this.row].Index > this.index)
+       while (this.blocks[this.row].Index + this.blocks[this.row].Count ()
+              <= this.index)
          this.row++;
       }
     else
@@ -1910,6 +1991,8 @@ MIM.State = function (name)
 
   MIM.Candidates.prototype.Select = function (selector)
   {
+    var idx = this.index;
+    var gidx = this.column > 0 ? idx / this.column : this.row;
     if (selector.type == 'selector')
       {
        switch (selector.val)
@@ -1919,40 +2002,89 @@ MIM.State = function (name)
          case '@-': prev.call (this); break;
          case '@+': next.call (this); break;
          case '@[': prev_group.call (this); break;
-         case '@]': next_group.cal (this); break;
+         case '@]': next_group.call (this); break;
          default: break;
          }
-       return this.Current ();
       }
-    var col, start, end
+    else
+      {
+       var col, start, end
+       if (this.column > 0)
+         {
+           col = this.index % this.column;
+           start = this.index - col;
+           end = start + this.column;
+         }
+       else
+         {
+           start = this.blocks[this.row].Index;
+           col = this.index - start;
+           end = start + this.blocks[this.row].Count;
+         }
+       if (end > this.total)
+         end = this.total;
+       this.index += selector.val - col;
+       if (this.index >= end)
+         this.index = end - 1;
+       if (this.column > 0)
+         {
+           if (selector.val > col)
+             while (this.blocks[this.row].Index + this.blocks[this.row].Count
+                    < this.index)
+               this.row++;
+           else
+             while (this.blocks[this.row].Index > this.index)
+               this.row--;
+         }
+      }
+    var newgidx = this.column > 0 ? this.index / this.column : this.row;
+    if (this.index != idx)
+      this.ic.changed |= (gidx == newgidx
+                         ? MIM.ChangedStatus.CandidateIndex
+                         : MIM.ChangedStatus.CandidateList);
+    return this.Current ();
+  }
+
+  MIM.Candidates.prototype.CurrentCol = function ()
+  {
+    return get_col.call (this);
+  }
+
+  MIM.Candidates.prototype.CurrentGroup = function ()
+  {
+    var col, start, end, gnum, gidx;
     if (this.column > 0)
       {
+       gnum = Math.floor ((this.total - 1) / this.column) + 1;
        col = this.index % this.column;
        start = this.index - col;
+       gidx = start / this.column + 1;
        end = start + this.column;
+       if (end > this.total)
+         end = this.total;
       }
     else
       {
+       gnum = this.blocks.length;
+       gidx = this.row + 1;
        start = this.blocks[this.row].Index;
        col = this.index - start;
-       end = start + this.blocks[this.row].Count;
+       end = start + this.blocks[this.row].Count ();
       }
-    if (end > this.total)
-      end = this.total;
-    this.index += selector.val - col;
-    if (this.index >= end)
-      this.index = end - 1;
-    if (this.column > 0)
+    var group = new Array ();
+    var indices = new Array (gnum, gidx, col);
+    group.push (indices);
+    var row = this.row;
+    var block = this.blocks[row++];
+    while (start < end)
       {
-       if (selector.val > col)
-         while (this.blocks[this.row].Index + this.blocks[this.row].Count
-                < this.index)
-           this.row++;
-       else
-         while (this.blocks[this.row].Index > this.index)
-           this.row--;
+       var c = block.get (start - block.Index);
+       group.push (c);
+       start++;
+       if (start == block.Index + block.Count ())
+         block = this.blocks[row++];
       }
-    return this.Current ();
+    return group;
   }
 }) ();
 
@@ -1981,8 +2113,9 @@ MIM.im_domain.DefType (MIM.State.prototype);
   function Finsert_candidates (domain, vari, args)
   {
     var ic = domain.context;
-    var gsize = domain.variables['candidates_group_size'];
-    var candidates = new MIM.Candidates (args, gsize ? gsize.Intval : 0);
+    var gsize = domain.variables['candidates-group-size'];
+    var candidates = new MIM.Candidates (ic, args,
+                                        gsize ? gsize.val.Intval () : 0);
     ic.ins (candidates.Current (), candidates);
     return args[0];
   }
@@ -1990,7 +2123,7 @@ MIM.im_domain.DefType (MIM.State.prototype);
   function Fdelete (domain, vari, args)
   {
     var ic = domain.context;
-    var pos = args[0].IsInt ? args[0].Intval : args[0].Position (ic);
+    var pos = args[0].IsInt ? args[0].Intval () : args[0].Position (ic);
     return new Xex.IntTerm (ic.del (pos));
   }
 
@@ -2010,6 +2143,20 @@ MIM.im_domain.DefType (MIM.State.prototype);
     return args[0];
   }
 
+  function Fshow (domain, vari, args)
+  {
+    domain.context.candidate_show = true;
+    domain.context.changed |= MIM.ChangedStatus.CandidateShow;
+    return Xex.nil;
+  }
+
+  function Fhide (domain, vari, args)
+  {
+    domain.context.candidate_show = false;
+    domain.context.changed |= MIM.ChangedStatus.CandidateShow;
+    return Xex.nil;
+  }
+
   function Fchar_at (domain, vari, args)
   {
     return new Xex.IntTerm (args[0].CharAt (domain.context));
@@ -2038,14 +2185,23 @@ MIM.im_domain.DefType (MIM.State.prototype);
     return args[0];
   }
 
+  function Fpop (domain, vari, args)
+  {
+    var ic = domain.context;
+    if (ic.key_head < ic.keys.val.length)
+      ic.keys.val.splice (ic.keys_head, 1);
+    return Xex.nil;
+  }
+
   function Fundo  (domain, vari, args)
   {
     var ic = domain.context;
     var n = args.length == 0 ? -2 : args[0].val;
+    Xex.Log ('undo with arg ' + args[0]);
     if (n < 0)
-      ic.keys.val.splice (ic.keys.length + n, -n);
+      ic.keys.val.splice (ic.keys.val.length + n, -n);
     else
-      ic.keys.val.splice (n, ic.keys.length);
+      ic.keys.val.splice (n, ic.keys.val.length);
     ic.reset ();
     return Xex.nil;
   }
@@ -2057,10 +2213,10 @@ MIM.im_domain.DefType (MIM.State.prototype);
   }
 
   function Funhandle (domain, vari, args)
-    {
-      domain.context.commit ();
-      return Xex.Fthrow (domain, vari, Xex.CatchTag._mimtag);
-    }
+  {
+    domain.context.commit ();
+    return Xex.Fthrow (domain, vari, Xex.CatchTag._mimtag);
+  }
 
   function Fshift (domain, vari, args)
   {
@@ -2073,6 +2229,17 @@ MIM.im_domain.DefType (MIM.State.prototype);
     return args[0];
   }
 
+  function Fshiftback (domain, vari, args)
+  {
+    domain.context.shift (null);
+    return Xex.nil;
+  }
+
+  function Fkey_count (domain, vari, args)
+  {
+    return new Xex.IntTerm (domain.context.key_head);
+  }
+
   function Fsurrounding_flag (domain, vari, args)
   {
     return new Xex.IntTerm (-1);
@@ -2082,19 +2249,19 @@ MIM.im_domain.DefType (MIM.State.prototype);
   im_domain.DefSubr (Finsert_candidates, "insert-candidates", false, 1, 1);
   im_domain.DefSubr (Fdelete, "delete", false, 1, 1);
   im_domain.DefSubr (Fselect, "select", false, 1, 1);
-  //im_domain.DefSubr (Fshow, "show-candidates", false, 0, 0);
-  //im_domain.DefSubr (Fhide, "hide-candidates", false, 0, 0);
+  im_domain.DefSubr (Fshow, "show-candidates", false, 0, 0);
+  im_domain.DefSubr (Fhide, "hide-candidates", false, 0, 0);
   im_domain.DefSubr (Fmove, "move", false, 1, 1);
   im_domain.DefSubr (Fmark, "mark", false, 1, 1);
   im_domain.DefSubr (Fpushback, "pushback", false, 1, 1);
-  //im_domain.DefSubr (Fpop, "pop", false, 0, 0);
+  im_domain.DefSubr (Fpop, "pop", false, 0, 0);
   im_domain.DefSubr (Fundo, "undo", false, 0, 1);
   im_domain.DefSubr (Fcommit, "commit", false, 0, 0);
   im_domain.DefSubr (Funhandle, "unhandle", false, 0, 0);
   im_domain.DefSubr (Fshift, "shift", false, 1, 1);
-  //im_domain.DefSubr (Fshiftback, "shiftback", false, 0, 0);
+  im_domain.DefSubr (Fshiftback, "shiftback", false, 0, 0);
   im_domain.DefSubr (Fchar_at, "char-at", false, 1, 1);
-  //im_domain.DefSubr (Fkey_count, "key-count", false, 0, 0);
+  im_domain.DefSubr (Fkey_count, "key-count", false, 0, 0);
   im_domain.DefSubr (Fsurrounding_flag, "surrounding-text-flag", false, 0, 0);
 }) ();
 
@@ -2156,7 +2323,7 @@ MIM.im_domain.DefType (MIM.State.prototype);
          {
            var vari = get_global_var (vname);
            if (vari != null)
-             this.domain.Defvar (vname);
+             this.domain.Defvar (vname, vari.desc, vari.val, vari.range);
          }
        vname = Xex.Term.Parse (this.domain, node)
       }
@@ -2200,8 +2367,8 @@ MIM.im_domain.DefType (MIM.State.prototype);
                alert ('inclusion fail');
                continue;
              }
-           for (var mapname in im.map_list)
-             this.map_list[mapname] = im.map_list[mapname];
+           for (var mname in im.map_list)
+             this.map_list[mname] = im.map_list[mname];
          }
        else
          {
@@ -2215,7 +2382,20 @@ MIM.im_domain.DefType (MIM.State.prototype);
     this.domain.map_list = this.map_list;
     for (node = node.firstElement (); node; node = node.nextElement ())
       {
-       if (node.nodeName == 'state')
+       if (node.nodeName == 'xi:include')
+         {
+           var im = include (node);
+           if (! im)
+             alert ('inclusion fail');
+           for (var sname in im.state_list)
+             {
+               state = im.state_list[sname];
+               if (! this.initial_state)
+                 this.initial_state = state;
+               this.state_list[sname] = state;
+             }
+         }
+       else if (node.nodeName == 'state')
          {
            var state = Xex.Term.Parse (this.domain, node);
            if (! state.title)
@@ -2339,20 +2519,15 @@ MIM.im_domain.DefType (MIM.State.prototype);
     this.table.length = 0;
   }
 
-  function detach_candidates (ic)
-  {
-    ic.candidate_table.clear ();
-    ic.candidates = null;
-    ic.changed |= (MIM.ChangedStatus.Preedit | MIM.ChangedStatus.CursorPos
-                  | ChangedStatus.CandidateList
-                  | ChangedStatus.CandidateIndex
-                  | ChangedStatus.CandidateShow);
-  }
-
   function set_cursor (prefix, pos)
   {
     this.cursor_pos = pos;
-    this.candidates = this.candidate_table.get (pos);
+    var candidates = this.candidate_table.get (pos);
+    if (this.candidates != candidates)
+      {
+       this.candidates = candidates;
+       this.changed |= MIM.ChangedStatus.CandidateList;
+      }
   }
 
   function save_state ()
@@ -2372,25 +2547,26 @@ MIM.im_domain.DefType (MIM.State.prototype);
 
   function handle_key ()
   {
-    var out = this.keymap.Lookup (this.keys, this.key_head);
+    Xex.Log ('Key(' + this.key_head + ') "' + this.keys.val[this.key_head]
+            + '" in ' + this.state.name + ':' + this.keymap.name
+            + " key/state/commit-head/len:"
+            + this.key_head + '/' + this.state_key_head + '/' + this.commit_key_head + '/' + this.keys.val.length);
+    var out = this.state.keymap.Lookup (this.keys, this.state_key_head);
     var sub = out.map;
 
-    Xex.Log ('handling ' + this.keys.val[this.key_head]
-            + ' in ' + this.state.name + ':' + this.keymap.name);
-    this.key_head = out.index;
-    if (sub != this.keymap)
+    if (out.index > this.key_head)
       {
-
+       this.key_head = out.index;
+       Xex.Log (' with submap', false, true);
        restore_state.call (this);
        this.keymap = sub;
-       Xex.Log ('submap found');
-       if (this.keymap.map_actions)
+       if (sub.map_actions)
          {
            Xex.Log ('taking map actions:');
-           if (! this.take_actions (this.keymap.map_actions))
+           if (! this.take_actions (sub.map_actions))
              return false;
          }
-       else if (this.keymap.submaps)
+       else if (sub.submaps)
          {
            Xex.Log ('no map actions');
            for (var i = this.state_key_head; i < this.key_head; i++)
@@ -2399,28 +2575,29 @@ MIM.im_domain.DefType (MIM.State.prototype);
                this.ins (this.keys.val[i].key, null);
              }
          }
-       if (! this.keymap.submaps)
+       if (! sub.submaps)
          {
            Xex.Log ('terminal:');
-           if (this.keymap.branch_actions != null)
+           if (this.keymap.branch_actions)
              {
                Xex.Log ('branch actions:');
                if (! this.take_actions (this.keymap.branch_actions))
                  return false;
              }
-           if (this.keymap != this.state.keymap)
+           if (sub != this.state.keymap)
              this.shift (this.state);
          }
       }
     else
       {
-       Xex.Log ('no submap');
+       Xex.Log (' without submap', false, true);
+       this.keymap = sub;
        var current_state = this.state;
        var map = this.keymap;
 
        if (map.branch_actions)
          {
-           Xex.Log ('branch actions');
+           Xex.Log ('branch actions:');
            if (! this.take_actions (map.branch_actions))
              return false;
          }
@@ -2455,7 +2632,7 @@ MIM.im_domain.DefType (MIM.State.prototype);
       this.state_var_values = {};
       this.state_pos = 0;
       this.key_head = 0;
-      this.keys.val.length = 0;
+      this.commit_key_head = 0;
       this.key_unhandled = false;
       this.unhandled_key = null;
       this.changed = MIM.ChangedStatus.None;
@@ -2476,6 +2653,8 @@ MIM.im_domain.DefType (MIM.State.prototype);
 
     take_actions: function (actions)
     {
+      if (actions.length == 0)
+       return true;;
       var func_progn = this.domain.GetFunc ('progn');
       var func_catch = this.domain.GetFunc ('catch');
       this.catch_args[1] = new Xex.Funcall (func_progn, null, actions);
@@ -2546,33 +2725,33 @@ MIM.im_domain.DefType (MIM.State.prototype);
                this.marker_positions[name] = from;
            }
        }
-      if (this.cursor_pos >= to)
-       set_cursor.call (this, 'adjust', this.cursor_pos + diff);
-      else if (this.cursor_pos > from)
-       set_cursor.call (this, 'adjust', from)
     },
 
     preedit_replace: function (from, to, text, candidates)
     {
+      var newlen = text.length;
       this.preedit = (this.preedit.substring (0, from)
                      + text + this.preedit.substring (to));
-      this.adjust_markers (from, to, text.length);
-      this.candidate_table.adjust (from, to, text.length);
+      this.changed |= MIM.ChangedStatus.Preedit | MIM.ChangedStatus.CursorPos;
+      this.adjust_markers (from, to, newlen);
+      this.candidate_table.adjust (from, to, newlen);
       if (candidates)
-       this.candidate_table.put (from, from + text.length, candidates)
+       this.candidate_table.put (from, from + newlen, candidates)
+      if (this.cursor_pos >= to)
+       set_cursor.call (this, 'adjust', this.cursor_pos + text.length - (to - from));
+      else if (this.cursor_pos > from)
+       set_cursor.call (this, 'adjust', from)
     },
 
     ins: function (text, candidates)
     {
       this.preedit_replace (this.cursor_pos, this.cursor_pos, text, candidates);
-      this.changed = MIM.ChangedStatus.Preedit | MIM.ChangedStatus.CursorPos;
     },
 
     rep: function (old_text, new_text, candidates)
     {
       this.preedit_replace (this.cursor_pos - old_text.length,
                            this.cursor_pos, new_text, candidates);
-      this.changed = MIM.ChangedStatus.Preedit | MIM.ChangedStatus.CursorPos;
     },
 
     del: function (pos)
@@ -2600,8 +2779,6 @@ MIM.im_domain.DefType (MIM.State.prototype);
          if (pos > this.cursor_pos)
            this.preedit_replace (this.cursor_pos, pos, '', null);
        }
-      if (deleted != 0)
-       this.changed = MIM.ChangedStatus.Preedit | MIM.ChangedStatus.CursorPos;
       return deleted;
     },
 
@@ -2672,6 +2849,9 @@ MIM.im_domain.DefType (MIM.State.prototype);
        this.candidate_table.clear ();
        this.produced += this.preedit;
        this.preedit_replace.call (this, 0, this.preedit.length, '', null);
+       this.preedit_saved = '';
+       this.state_pos = 0;
+       this.commit_key_head = this.key_head;
       }
     },
 
@@ -2690,7 +2870,7 @@ MIM.im_domain.DefType (MIM.State.prototype);
            {
              this.commit ();
              this.keys.val.splice (0, this.key_head);
-             this.key_head = 0;
+             this.key_head = this.state_key_head = this.commit_key_head = 0;
              this.prev_state = null;
            }
        }
@@ -2705,7 +2885,6 @@ MIM.im_domain.DefType (MIM.State.prototype);
        this.changed |= MIM.ChangedStatus.StateTitle;
       this.state = state;
       this.keymap = state.keymap;
-      this.state_key_head = this.key_head;
       save_state.call (this);
     },
 
@@ -2713,6 +2892,7 @@ MIM.im_domain.DefType (MIM.State.prototype);
     {
       if (! this.active)
        {
+         Xex.Log ("active = false");
          this.key_unhandled = true;
          this.unhandled_key = key;
          return false;
@@ -2733,6 +2913,10 @@ MIM.im_domain.DefType (MIM.State.prototype);
                  this.unhandled_key = this.keys.val[this.key_head];
                  this.keys.val.splice (this.key_head, this.key_head + 1);
                }
+             if (this.state_key_head > 0)
+               this.state_key_head--;
+             if (this.commit_key_head > 0)
+               this.commit_key_head--;
              this.key_unhandled = true;
              break;
            }
@@ -2743,14 +2927,31 @@ MIM.im_domain.DefType (MIM.State.prototype);
              break;
            }
        }
+      if (this.keymap == this.initial_state.keymap)
+       this.commit ();
+
+      if (this.commit_key_head > 0)
+       {
+         this.keys.val.splice (0, this.commit_key_head);
+         this.key_head -= this.commit_key_head;
+         this.state_key_head -= this.commit_key_head;
+         this.commit_key_head = 0;
+       }
       if (this.key_unhandled)
        {
          this.keys.val.length = 0;
+         //this.keys.val.splice (0, this.keys.val.length);
          this.key_head = this.state_key_head = this.commit_key_head = 0;
        }
+      if (this.changed & MIM.ChangedStatus.Candidate)
+       {
+         if (this.candidate_show && this.candidates)
+           MIM.show (this);
+         else
+           MIM.hide (this);
+       }
       return (! this.key_unhandled
-             && this.produced.length == 0
-             && this.preedit.length == 0);
+             && this.produced.length == 0);
     }
   }
 
@@ -2818,22 +3019,64 @@ MIM.im_domain.DefType (MIM.State.prototype);
   keys[0x90] = "numlock";
   keys[0xF0] = "capslock";
 
+  var keyids = {};
+  keyids['U+0008'] = 'Backspace';
+  keyids['U+0009'] = 'Tab';
+  keyids['U+0018'] = 'Cancel';
+  keyids['U+001B'] = 'Escape';
+  keyids['U+0020'] = 'Space';
+  keyids['U+007F'] = 'Delete';
+
+  var modifiers = {}
+  modifiers.Shift = 1;
+  modifiers.Control = 1;
+  modifiers.Alt = 1;
+  modifiers.AltGraph = 1;
+  modifiers.Meta = 1
+
   MIM.decode_key_event = function (event)
   {
-    var key = ((event.type == 'keydown' || event.keyCode) ? event.keyCode
+    var key = event.keyIdentifier;
+
+    if (key)                   // keydown event of Chrome
+      {
+       if (modifiers[key])
+         return false;
+       var mod = '';
+       if (event.ctrlKey) mod += 'C-';
+       if (event.metaKey) mod += 'M-';
+       if (event.altKey) mod += 'A-';
+       var keysym = keyids[key];
+       if (keysym)
+         key = keysym;
+       else if (key.match(/^U\+([0-9A-Z]+)$/))
+         {
+           if (mod.length == 0)
+             return false;
+           key = String.fromCharCode (parseInt (RegExp.$1, 16));
+         }
+       //else
+       //key = key.toLowerCase ();
+       if (event.shiftKey) mod += 'S-';
+       return new MIM.Key (mod + key);
+      }
+    else
+      {
+       key = ((event.type == 'keydown' || event.keyCode) ? event.keyCode
               : event.charCode ? event.charCode
               : false);
-    if (! key)
-      return false;
-    if (event.type == 'keydown')
-      {
-       key = keys[key];
        if (! key)
          return false;
-       if (event.shiftKey) key = "S-" + key ;
+       if (event.type == 'keydown')
+         {
+           key = keys[key];
+           if (! key)
+             return false;
+           if (event.shiftKey) key = "S-" + key ;
+         }
+       else
+         key = String.fromCharCode (key);
       }
-    else
-      key = String.fromCharCode (key);
     if (event.altKey) key = "A-" + key ;
     if (event.ctrlKey) key = "C-" + key ;
     return new MIM.Key (key);
@@ -2864,10 +3107,10 @@ MIM.debug_print = function (event, ic)
   if (! MIM.debug_nodes)
     {
       MIM.debug_nodes = new Array ();
-      MIM.debug_nodes['keydown'] = document.getElementById ('keydown');
-      MIM.debug_nodes['keypress'] = document.getElementById ('keypress');
       MIM.debug_nodes['status0'] = document.getElementById ('status0');
       MIM.debug_nodes['status1'] = document.getElementById ('status1');
+      MIM.debug_nodes['keydown'] = document.getElementById ('keydown');
+      MIM.debug_nodes['keypress'] = document.getElementById ('keypress');
       MIM.debug_nodes['keymap0'] = document.getElementById ('keymap0');
       MIM.debug_nodes['keymap1'] = document.getElementById ('keymap1');
       MIM.debug_nodes['preedit0'] = document.getElementById ('preedit0');
@@ -2875,11 +3118,11 @@ MIM.debug_print = function (event, ic)
     }
   var target = event.target;
   var code = event.keyCode;
-  var ch = event.type == 'keydown' ? 0 : event.charCode;
+  var ch = event.type == 'keypress' ? event.charCode : 0;
   var key = MIM.decode_key_event (event);
   var index;
 
-  MIM.debug_nodes[event.type].innerHTML = "" + code + "/" + ch + " : " + key;
+  MIM.debug_nodes[event.type].innerHTML = "" + code + "/" + ch + ":" + key + '/' + event.keyIdentifier;
   index = (event.type == 'keydown' ? '0' : '1');
   if (ic)
     MIM.debug_nodes['status' + index].innerHTML = ic.im.load_status;
@@ -2951,16 +3194,148 @@ MIM.update = function (target, ic)
   MIM.set_caret (target, ic);
 };
 
-MIM.reset_ic = function (event)
+(function () {
+  var style_props = {
+    width: 'width',
+    height: 'height',
+    padingLeft: 'padding-left',
+    paddingRight: 'padding-right',
+    paddingTop: 'padding-top',
+    paddintBottom: 'padding-bottom', 
+    borderLeftStyle: 'border-left-style',
+    borderRightStyle: 'border-right-style',
+    borderTopStyle: 'border-top-style',
+    borderBottomStyle: 'border-bottom-style',
+    borderLeftWidth: 'border-left-width',
+    borderRightWidth: 'border-right-width',
+    borderTopWidth: 'border-top-width',
+    borderBottomWidth: 'border-bottom-width',
+    fontFamily: 'font-family',
+    fontSize: 'font-size',
+    lineHeight: 'line-height',
+    letterSpacing: 'letter-spacing',
+    wordSpacing: 'word-spacing' };
+
+  function copy_style (from, to)
+  {
+    var from_style = getComputedStyle(from,'');
+    for(var name in style_props)
+      to.style[name] = from_style.getPropertyValue (style_props[name]);
+    to.style.left = from.offsetLeft + 'px'; 
+    to.style.top = from.offsetTop + 'px';
+    to.style.width = from.offsetWidth;
+    to.style.height = from.offsetHeight;
+  }
+
+  MIM.show = function (ic)
+  {
+    if (! ic.candidates)
+      return;
+    var target = ic.target;
+    if (! ic.div_node)
+      {
+       ic.target_top = 0;
+       ic.target_left = 0;
+       for (var elm = ic.target.offsetParent; elm; elm = elm.offsetParent)
+         {
+           ic.target_top += elm.offsetTop;
+           ic.target_left += elm.offsetLeft;
+         }
+       ic.div_node = document.createElement ('div');
+       copy_style (target, ic.div_node);
+       ic.div_node.style.visibility="hidden";
+       ic.div_node.style.position = "absolute";
+       document.getElementsByTagName ('body')[0].appendChild (ic.div_node);
+       ic.div_node_first = document.createElement ('span');
+       ic.div_node_last = document.createElement('span');
+       ic.div_node_last.innerHTML = '.';
+       ic.div_node.appendChild (ic.div_node_first);
+       ic.div_node.appendChild (ic.div_node_last);
+       ic.can_node = document.createElement ('table');
+       ic.can_node.style.position = 'absolute';
+       ic.can_node.style.display = 'none';
+       ic.can_node.style.backgroundColor = "white";
+       ic.can_node.style.border = "1px solid black";
+       document.getElementsByTagName ('body')[0].appendChild (ic.can_node);
+      }
+
+    if (ic.changed & MIM.ChangedStatus.CandidateList)
+      {
+       while (ic.can_node.childNodes.length > 0)
+         ic.can_node.removeChild (ic.can_node.firstChild);
+       var tr = document.createElement ('tr');
+       var group = ic.candidates.CurrentGroup ();
+       var td = document.createElement ('td');
+       td.innerHTML = group[0][1] + '/' + group[0][0];
+       td.style.color = 'white';
+       td.style.backgroundColor = 'black';
+       tr.appendChild (td);
+       for (var i = 1; i < group.length; i++)
+         {
+           var td = document.createElement ('td');
+           td.noWrap = true;
+           td.innerHTML = (i < 10 ? i : i == 10 ? '0' : String.fromCharCode (0x60 + (i - 10))) + '.' + group[i];
+           if (i == group[0][2] + 1)
+             td.style.backgroundColor = 'lightblue';
+           tr.appendChild (td);
+         }
+       ic.can_node.appendChild (tr);
+       ic.div_node_first.innerHTML = target.value.substr (0, ic.range[0]);
+       var x = ic.target_left + ic.div_node.lastChild.offsetLeft;
+       var y = (ic.target_top + ic.div_node.lastChild.offsetTop
+                + ic.div_node.lastChild.offsetHeight - target.scrollTop + 10);
+       ic.can_node.style.left = x + 'px';
+       ic.can_node.style.top = y + 'px';
+      }
+    else
+      {
+       var td = ic.can_node.firstElement ().firstElement ().nextElement ();
+       var col = ic.candidates.CurrentCol ();
+       for (var i = 0; td; td = td.nextElement ())
+         td.style.backgroundColor = (i++ == col ? 'lightblue' : 'white');
+      }
+    ic.can_node.style.display = 'block';
+  }
+
+  MIM.hide = function (ic)
+  {
+    if (ic.can_node)
+      ic.can_node.style.display = 'none';
+  }
+}) ();
+
+MIM.focus_in = function (event)
 {
-  if (event.target.mim_ic)
+  var target = event.target;
+  var ic = target.mim_ic;
+  if (ic.wait_update == true)
     {
-      var target = event.target;
-      var ic = target.mim_ic;
-      if (ic.preedit.length > 0)
-       event.target.setSelectionRange (ic.range[1], ic.range[1]);
-      ic.reset ();
+      Xex.Log ("Focus in " + target.tagName + ' IGNORED');
+      event.preventDefault ();
+      return false;
     }
+  Xex.Log ("Focus in " + target.tagName);
+  ic.Filter (MIM.Key.FocusIn);
+  function up () {MIM.update (target, ic);}
+  setTimeout (up, 100);
+}
+
+MIM.focus_out = function (event)
+{
+  var target = event.target;
+  var ic = target.mim_ic;
+  function reset_update () { ic.wait_update = false; };
+  if (ic.wait_update == true)
+    {
+      Xex.Log ("Focus out " + target.tagName + ' IGNORED');
+      event.preventDefault ();
+      return false;
+    }
+  Xex.Log ("Focus out " + target.tagName);
+  ic.Filter (MIM.Key.FocusOut);
+  ic.wait_update = true;
+  MIM.update (target, ic, true);
+  setTimeout (reset_update, 1000);
 };
 
 MIM.keydown = function (event)
@@ -2980,7 +3355,8 @@ MIM.keydown = function (event)
       if (ic.im.load_status != MIM.LoadStatus.Loaded)
        return;
       target.mim_ic = ic;
-      MIM.add_event_listener (target, 'blur', MIM.reset_ic);
+      MIM.add_event_listener (target, 'focus', MIM.focus_in);
+      MIM.add_event_listener (target, 'blur', MIM.focus_out);
       MIM.get_range (target, ic)
     }
   else
@@ -2990,6 +3366,18 @@ MIM.keydown = function (event)
     }
   MIM.debug_print (event, ic);
   ic.key = MIM.decode_key_event (event);
+  if (ic.key)
+    {
+      try {
+       var result = ic.Filter (ic.key);
+      } catch (e) {
+       Xex.Log ('Error' + e);
+       throw (e);
+      }
+      MIM.update (target, ic);
+      if (! ic.key_unhandled)
+       event.preventDefault ();
+    }
 };
 
 MIM.keypress = function (event)
@@ -3014,77 +3402,279 @@ MIM.keypress = function (event)
        return;
       }
     
-    Xex.Log ("filtering " + ic.key);
-    var result = ic.Filter (ic.key);
+    try {
+      var result = ic.Filter (ic.key);
+    } catch (e) {
+      Xex.Log ('Error:' + e);
+      throw (e);
+    }
     MIM.update (target, ic);
     if (! ic.key_unhandled)
       event.preventDefault ();
+  } catch (e) {
+    Xex.Log ("error:" + e);
+    event.preventDefault ();
   } finally {
     MIM.debug_print (event, ic);
   }
+
   return;
 };
 
-MIM.select_im = function (event)
-{
-  var target = event.target.parentNode;
-  while (target.tagName != "SELECT")
-    target = target.parentNode;
-  var idx = 0;
-  var im = false;
-  for (var lang in MIM.imlist)
-    for (var name in MIM.imlist[lang])
-      if (idx++ == target.selectedIndex)
-       {
-         im = MIM.imlist[lang][name];
-         break;
-       }
-  document.getElementsByTagName ('body')[0].removeChild (target);
-  target.target.focus ();
-  if (im && im != MIM.current)
-    {
-      MIM.current = im;
-      Xex.Log ('select IM: ' + im.name);
-    }
-};
+(function () {
+  var lang_category = {
+    European: {
+      cs: { name: 'Czech' },
+      da: { name: 'Danish' },
+      el: { name: 'Greek' },
+      en: { name: 'English' },
+      eo: { name: 'Esperanto' },
+      fr: { name: 'French' },
+      grc: { name: 'ClassicGreek' },
+      hr: { name: 'Croatian' },
+      hy: { name: 'Armenian' },
+      ka: { name: 'Georgian' },
+      kk: { name: 'Kazakh' },
+      ru: { name: 'Russian' },
+      sk: { name: 'Slovak' },
+      sr: { name: 'Serbian' },
+      sv: { name: 'Swedish' },
+      yi: { name: 'Yiddish' } },
+    MiddleEast: {
+      ar: { name: 'Arabic' },
+      dv: { name: 'Divehi' },
+      fa: { name: 'Persian' },
+      he: { name: 'Hebrew' },
+      kk: { name: 'Kazakh' },
+      ps: { name: 'Pushto' },
+      ug: { name: 'Uighur' },
+      yi: { name: 'Yiddish' } },
+    SouthAsia: {
+      as: { name: 'Assamese' },
+      bn: { name: 'Bengali' },
+      bo: { name: 'Tibetan' },
+      gu: { name: 'Gujarati' },
+      hi: { name: 'Hindi' },
+      kn: { name: 'Kannada' },
+      ks: { name: 'Kashmiri' },
+      ml: { name: 'Malayalam' },
+      mr: { name: 'Marathi' },
+      ne: { name: 'Nepali' },
+      or: { name: 'Oriya' },
+      pa: { name: 'Panjabi' },
+      sa: { name: 'Sanskirit' },
+      sd: { name: 'Sindhi' },
+      si: { name: 'Sinhalese' },
+      ta: { name: 'Tamil' },
+      te: { name: 'Telugu' },
+      ur: { name: 'Urdu' } },
+    SouthEastAsia: {
+      cmc: { name: 'Cham' },
+      km: { name: 'Khmer'},
+      lo: { name: 'Lao' },
+      my: { name: 'Burmese' },
+      tai: { name: 'Tai Viet' },
+      th: { name: 'Thai' },
+      vi: { name: 'Vietanamese' } },
+    EastAsia: {
+      ii: { name: 'Yii' },
+      ja: { name: 'Japanese' },
+      ko: { name: 'Korean' },
+      zh: { name: 'Chinese' } },
+    Other: {
+      am: { name:  'Amharic' },
+      ath: { name: 'Carrier' },
+      bla: { name: 'Blackfoot' },
+      cr: { name: 'Cree' },
+      eo: { name: 'Esperanto' },
+      iu: { name: 'Inuktitut' },
+      nsk: { name: 'Naskapi' },
+      oj: { name: 'Ojibwe' },
+      t: { name: 'Generic' } }
+  };
+
+  function categorize_im ()
+  {
+    var cat, lang, list, name;
+    for (lang in MIM.imlist)
+      {
+       list = null;
+       for (cat in lang_category)
+         if (lang_category[cat][lang])
+           {
+             list = lang_category[cat][lang].list;
+             if (! list)
+               list = lang_category[cat][lang].list = {};
+             for (name in MIM.imlist[lang])
+               list[name] = MIM.imlist[lang][name];
+           }
+       if (! list)
+         for (name in MIM.imlist[lang])
+           Xex.Log ('no category ' + lang + '-' + name);
+      }
+  }
 
-MIM.destroy_menu = function (event)
-{
-  if (event.target.tagName == "SELECT")
-    document.getElementsByTagName ('body')[0].removeChild (event.target);
-};
+  var destroy_timer;
+  var last_target;
 
-MIM.select_menu = function (event)
-{
-  var target = event.target;
+  function destroy ()
+  {
+    clearTimeout (destroy_timer);
+    destroy_timer = null;
+    var target = document.getElementById ('mim-menu');
+    if (target)
+      {
+       for (; last_target && last_target.menu_level;
+            last_target = last_target.parentLi)
+         last_target.style.backgroundColor = 'white';
+       var nodes = target.getElementsByTagName ('ul');
+       for (var i = 0; i < nodes.length; i++)
+         nodes[i].style.visibility = 'hidden';
+       document.getElementsByTagName ('body')[0].removeChild (target);
+      }
+  }    
 
-  if (! ((target.type == "text" || target.type == "textarea")
-        && event.which == 1 && event.ctrlKey))
-    return;
+  function destroy_menu () {
+    if (! destroy_timer)
+      destroy_timer = setTimeout (destroy, 1000);
+  }
 
-  var sel = document.createElement ('select');
-  sel.onclick = MIM.select_im;
-  sel.onmouseout = MIM.destroy_menu;
-  sel.style.position='absolute';
-  sel.style.left = (event.clientX - 10) + "px";
-  sel.style.top = (event.clientY - 10) + "px";
-  sel.target = target;
-  var idx = 0;
-  for (var lang in MIM.imlist)
-    for (var name in MIM.imlist[lang])
-      {
-       var option = document.createElement ('option');
-       var imname = lang + "-" + name;
-       option.appendChild (document.createTextNode (imname));
-       option.value = imname;
-       sel.appendChild (option);
-       if (MIM.imlist[lang][name] == MIM.current)
-         sel.selectedIndex = idx;
-       idx++;
-      }
-  sel.size = idx;
-  document.getElementsByTagName ('body')[0].appendChild (sel);
-};
+  function show_submenu (event)
+  {
+    if (destroy_timer)
+      {
+       clearTimeout (destroy_timer);
+       destroy_timer = null;
+      }
+    var target = event.target;
+    if (! target.menu_level)
+      return;
+    if (last_target && target.parentLi != last_target)
+      {
+       last_target.style.backgroundColor = 'white';
+       if (target.menu_level < last_target.menu_level)
+         {
+           last_target = last_target.parentLi;
+           last_target.style.backgroundColor = 'white';
+         }
+       var uls = last_target.getElementsByTagName ('ul');
+       for (var i = 0; i < uls.length; i++)
+         uls[i].style.visibility = 'hidden';
+      }
+    last_target = target;
+    target.style.backgroundColor = 'yellow';
+    if (target.menu_level < 3)
+      {
+       target.lastChild.style.visibility = 'visible';
+       target.lastChild.style.left = target.clientWidth + 'px';
+      }
+    event.preventDefault ();   
+  }
+
+  function select_im (event)
+  {
+    var target = event.target;
+    if (target.im)
+      {
+       MIM.current = target.im;
+       destroy ();
+      }
+    event.preventDefault ();
+  }
+
+  function create_ul (visibility)
+  {
+    var ul = document.createElement ('ul');
+    ul.style.position = 'absolute';
+    ul.style.margin = '0px';
+    ul.style.padding = '0px';
+    ul.style.border = '1px solid gray';
+    ul.style.borderBottom = 'none';
+    ul.style.top = '-1px';
+    ul.style.backgroundColor = 'white';
+    ul.style.visibility = visibility;
+    return ul;
+  }
+
+  function create_li (level, text)
+  {
+    var li = document.createElement ('li');
+    li.style.position = 'relative';
+    li.style.margin = '0px';
+    li.style.padding = '1px';
+    li.style.borderBottom = '1px solid gray';
+    li.style.top = '0px';
+    li.style.listStyle = 'none';
+    li.menu_level = level;
+    var nobr = document.createElement ('nobr');
+    nobr.innerHTML = text;
+    li.appendChild (nobr);
+    return li;
+  }
+
+  var menu;
+
+  function create_menu (event)
+  {
+    var target = event.target;
+
+    if (! ((target.type == "text" || target.type == "textarea")
+          && event.which == 1 && event.ctrlKey))
+      return;
+    if (! menu)
+      {
+       categorize_im ();
+       menu = create_ul ('visible');
+       menu.style.fontFamily = 'sans-serif';
+       menu.style.fontWeight = 'bold';
+       menu.id = 'mim-menu';
+       menu.onclick = select_im;
+       menu.onmouseover = show_submenu;
+       menu.onmouseout = destroy_menu;
+       for (var catname in lang_category)
+         {
+           var cat = lang_category[catname];
+           var li = create_li (1, catname);
+           var sub = create_ul ('hidden');
+           for (var langname in cat)
+             {
+               var lang = cat[langname];
+               if (! lang.list)
+                 continue;
+               var sub_li = create_li (2, lang.name);
+               sub_li.parentLi = li;
+               var subsub = create_ul ('hidden');
+               for (var name in lang.list)
+                 {
+                   var im = lang.list[name];
+                   var subsub_li = create_li (3, im.name);
+                   subsub_li.parentLi = sub_li;
+                   subsub_li.im = im;
+                   subsub.appendChild (subsub_li);
+                 }
+               sub_li.appendChild (subsub);
+               sub.appendChild (sub_li);
+             }
+           li.appendChild (sub);
+           menu.appendChild (li);
+         }
+       lang_category = null;
+      }
+    menu.style.left = (event.clientX - 10) + "px";
+    menu.style.top = (event.clientY - 10) + "px";
+    document.getElementsByTagName ('body')[0].appendChild (menu);
+  };
+
+  MIM.init = function ()
+  {
+    MIM.add_event_listener (window, 'keydown', MIM.keydown);
+    MIM.add_event_listener (window, 'keypress', MIM.keypress);
+    MIM.add_event_listener (window, 'mousedown', create_menu);
+    if (window.location == 'http://localhost/mim/index.html')
+      MIM.server = 'http://localhost/mim';
+    MIM.current = MIM.imlist['zh']['py-gb'];
+  };
+}) ();
 
 MIM.test = function ()
 {
@@ -3109,19 +3699,10 @@ MIM.test = function ()
 }
 
 
-MIM.init = function ()
-{
-  MIM.add_event_listener (window, 'keydown', MIM.keydown);
-  MIM.add_event_listener (window, 'keypress', MIM.keypress);
-  MIM.add_event_listener (window, 'mousedown', MIM.select_menu);
-  if (window.location == 'http://localhost/mim/index.html')
-    MIM.server = 'http://localhost/mim';
-  MIM.current = MIM.imlist['vi']['telex'];
-};
-
 MIM.init_debug = function ()
 {
   MIM.debug = true;
   Xex.LogNode = document.getElementById ('log');
+  Xex.Log (null);
   MIM.init ();
 };