733d5d1294afc5e0b3739a90d7c6cbb247138ca0
[m17n/m17n-lib-js.git] / mim.js
1 // mim.js -- M17N Input Method driver
2 // Copyright (C) 2010
3 //   National Institute of Advanced Industrial Science and Technology (AIST)
4 //   Registration Number H15PRO112
5
6 // This file is part of the m17n database; a sub-part of the m17n
7 // library.
8
9 // The m17n library is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU Lesser General Public License
11 // as published by the Free Software Foundation; either version 2.1 of
12 // the License, or (at your option) any later version.
13
14 // The m17n library is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 // Lesser General Public License for more details.
18
19 // You should have received a copy of the GNU Lesser General Public
20 // License along with the m17n library; if not, write to the Free
21 // Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 // Boston, MA 02110-1301, USA.
23
24 // Please note that the code is not yet matured.
25
26 var MIM = {
27   // URL of the input method server.
28   server: "http://www.m17n.org/common/mim-js",
29   // Boolean flag to tell if MIM is active or not.
30   enabled: true,
31   // Boolean flag to tell if MIM is running in debug mode or not.
32   debug: false,
33   // List of main input methods.
34   imlist: {},
35   // List of extra input methods;
36   imextra: {},
37   // Global input method data
38   im_global: null,
39   // Currently selected input method.
40   current: false,
41
42   // enum
43   LoadStatus: { NotLoaded:0, Loading:1, Loaded:2, Error:-1 },
44   ChangedStatus: {
45     None:       0x00,
46     StateTitle: 0x01,
47     PreeditText:0x02,
48     CursorPos:  0x04,
49     CandidateList:0x08,
50     CandidateIndex:0x10,
51     CandidateShow:0x20,
52     Preedit:    0x06,           // PreeditText | CursorPos
53     Candidate:  0x38 // CandidateList | CandidateIndex | CandidateShow
54   },
55   KeyModifier: {
56     SL: 0x00400000,
57     SR: 0x00800000,
58     S:  0x00C00000,
59     CL: 0x01000000,
60     CR: 0x02000000,
61     C:  0x03000000,
62     AL: 0x04000000,
63     AR: 0x08000000,
64     A:  0x0C000000,
65     ML: 0x04000000,
66     MR: 0x08000000,
67     M:  0x0C000000,
68     G:  0x10000000,
69     s:  0x20000000,
70     H:  0x40000000,
71     High:       0x70000000,
72     All:        0x7FC00000
73   },
74   Error: {
75     ParseError: "parse-error"
76   }
77 };
78   
79 if (window.location.hostname == 'localhost')
80   MIM.server = 'http://localhost/mim';
81
82 (function () {
83   var keysyms = new Array ();
84   keysyms["bs"] = "BackSpace";
85   keysyms["lf"] = "Linefeed";
86   keysyms["cr"] = keysyms["enter"] = "Return";
87   keysyms["esc"] = "Escape";
88   keysyms["spc"] = "space";
89   keysyms["del"] = "Delete";
90
91   function decode_keysym (str) {
92     if (str.length == 1)
93       return str;
94     var parts = str.split ("-");
95     var len = parts.length, i;
96     var has_modifier = len > 1;
97
98     for (i = 0; i < len - 1; i++)
99       if (! MIM.KeyModifier.hasOwnProperty (parts[i]))
100         return false;
101     var key = parts[len - 1];
102     if (key.length > 1)
103       {
104         key = keysyms[key.toLowerCase ()];
105         if (key)
106           {
107             if (len > 1)
108               {
109                 str = parts[0];
110                 for (i = 1; i < len - 1; i++)
111                   str += '-' + parts[i];
112                 str += '-' + key;
113               }
114             else
115               str = key;
116           }
117       }
118     if (has_modifier)
119       {
120         parts = new Array ();
121         parts.push (str);
122         return parts;
123       }
124     return str;
125   }
126
127   MIM.Key = function (val)
128   {
129     this.key;
130     if (val instanceof Xex.Term)
131       this.key = val.val;
132     else if (typeof val == 'string' || val instanceof String)
133       {
134         this.key = decode_keysym (val);
135         if (! this.key)
136           throw new Xex.ErrTerm (MIM.Error.ParseError, "Invalid key: " + val);
137         if (this.key instanceof Array)
138           {
139             this.key = this.key[0];
140             this.has_modifier = true;
141           }
142       }
143     else if (typeof val == 'number' || val instanceof Number)
144       this.key = String.fromCharCode (val);
145     else
146       throw new Xex.ErrTerm (MIM.Error.ParseError, "Invalid key: " + val);
147   }
148
149   MIM.Key.prototype.toString = function () { return this.key; };
150
151   MIM.Key.FocusIn = new MIM.Key (new Xex.StrTerm ('input-focus-in'));
152   MIM.Key.FocusOut = new MIM.Key (new Xex.StrTerm ('input-focus-out'));
153   MIM.Key.FocusMove = new MIM.Key (new Xex.StrTerm ('input-focus-move'));
154 }) ();
155
156 (function () {
157   MIM.KeySeq = function (seq)
158   {
159     this.val = new Array ();
160
161     if (seq)
162       {
163         if (seq.IsList)
164           {
165             var len = seq.val.length;
166             for (var i = 0; i < len; i++)
167               {
168                 var v = seq.val[i], key;
169                 if (v.type == 'symbol' || v.type == 'string')
170                   key = new MIM.Key (v);
171                 else if (v.type == 'integer')
172                   key = new MIM.Key (v.val);
173                 else
174                   throw new Xex.ErrTerm (MIM.Error.ParseError,
175                                          "Invalid key: " + v);
176                 this.val.push (key);
177                 if (key.has_modifier)
178                   this.has_modifier = true;
179               }
180           }
181         else if (seq.IsStr)
182           {
183             var len = seq.val.length;
184             for (var i = 0; i < len; i++)
185               this.val.push (new MIM.Key (seq.val.charCodeAt (i)));
186           }
187         else
188           throw new Xex.ErrTerm (MIM.Error.ParseError, "Invalid key: " + seq);
189       }
190   }
191
192   var proto = new Xex.Term ('keyseq');
193   proto.Clone = function () { return this; }
194   proto.Parser = function (domain, node)
195   {
196     var seq = new Array ();
197     for (node = node.firstChild; node; node = node.nextSibling)
198       if (node.nodeType == 1)
199         {
200           var term = Xex.Term.Parse (domain, node);
201           return new MIM.KeySeq (term);
202         }
203     throw new Xex.ErrTerm (MIM.Error.ParseError, "Invalid keyseq");
204   }
205   proto.toString = function ()
206   {
207     var len = this.val.length;
208     if (len == 0)
209       return '<keyseq/>';
210     var first = true;
211     var str = '<keyseq>';
212     for (var i = 0; i < len; i++)
213       {
214         if (first)
215           first = false;
216         else if (this.has_modifier)
217           str += ' ';
218         str += this.val[i].toString ();
219       }
220     return str + '</keyseq>';
221   }
222
223   MIM.KeySeq.prototype = proto;
224 }) ();
225
226 (function () {
227   MIM.Marker = function () { }
228   MIM.Marker.prototype = new Xex.Term ('marker');
229   MIM.Marker.prototype.CharAt = function (ic)
230   {
231     var p = this.Position (ic);
232     if (p < 0 || p >= ic.preedit.length)
233       return 0;
234     return ic.preedit.charCodeAt (p);
235   }
236
237   MIM.FloatingMarker = function (name) { this.val = name; };
238   var proto = new MIM.Marker ();
239   MIM.FloatingMarker.prototype = proto;
240   proto.Position = function (ic) { return ic.marker_positions[this.val]; };
241   proto.Mark = function (ic) { ic.marker_positions[this.val] = ic.cursor_pos; };
242
243   MIM.PredefinedMarker = function (name) { this.val = name; }
244   MIM.PredefinedMarker.prototype = new MIM.Marker ();
245   MIM.PredefinedMarker.prototype.Position = function (ic)
246   {
247     if (typeof this.pos == 'number')
248       return this.pos;
249     return this.pos (ic);
250   }
251
252   var predefined = { }
253
254   function def_predefined (name, position)
255   {
256     predefined[name] = new MIM.PredefinedMarker (name);
257     predefined[name].pos = position;
258   }
259
260   def_predefined ('@<', 0);
261   def_predefined ('@>', function (ic) { return ic.preedit.length; });
262   def_predefined ('@-', function (ic) { return ic.cursor_pos - 1; });
263   def_predefined ('@+', function (ic) { return ic.cursor_pos + 1; });
264   def_predefined ('@[', function (ic) {
265     if (ic.cursor_pos > 0)
266       {
267         var pos = ic.cursor_pos;
268         return ic.preedit.FindProp ('candidates', pos - 1).from;
269       }
270     return 0;
271   });
272   def_predefined ('@]', function (ic) {
273     if (ic.cursor_pos < ic.preedit.length - 1)
274       {
275         var pos = ic.cursor_pos;
276         return ic.preedit.FindProp ('candidates', pos).to;
277       }
278     return ic.preedit.length;
279   });
280   for (var i = 0; i < 10; i++)
281     def_predefined ("@" + i, i);
282   predefined['@first'] = predefined['@<'];
283   predefined['@last'] = predefined['@>'];
284   predefined['@previous'] = predefined['@-'];
285   predefined['@next'] = predefined['@+'];
286   predefined['@previous-candidate-change'] = predefined['@['];
287   predefined['@next-candidate-change'] = predefined['@]'];
288
289   MIM.SurroundMarker = function (name)
290   {
291     this.val = name;
292     this.distance = parseInt (name.slice (1));
293     if (isNaN (this.distance))
294       throw new Xex.ErrTerm (MIM.Error.ParseError, "Invalid marker: " + name);
295   }
296   MIM.SurroundMarker.prototype = new MIM.Marker ();
297   MIM.SurroundMarker.prototype.Position = function (ic)
298   {
299     return ic.cursor_pos + this.distance;
300   }
301   MIM.SurroundMarker.prototype.CharAt = function (ic)
302   {
303     if (this.val == '@-0')
304       return -1;
305     var p = this.Position (ic);
306     if (p < 0)
307       return ic.GetSurroundingChar (p);
308     else if (p >= ic.preedit.length)
309       return ic.GetSurroundingChar (p - ic.preedit.length);
310     return ic.preedit.charCodeAt (p);
311   }
312
313   MIM.Marker.prototype.Parser = function (domain, node)
314   {
315     var name = node.firstChild.nodeValue;
316     if (name.charAt (0) == '@')
317       {
318         var n = predefined[name];
319         if (n)
320           return n;
321         if (name.charAt (1) == '-' || name.charAt (1) == '+')
322           return new MIM.SurroundMarker (name);
323         throw new Xex.ErrTerm (MIM.Error.ParseError,
324                                "Invalid marker: " + name);
325       }
326     return new MIM.FloatingMarker (name);;
327   }
328 }) ();
329
330 MIM.Selector = function (name)
331 {
332   this.val = name;
333 }
334 MIM.Selector.prototype = new Xex.Term ('selector');
335
336 (function () {
337   var selectors = {};
338   selectors["@<"] = selectors["@first"] = new MIM.Selector ('@<');
339   selectors["@="] = selectors["@current"] = new MIM.Selector ('@=');
340   selectors["@>"] = selectors["@last"] = new MIM.Selector ('@>');
341   selectors["@-"] = selectors["@previous"] = new MIM.Selector ('@-');
342   selectors["@+"] = selectors["@next"] = new MIM.Selector ('@+');
343   selectors["@["] = selectors["@previous-group"] = new MIM.Selector ('@[');
344   selectors["@]"] = selectors["@next-group"] = new MIM.Selector ('@]');
345
346   MIM.Selector.prototype.Parser = function (domain, node)
347   {
348     var name = node.firstChild.nodeValue;
349     var s = selectors[name];
350     if (! s)
351       throw new Xex.ErrTerm (MIM.Error.ParseError,
352                              "Invalid selector: " + name);
353     return s;
354   }
355 }) ();
356
357 MIM.Rule = function (keyseq, actions)
358 {
359   this.keyseq = keyseq;
360   if (actions)
361     this.actions = actions;
362 }
363 MIM.Rule.prototype = new Xex.Term ('rule');
364 MIM.Rule.prototype.Parser = function (domain, node)
365 {
366   var n;
367   for (n = node.firstChild; n && n.nodeType != 1; n = n.nextSibling);
368   if (! n)
369     throw new Xex.ErrTerm (MIM.Error.ParseError, "invalid rule:" + node);
370   var keyseq = Xex.Term.Parse (domain, n);
371   if (keyseq.type != 'keyseq')
372     throw new Xex.ErrTerm (MIM.Error.ParseError, "invalid rule:" + node);
373   var actions = null;
374   n = n.nextElement ();
375   if (n)
376     actions = Xex.Term.Parse (domain, n, null);
377   return new MIM.Rule (keyseq, actions);
378 }
379 MIM.Rule.prototype.toString = function ()
380 {
381   return '<rule/>';
382 }
383
384 MIM.Map = function (name)
385 {
386   this.name = name;
387   this.rules = new Array ();
388 };
389
390 (function () {
391   var proto = new Xex.Term ('map');
392
393   proto.Parser = function (domain, node)
394   {
395     var name = node.attributes['mname'].nodeValue;
396     if (! name)
397       throw new Xex.ErrTerm (MIM.Error.ParseError, "invalid map");
398     var map = new MIM.Map (name);
399     for (var n = node.firstChild; n; n = n.nextSibling)
400       if (n.nodeType == 1)
401         map.rules.push (Xex.Term.Parse (domain, n));
402     return map;
403   }
404
405   proto.toString = function ()
406   {
407     var str = '<map mname="' + this.name + '">';
408     var len = this.rules.length;
409     for (i = 0; i < len; i++)
410       str += this.rules[i];
411     return str + '</map>';
412   }
413
414   MIM.Map.prototype = proto;
415 }) ();
416
417 Xex.CatchTag._mimtag = new Xex.SymTerm ('@mimtag');
418
419 MIM.Action = function (domain, terms)
420 {
421   var args = new Array ();
422   args.push (Xex.CatchTag_.mimtag);
423   for (var i = 0; i < terms.length; i++)
424     args.push (terms[i]);
425   this.action = Xex.Funcall.prototype.New (domain, 'catch', null, args);
426 }
427
428 MIM.Action.prototype.Run = function (domain)
429 {
430   var result = this.action.Eval (domain);
431   if (result.type == 'error')
432     {
433       domain.context.Error = result.toString ();
434       return false;
435     }
436   return (result != Xex.CatchTag._mimtag);
437 }
438
439 MIM.Keymap = function ()
440 {
441   this.name = 'TOP';
442   this.submaps = null;
443 };
444
445 (function () {
446   var proto = {};
447
448   function add_rule (keymap, rule, branch_actions)
449   {
450     var keyseq = rule.keyseq;
451     var len = keyseq.val.length;
452     var name = '';
453
454     for (var i = 0; i < len; i++)
455       {
456         var key = keyseq.val[i];
457         var sub = false;
458
459         name += key.key;
460         if (! keymap.submaps)
461           keymap.submaps = {};
462         else
463           sub = keymap.submaps[key.key];
464         if (! sub)
465           keymap.submaps[key.key] = sub = new MIM.Keymap ();
466         keymap = sub;
467         keymap.name = name;
468       }
469     if (rule.actions)
470       keymap.map_actions = rule.actions;
471     if (branch_actions)
472       keymap.branch_actions = branch_actions;
473   }
474
475   proto.Add = function (map, branch_actions)
476   {
477     var rules = map.rules;
478     var len = rules.length;
479
480     for (var i = 0; i < len; i++)
481       add_rule (this, rules[i], branch_actions);
482   }
483   proto.Lookup = function (keys, index)
484   {
485     var sub;
486
487     if (index < keys.val.length && this.submaps
488         && ! keys.val[index])
489       {
490         Xex.Log ('invalid key at ' + index);
491         throw 'invalid key';
492       }
493
494     if (index < keys.val.length && this.submaps
495         && (sub = this.submaps[keys.val[index].key]))
496       {
497         index++;
498         return sub.Lookup (keys, index);
499       }
500     return { map: this, index: index };
501   }
502
503   MIM.Keymap.prototype = proto;
504 }) ();
505
506 MIM.State = function (name)
507 {
508   this.name = name;
509   this.keymap = new MIM.Keymap ();
510 };
511
512 (function () {
513   var proto = new Xex.Term ('state');
514
515   proto.Parser = function (domain, node)
516   {
517     var map_list = domain.map_list;
518     var name = node.attributes['sname'].nodeValue;
519     if (! name)
520       throw new Xex.ErrTerm (MIM.Error.ParseError, "invalid map");
521     var state = new MIM.State (name);
522     for (node = node.firstElement (); node; node = node.nextElement ())
523       {
524         if (node.nodeName == 'title')
525           state.title = node.firstChild.nodeValue;
526         else
527           {
528             var n = node.firstElement ();
529             var branch_actions = n ? Xex.Term.Parse (domain, n, null) : null;
530             if (node.nodeName == 'branch')
531               state.keymap.Add (map_list[node.attributes['mname'].nodeValue],
532                                 branch_actions);
533             else if (node.nodeName == 'state-hook')
534               state.keymap.map_actions = branch_actions;
535             else if (node.nodeName == 'catch-all-branch')
536               state.keymap.branch_actions = branch_actions;
537           }
538       }
539     return state;
540   }
541
542   proto.toString = function ()
543   {
544     return '<state sname="' + this.name + '">' + this.keymap + '</state>';
545   }
546
547   MIM.State.prototype = proto;
548 }) ();
549
550 (function () {
551   function Block (index, term)
552   {
553     this.Index = index;
554     if (term.IsStr)
555       this.Data = term.val;
556     else if (term.IsList)
557       {
558         this.Data = new Array ();
559         for (var i = 0; i < term.val.length; i++)
560           this.Data.push (term.val[i].val);
561       }
562   }
563
564   Block.prototype.Count = function () { return this.Data.length; }
565   Block.prototype.get = function (i)
566   {
567     return (this.Data instanceof Array ? this.Data[i] : this.Data.charAt (i));
568   }
569
570   MIM.Candidates = function (ic, candidates, column)
571   {
572     this.ic = ic;
573     this.column = column;
574     this.row = 0;
575     this.index = 0;
576     this.total = 0;
577     this.blocks = new Array ();
578
579     for (var i = 0; i < candidates.length; i++)
580       {
581         var block = new Block (this.total, candidates[i]);
582         this.blocks.push (block);
583         this.total += block.Count ();
584       }
585   }
586
587   function get_col ()
588   {
589     return (this.column > 0 ? this.index % this.column
590             : this.index - this.blocks[this.row].Index);
591   }
592
593   function prev_group ()
594   {
595     var col = get_col.call (this);
596     var nitems;
597     if (this.column > 0)
598       {
599         this.index -= this.column;
600         if (this.index >= 0)
601           nitems = this.column;
602         else
603           {
604             var lastcol = (this.total - 1) % this.column;
605             this.index = (col < lastcol ? this.total - lastcol + col
606                           : this.total - 1);
607             this.row = this.blocks.length - 1;
608             nitems = lastcol + 1;
609           }
610         while (this.blocks[this.row].Index > this.index)
611           this.row--;
612       }
613     else
614       {
615         this.row = this.row > 0 ? this.row - 1 : this.blocks.length - 1;
616         nitems = this.blocks[this.row].Count ();
617         this.index = (this.blocks[this.row].Index
618                       + (col < nitems ? col : nitems - 1));
619       }
620     return nitems;
621   }
622
623   function next_group ()
624   {
625     var col = get_col.call (this);
626     var nitems;
627     if (this.column > 0)
628       {
629         this.index += this.column - col;
630         if (this.index < this.total)
631           {
632             if (this.index + col >= this.total)
633               {
634                 nitems = this.total - this.index;
635                 this.index = this.total - 1;
636               }
637             else
638               {
639                 nitems = this.column;
640                 this.index += col;
641               }
642           }
643         else
644           {
645             this.index = col;
646             this.row = 0;
647           }
648         while (this.blocks[this.row].Index + this.blocks[this.row].Count ()
649                <= this.index)
650           this.row++;
651       }
652     else
653       {
654         this.row = this.row < this.blocks.length - 1 ? this.row + 1 : 0;
655         nitems = this.blocks[this.row].Count ();
656         this.index = (this.blocks[this.row].Index
657                       + (col < nitems ? col : nitems - 1));
658       }
659     return nitems;
660   }
661
662   function prev ()
663   {
664     if (this.index == 0)
665       {
666         this.index = this.total - 1;
667         this.row = this.blocks.length - 1;
668       }
669     else
670       {
671         this.index--;
672         if (this.blocks[this.row].Index > this.index)
673           this.row--;
674       }
675     }
676
677   function next ()
678   {
679     this.index++;
680     if (this.index == this.total)
681       {
682         this.index = 0;
683         this.row = 0;
684       }
685     else
686       {
687         var b = this.blocks[this.row];
688         if (this.index == b.Index + b.Count ())
689           this.row++;
690       }
691   }
692
693   function first ()
694   {
695     this.index -= get_col.call (this);
696     while (this.blocks[this.row].Index > this.index)
697       this.row--;
698   }
699
700   function last ()
701   {
702     var b = this.blocks[this.row];
703     if (this.column > 0)
704       {
705         if (this.index + 1 < this.total)
706           {
707             this.index += this.column - get_col.call (this) + 1;
708             while (b.Index + b.Count () <= this.index)
709               b = this.blocks[++this.row];
710           }
711       }
712     else
713       this.index = b.Index + b.Count () - 1;
714   }
715
716   MIM.Candidates.prototype.Current = function ()
717   {
718     var b = this.blocks[this.row];
719     return b.get (this.index - b.Index);
720   }
721
722   MIM.Candidates.prototype.Select = function (selector)
723   {
724     var idx = this.index;
725     var gidx = this.column > 0 ? idx / this.column : this.row;
726     if (selector.type == 'selector')
727       {
728         switch (selector.val)
729           {
730           case '@<': first.call (this); break;
731           case '@>': last.call (this); break;
732           case '@-': prev.call (this); break;
733           case '@+': next.call (this); break;
734           case '@[': prev_group.call (this); break;
735           case '@]': next_group.call (this); break;
736           default: break;
737           }
738       }
739     else
740       {
741         var col, start, end
742         if (this.column > 0)
743           {
744             col = this.index % this.column;
745             start = this.index - col;
746             end = start + this.column;
747           }
748         else
749           {
750             start = this.blocks[this.row].Index;
751             col = this.index - start;
752             end = start + this.blocks[this.row].Count;
753           }
754         if (end > this.total)
755           end = this.total;
756         this.index += selector.val - col;
757         if (this.index >= end)
758           this.index = end - 1;
759         if (this.column > 0)
760           {
761             if (selector.val > col)
762               while (this.blocks[this.row].Index + this.blocks[this.row].Count
763                      < this.index)
764                 this.row++;
765             else
766               while (this.blocks[this.row].Index > this.index)
767                 this.row--;
768           }
769       }
770     var newgidx = this.column > 0 ? this.index / this.column : this.row;
771     if (this.index != idx)
772       this.ic.changed |= (gidx == newgidx
773                           ? MIM.ChangedStatus.CandidateIndex
774                           : MIM.ChangedStatus.CandidateList);
775     return this.Current ();
776   }
777
778   MIM.Candidates.prototype.CurrentCol = function ()
779   {
780     return get_col.call (this);
781   }
782
783   MIM.Candidates.prototype.CurrentGroup = function ()
784   {
785     var col, start, end, gnum, gidx;
786     if (this.column > 0)
787       {
788         gnum = Math.floor ((this.total - 1) / this.column) + 1;
789         col = this.index % this.column;
790         start = this.index - col;
791         gidx = start / this.column + 1;
792         end = start + this.column;
793         if (end > this.total)
794           end = this.total;
795       }
796     else
797       {
798         gnum = this.blocks.length;
799         gidx = this.row + 1;
800         start = this.blocks[this.row].Index;
801         col = this.index - start;
802         end = start + this.blocks[this.row].Count ();
803       }
804     var group = new Array ();
805     var indices = new Array (gnum, gidx, col);
806     group.push (indices);
807     var row = this.row;
808     var block = this.blocks[row++];
809     while (start < end)
810       {
811         var c = block.get (start - block.Index);
812         group.push (c);
813         start++;
814         if (start == block.Index + block.Count ())
815           block = this.blocks[row++];
816       }
817     return group;
818   }
819 }) ();
820
821 MIM.im_domain = new Xex.Domain ('input-method', null, null);
822 MIM.im_domain.DefType (MIM.KeySeq.prototype);
823 MIM.im_domain.DefType (MIM.Marker.prototype);
824 MIM.im_domain.DefType (MIM.Selector.prototype);
825 MIM.im_domain.DefType (MIM.Rule.prototype);
826 MIM.im_domain.DefType (MIM.Map.prototype);
827 MIM.im_domain.DefType (MIM.State.prototype);
828
829 (function () {
830   var im_domain = MIM.im_domain;
831
832   function Finsert (domain, vari, args)
833   {
834     var text;
835     if (args[0].type == 'integer')
836       text = String.fromCharCode (args[0].val);
837     else
838       text = args[0].val;
839     domain.context.ins (text, null);
840     return args[0];
841   }
842
843   function Finsert_candidates (domain, vari, args)
844   {
845     var ic = domain.context;
846     var gsize = domain.variables['candidates-group-size'];
847     var candidates = new MIM.Candidates (ic, args,
848                                          gsize ? gsize.val.Intval () : 0);
849     ic.ins (candidates.Current (), candidates);
850     return args[0];
851   }
852
853   function Fdelete (domain, vari, args)
854   {
855     var ic = domain.context;
856     var pos = args[0].IsInt ? args[0].Intval () : args[0].Position (ic);
857     return new Xex.IntTerm (ic.del (pos));
858   }
859
860   function Fselect (domain, vari, args)
861   {
862     var ic = domain.context;
863     var can = ic.candidates;
864
865     if (can)
866       {
867         var old_text = can.Current ();
868         var new_text = can.Select (args[0]);
869         ic.rep (old_text, new_text, can);
870       }
871     else
872       Xex.Log ('no candidates at ' + ic.cursor_pos + ' of ' + ic.candidate_table.table.length);
873     return args[0];
874   }
875
876   function Fshow (domain, vari, args)
877   {
878     domain.context.candidate_show = true;
879     domain.context.changed |= MIM.ChangedStatus.CandidateShow;
880     return Xex.nil;
881   }
882
883   function Fhide (domain, vari, args)
884   {
885     domain.context.candidate_show = false;
886     domain.context.changed |= MIM.ChangedStatus.CandidateShow;
887     return Xex.nil;
888   }
889
890   function Fchar_at (domain, vari, args)
891   {
892     return new Xex.IntTerm (args[0].CharAt (domain.context));
893   }
894
895   function Fmove (domain, vari, args)
896   {
897     var ic = domain.context;
898     var pos = args[0].IsInt ? args[0].val : args[0].Position (ic);
899     ic.move (pos);
900     return new Xex.IntTerm (pos);
901   }
902
903   function Fmark (domain, vari, args)
904   {
905     args[0].Mark (domain.context);
906     return args[0];
907   }
908
909   function Fpushback (domain, vari, args)
910   {
911     var a = (args[0].IsInt ? args[0].Intval ()
912              : args[0].IsStr ? new KeySeq (args[0])
913              : args[0]);
914     domain.context.pushback (a);
915     return args[0];
916   }
917
918   function Fpop (domain, vari, args)
919   {
920     var ic = domain.context;
921     if (ic.key_head < ic.keys.val.length)
922       ic.keys.val.splice (ic.keys_head, 1);
923     return Xex.nil;
924   }
925
926   function Fundo  (domain, vari, args)
927   {
928     var ic = domain.context;
929     var n = args.length == 0 ? -2 : args[0].val;
930     Xex.Log ('undo with arg ' + args[0]);
931     if (n < 0)
932       ic.keys.val.splice (ic.keys.val.length + n, -n);
933     else
934       ic.keys.val.splice (n, ic.keys.val.length);
935     ic.reset (false);
936     return Xex.nil;
937   }
938
939   function Fcommit (domain, vari, args)
940   {
941     domain.context.commit ();
942     return Xex.nil;
943   }
944
945   function Funhandle (domain, vari, args)
946   {
947     domain.context.commit ();
948     return Xex.Fthrow (domain, vari, Xex.CatchTag._mimtag);
949   }
950
951   function Fshift (domain, vari, args)
952   {
953     var ic = domain.context;
954     var state_name = args[0].val;
955     var state = ic.im.state_list[state_name];
956     if (! state)
957       throw ("Unknown state: " + state_name);
958       ic.shift (state);
959     return args[0];
960   }
961
962   function Fshiftback (domain, vari, args)
963   {
964     domain.context.shift (null);
965     return Xex.nil;
966   }
967
968   function Fkey_count (domain, vari, args)
969   {
970     return new Xex.IntTerm (domain.context.key_head);
971   }
972
973   function Fsurrounding_flag (domain, vari, args)
974   {
975     return new Xex.IntTerm (-1);
976   }
977
978   im_domain.DefSubr (Finsert, "insert", false, 1, 1);
979   im_domain.DefSubr (Finsert_candidates, "insert-candidates", false, 1, 1);
980   im_domain.DefSubr (Fdelete, "delete", false, 1, 1);
981   im_domain.DefSubr (Fselect, "select", false, 1, 1);
982   im_domain.DefSubr (Fshow, "show-candidates", false, 0, 0);
983   im_domain.DefSubr (Fhide, "hide-candidates", false, 0, 0);
984   im_domain.DefSubr (Fmove, "move", false, 1, 1);
985   im_domain.DefSubr (Fmark, "mark", false, 1, 1);
986   im_domain.DefSubr (Fpushback, "pushback", false, 1, 1);
987   im_domain.DefSubr (Fpop, "pop", false, 0, 0);
988   im_domain.DefSubr (Fundo, "undo", false, 0, 1);
989   im_domain.DefSubr (Fcommit, "commit", false, 0, 0);
990   im_domain.DefSubr (Funhandle, "unhandle", false, 0, 0);
991   im_domain.DefSubr (Fshift, "shift", false, 1, 1);
992   im_domain.DefSubr (Fshiftback, "shiftback", false, 0, 0);
993   im_domain.DefSubr (Fchar_at, "char-at", false, 1, 1);
994   im_domain.DefSubr (Fkey_count, "key-count", false, 0, 0);
995   im_domain.DefSubr (Fsurrounding_flag, "surrounding-text-flag", false, 0, 0);
996 }) ();
997
998
999 (function () {
1000   function get_global_var (vname)
1001   {
1002     if (MIM.im_global.load_status == MIM.LoadStatus.NotLoaded)
1003       MIM.im_global.Load ()
1004     return MIM.im_global.domain.variables[vname];
1005   }
1006
1007   function include (node)
1008   {
1009     node = node.firstElement ();
1010     if (node.nodeName != 'tags')
1011       return null;
1012     
1013     var lang = null, name = null, extra = null;
1014     for (node = node.firstElement (); node; node = node.nextElement ())
1015       {
1016         if (node.nodeName == 'language')
1017           lang = node.firstChild.nodeValue;
1018         else if (node.nodeName == 'name')
1019           name = node.firstChild.nodeValue;
1020         else if (node.nodeName == 'extra-id')
1021           extra = node.firstChild.nodeValue;
1022       }
1023     if (! lang || ! MIM.imlist[lang])
1024       return null;
1025     if (! extra)
1026       {
1027         if (! name || ! (im = MIM.imlist[lang][name]))
1028           return null;
1029       }
1030     else
1031       {
1032         if (! (im = MIM.imextra[lang][extra]))
1033           return null;
1034       }
1035     if (im.load_status != MIM.LoadStatus.Loaded
1036         && (im.load_status != MIM.LoadStatus.NotLoaded || ! im.Load ()))
1037       return null;
1038     return im;
1039   }
1040
1041   var parsers = { };
1042
1043   parsers['description'] = function (node)
1044   {
1045     this.description = node.firstChild.nodeValue;
1046   }
1047   parsers['variable-list'] = function (node)
1048   {
1049     for (node = node.firstElement (); node; node = node.nextElement ())
1050       {
1051         var vname = node.attributes['vname'].nodeValue;
1052         if (this != MIM.im_global)
1053           {
1054             var vari = get_global_var (vname);
1055             if (vari != null)
1056               this.domain.Defvar (vname, vari.desc, vari.val, vari.range);
1057           }
1058         vname = Xex.Term.Parse (this.domain, node)
1059       }
1060   }
1061   parsers['command-list'] = function (node)
1062   {
1063   }
1064   parsers['macro-list'] = function (node)
1065   {
1066     for (var n = node.firstElement (); n; n = n.nextElement ())
1067       if (n.nodeName == 'xi:include')
1068         {
1069           var im = include (n);
1070           if (! im)
1071             alert ('inclusion fail');
1072           else
1073             for (var macro in im.domain.functions)
1074               {
1075                 var func = im.domain.functions[macro];
1076                 if (func instanceof Xex.Macro)
1077                   im.domain.CopyFunc (this.domain, macro);
1078               }
1079           n = n.previousSibling;
1080           node.removeChild (n.nextSibling);
1081         }
1082     Xex.Term.Parse (this.domain, node.firstElement (), null);
1083   }
1084   parsers['title'] = function (node)
1085   {
1086     this.title = node.firstChild.nodeValue;
1087   }
1088   parsers['map-list'] = function (node)
1089   {
1090     for (node = node.firstElement (); node; node = node.nextElement ())
1091       {
1092         if (node.nodeName == 'xi:include')
1093           {
1094             var im = include (node);
1095             if (! im)
1096               {
1097                 alert ('inclusion fail');
1098                 continue;
1099               }
1100             for (var mname in im.map_list)
1101               this.map_list[mname] = im.map_list[mname];
1102           }
1103         else
1104           {
1105             var map = Xex.Term.Parse (this.domain, node);
1106             this.map_list[map.name] = map;
1107           }
1108       }
1109   }
1110   parsers['state-list'] = function (node)
1111   {
1112     this.domain.map_list = this.map_list;
1113     for (node = node.firstElement (); node; node = node.nextElement ())
1114       {
1115         if (node.nodeName == 'xi:include')
1116           {
1117             var im = include (node);
1118             if (! im)
1119               alert ('inclusion fail');
1120             for (var sname in im.state_list)
1121               {
1122                 state = im.state_list[sname];
1123                 if (! this.initial_state)
1124                   this.initial_state = state;
1125                 this.state_list[sname] = state;
1126               }
1127           }
1128         else if (node.nodeName == 'state')
1129           {
1130             var state = Xex.Term.Parse (this.domain, node);
1131             if (! state.title)
1132               state.title = this.title;
1133             if (! this.initial_state)
1134               this.initial_state = state;
1135             this.state_list[state.name] = state;
1136           }
1137       }
1138     delete this.domain.map_list;
1139   }
1140
1141   MIM.IM = function (lang, name, extra_id, file)
1142   {
1143     this.lang = lang;
1144     this.name = name;
1145     this.extra_id = extra_id;
1146     this.file = file;
1147     this.load_status = MIM.LoadStatus.NotLoaded;
1148     this.domain = new Xex.Domain (this.lang + '-'
1149                                   + (this.name != 'nil'
1150                                      ? this.name : this.extra_id),
1151                                   MIM.im_domain, null);
1152   }
1153
1154   var proto = {
1155     Load: function ()
1156     {
1157       var node = Xex.Load (MIM.server, this.file);
1158       if (! node)
1159         {
1160           this.load_status = MIM.LoadStatus.Error;
1161           return false;
1162         }
1163       this.map_list = {};
1164       this.initial_state = null;
1165       this.state_list = {};
1166       for (node = node.firstElement (); node; node = node.nextElement ())
1167         {
1168           var name = node.nodeName;
1169           var parser = parsers[name];
1170           if (parser)
1171             parser.call (this, node);
1172         }
1173       this.load_status = MIM.LoadStatus.Loaded;
1174       return true;
1175     }
1176   }
1177
1178   MIM.IM.prototype = proto;
1179
1180   MIM.IC = function (im, target)
1181   {
1182     if (im.load_status == MIM.LoadStatus.NotLoaded)
1183       im.Load ();
1184     if (im.load_status != MIM.LoadStatus.Loaded)
1185       alert ('im:' + im.name + ' error:' + im.load_status);
1186     this.im = im;
1187     this.target = target;
1188     this.domain = new Xex.Domain ('context', im.domain, this);
1189     this.active = true;
1190     this.range = new Array ();
1191     this.range[0] = this.range[1] = 0;
1192     this.state = null;
1193     this.initial_state = this.im.initial_state;
1194     this.keys = new MIM.KeySeq ();
1195     this.marker_positions = new Array ();
1196     this.candidate_table = new MIM.CandidateTable ();
1197     this.reset (false);
1198   }
1199
1200   MIM.CandidateTable = function ()
1201   {
1202     this.table = new Array ();
1203   }
1204
1205   MIM.CandidateTable.prototype.get = function (pos)
1206   {
1207     for (var i = 0; i < this.table.length; i++)
1208       {
1209         var elt = this.table[i];
1210         if (elt.from < pos && pos <= elt.to)
1211           return elt.val;
1212       }
1213   }
1214
1215   MIM.CandidateTable.prototype.put = function (from, to, candidates)
1216   {
1217     for (var i = 0; i < this.table.length; i++)
1218       {
1219         var elt = this.table[i];
1220         if (elt.from < to && elt.to > from)
1221           {
1222             elt.from = from;
1223             elt.to = to;
1224             elt.val = candidates;
1225             return;
1226           }
1227       }
1228     this.table.push ({ from: from, to: to, val: candidates });
1229   }
1230
1231   MIM.CandidateTable.prototype.adjust = function (from, to, inserted)
1232   {
1233     var diff = inserted - (to - from);
1234     if (diff == 0)
1235       return;
1236     for (var i = 0; i < this.table.length; i++)
1237       {
1238         var elt = this.table[i];
1239         if (elt.from >= to)
1240           {
1241             elt.from += diff;
1242             elt.to += diff;
1243           }
1244       }
1245   }
1246
1247   MIM.CandidateTable.prototype.clear = function ()
1248   {
1249     this.table.length = 0;
1250   }
1251
1252   function set_cursor (prefix, pos)
1253   {
1254     this.cursor_pos = pos;
1255     var candidates = this.candidate_table.get (pos);
1256     if (this.candidates != candidates)
1257       {
1258         this.candidates = candidates;
1259         this.changed |= MIM.ChangedStatus.CandidateList;
1260       }
1261   }
1262
1263   function save_state ()
1264   {
1265     this.state_var_values = this.domain.SaveValues ();
1266     this.state_preedit = this.preedit;
1267     this.state_key_head = this.key_head;
1268     this.state_pos = this.cursor_pos;
1269   }
1270
1271   function restore_state ()
1272   {
1273     this.domain.RestoreValues (this.state_var_values);
1274     this.preedit = this.state_preedit;
1275     set_cursor.call (this, "restore", this.state_pos);
1276   }
1277
1278   function handle_key ()
1279   {
1280     Xex.Log ('Key(' + this.key_head + ') "' + this.keys.val[this.key_head]
1281              + '" in ' + this.state.name + ':' + this.keymap.name
1282              + " key/state/commit-head/len:"
1283              + this.key_head + '/' + this.state_key_head + '/' + this.commit_key_head + '/' + this.keys.val.length);
1284     var out = this.state.keymap.Lookup (this.keys, this.state_key_head);
1285     var sub = out.map;
1286
1287     if (out.index > this.key_head)
1288       {
1289         this.key_head = out.index;
1290         Xex.Log (' with submap', -1);
1291         restore_state.call (this);
1292         this.keymap = sub;
1293         if (sub.map_actions)
1294           {
1295             Xex.Log ('taking map actions:');
1296             if (! this.take_actions (sub.map_actions))
1297               return false;
1298           }
1299         else if (sub.submaps)
1300           {
1301             Xex.Log ('no map actions');
1302             for (var i = this.state_key_head; i < this.key_head; i++)
1303               {
1304                 Xex.Log ('inserting key:' + this.keys.val[i].key);
1305                 this.ins (this.keys.val[i].key, null);
1306               }
1307           }
1308         if (! sub.submaps)
1309           {
1310             Xex.Log ('terminal:');
1311             if (this.keymap.branch_actions)
1312               {
1313                 Xex.Log ('branch actions:');
1314                 if (! this.take_actions (this.keymap.branch_actions))
1315                   return false;
1316               }
1317             if (sub != this.state.keymap)
1318               this.shift (this.state);
1319           }
1320       }
1321     else
1322       {
1323         Xex.Log (' without submap', -1);
1324         this.keymap = sub;
1325         var current_state = this.state;
1326         var map = this.keymap;
1327
1328         if (map.branch_actions)
1329           {
1330             Xex.Log ('branch actions:');
1331             if (! this.take_actions (map.branch_actions))
1332               return false;
1333           }
1334
1335         if (map == this.keymap)
1336           {
1337             Xex.Log ('no state change');
1338             if (map == this.initial_state.keymap
1339                 && this.key_head < this.keys.val.length)
1340               {
1341                 Xex.Log ('unhandled');
1342                 return false;
1343               }
1344             if (map != current_state.keymap)
1345               this.shift (current_state);
1346             else if (this.keymap.actions == null)
1347               this.shift (this.initial_state);
1348           }
1349       }
1350     return true;
1351   }
1352
1353   MIM.IC.prototype = {
1354     reset: function (clear_keys)
1355     {
1356       this.cursor_pos = 0;
1357       this.prev_state = null;
1358       this.title = this.initial_state.title;
1359       this.state_preedit = '';
1360       this.state_key_head = 0;
1361       this.state_var_values = {};
1362       this.state_pos = 0;
1363       this.key_head = 0;
1364       if (clear_keys)
1365         this.keys.val.length = 0;
1366       this.commit_key_head = 0;
1367       this.key_unhandled = false;
1368       this.unhandled_key = null;
1369       this.changed = MIM.ChangedStatus.None;
1370       this.error_message = '';
1371       this.title = this.initial_state.title;
1372       this.produced = '';
1373       this.preedit = '';
1374       this.preedit_saved = '';
1375       if (this.candidate_show)
1376         MIM.hide (this);
1377       this.candidate_table.clear ();
1378       this.candidates = null;
1379       this.candidate_show = false;
1380       for (var elt in this.marker_positions)
1381         this.marker_positions[elt] = 0;
1382       this.shift (this.initial_state);
1383     },
1384
1385     catch_args: new Array (Xex.CatchTag._mimtag, null),
1386
1387     take_actions: function (actions)
1388     {
1389       if (actions.length == 0)
1390         return true;;
1391       var func_progn = this.domain.GetFunc ('progn');
1392       var func_catch = this.domain.GetFunc ('catch');
1393       this.catch_args[1] = new Xex.Funcall (func_progn, null, actions);
1394       var term = new Xex.Funcall (func_catch, null, this.catch_args);
1395       term = term.Eval (this.domain);
1396       return (! term.IsSymbol || term.val != '@mimtag');
1397     },
1398
1399     GetSurroundingChar: function (pos)
1400     {
1401       if (pos < 0)
1402         {
1403           pos += this.range[0];
1404           if (pos < 0)
1405             return -1;
1406         }
1407       else
1408         {
1409           pos += this.range[1];
1410           if (pos >= this.target.value.length)
1411             return -1;
1412         }
1413       return this.target.value.charCodeAt (pos);
1414     },
1415     
1416     DelSurroundText: function (pos)
1417     {
1418       var text;
1419       if (pos < 0)
1420         {
1421           pos += this.range[0];
1422           if (pos <= 0)
1423             {
1424               pos = 0; text = '';
1425             }
1426           else
1427             text = this.target.value.substring (0, pos);
1428           if (this.range[0] < this.target.value.length)
1429             text += this.target.value.substring (this.range[0]);
1430           this.target.value = text;
1431           this.range[1] -= this.range[0] - pos;
1432           this.range[0] = pos;
1433         }
1434       else
1435         {
1436           pos += this.range[1];
1437           text = this.target.value.substring (0, this.range[1]);
1438           if (pos >= this.target.value.length)
1439             pos = this.target.value.length;
1440           else
1441             text += this.target.value.substring (pos);
1442           this.target.value = text;
1443         }
1444     },
1445
1446     adjust_markers: function (from, to, inserted)
1447     {
1448       var diff = inserted - (to - from);
1449
1450       for (var name in this.marker_positions)
1451         {
1452           var pos = this.marker_positions[name];
1453           if (pos > from)
1454             {
1455               if (pos >= to)
1456                 this.marker_positions[name] += diff;
1457               else if (pos > from)
1458                 this.marker_positions[name] = from;
1459             }
1460         }
1461     },
1462
1463     preedit_replace: function (from, to, text, candidates)
1464     {
1465       var newlen = text.length;
1466       this.preedit = (this.preedit.substring (0, from)
1467                       + text + this.preedit.substring (to));
1468       this.changed |= MIM.ChangedStatus.Preedit | MIM.ChangedStatus.CursorPos;
1469       this.adjust_markers (from, to, newlen);
1470       this.candidate_table.adjust (from, to, newlen);
1471       if (candidates)
1472         this.candidate_table.put (from, from + newlen, candidates)
1473       if (this.cursor_pos >= to)
1474         set_cursor.call (this, 'adjust', this.cursor_pos + text.length - (to - from));
1475       else if (this.cursor_pos > from)
1476         set_cursor.call (this, 'adjust', from)
1477     },
1478
1479     ins: function (text, candidates)
1480     {
1481       this.preedit_replace (this.cursor_pos, this.cursor_pos, text, candidates);
1482     },
1483
1484     rep: function (old_text, new_text, candidates)
1485     {
1486       this.preedit_replace (this.cursor_pos - old_text.length,
1487                             this.cursor_pos, new_text, candidates);
1488     },
1489
1490     del: function (pos)
1491     {
1492       var deleted = pos - this.cursor_pos;
1493       if (pos < this.cursor_pos)
1494         {
1495           if (pos < 0)
1496             {
1497               this.DelSurroundText (pos);
1498               deleted = - this.cursor_pos;
1499               pos = 0;
1500             }
1501           if (pos < this.cursor_pos)
1502             this.preedit_replace (pos, this.cursor_pos, '', null);
1503         }
1504       else
1505         {
1506           if (pos > this.preedit.length)
1507             {
1508               this.DelSurroundText (pos - this.preedit.length);
1509               deleted = this.preedit.length - this.cursor_pos;
1510               pos = this.preedit.length;
1511             }
1512           if (pos > this.cursor_pos)
1513             this.preedit_replace (this.cursor_pos, pos, '', null);
1514         }
1515       return deleted;
1516     },
1517
1518     show: function ()
1519     {
1520       this.candidate_show = true;
1521       this.changed |= MIM.ChangedStatus.CandidateShow;
1522     },
1523
1524     hide: function ()
1525     {
1526       this.candidate_show = false;
1527       this.changed |= MIM.ChangedStatus.CandidateShow;
1528     },
1529
1530     move: function (pos)
1531     {
1532       if (pos < 0)
1533         pos = 0;
1534       else if (pos > this.preedit.length)
1535         pos = this.preedit.length;
1536       if (pos != this.cursor_pos)
1537         {
1538           set_cursor.call (this, 'move', pos);
1539           this.changed |= MIM.ChangedStatus.Preedit;
1540         }
1541     },
1542
1543     pushback: function (n)
1544     {
1545       if (n instanceof MIM.KeySeq)
1546         {
1547           if (this.key_head > 0)
1548             this.key_head--;
1549           if (this.key_head < this.keys.val.length)
1550             this.keys.val.splice (this.key_head,
1551                                   this.keys.val.length - this.key_head);
1552           for (var i = 0; i < n.val.length; i++)
1553             this.keys.val.push (n.val[i]);
1554           return;
1555         }
1556       if (n > 0)
1557         {
1558           this.key_head -= n;
1559           if (this.key_head < 0)
1560             this.key_head = 0;
1561         }
1562       else if (n == 0)
1563         this.key_head = 0;
1564       else
1565       {
1566         this.key_head = - n;
1567         if (this.key_head > this.keys.val.length)
1568           this.key_head = this.keys.val.length;
1569       }
1570     },
1571
1572     pop: function ()
1573     {
1574       if (this.key_head < this.keys.val.length)
1575         this.keys.val.splice (this.key_head, 1);
1576     },
1577
1578     commit: function ()
1579     {
1580       if (this.preedit.length > 0)
1581       {
1582         this.candidate_table.clear ();
1583         this.produced += this.preedit;
1584         this.preedit_replace.call (this, 0, this.preedit.length, '', null);
1585         this.preedit_saved = '';
1586         this.state_pos = 0;
1587         this.commit_key_head = this.key_head;
1588       }
1589     },
1590
1591     shift: function (state)
1592     {
1593       if (state == null)
1594         {
1595           if (this.prev_state == null)
1596             return;
1597           state = this.prev_state;
1598         }
1599
1600       if (state == this.initial_state)
1601         {
1602           if (this.state)
1603             {
1604               this.commit ();
1605               this.keys.val.splice (0, this.key_head);
1606               this.key_head = this.state_key_head = this.commit_key_head = 0;
1607               this.prev_state = null;
1608             }
1609         }
1610       else
1611         {
1612           if (state != this.state)
1613             this.prev_state = this.state;
1614         }
1615       if (state != this.state && state.keymap.map_actions)
1616         this.take_actions (state.keymap.map_actions);
1617       if (! this.state || this.state.title != state.title)
1618         this.changed |= MIM.ChangedStatus.StateTitle;
1619       this.state = state;
1620       this.keymap = state.keymap;
1621       save_state.call (this);
1622     },
1623
1624     Filter: function (key)
1625     {
1626       if (! this.active)
1627         {
1628           Xex.Log ("active = false");
1629           this.key_unhandled = true;
1630           this.unhandled_key = key;
1631           return false;
1632         }
1633       if (key.key == '_reload')
1634         return true;
1635       this.changed = MIM.ChangedStatus.None;
1636       this.produced = '';
1637       this.key_unhandled = false;
1638       this.keys.val.push (key);
1639       var count = 0;
1640       while (this.key_head < this.keys.val.length)
1641         {
1642           if (! handle_key.call (this))
1643             {
1644               if (this.key_head < this.keys.val.length)
1645                 {
1646                   this.unhandled_key = this.keys.val[this.key_head];
1647                   this.keys.val.splice (this.key_head, this.key_head + 1);
1648                 }
1649               if (this.state_key_head > 0)
1650                 this.state_key_head--;
1651               if (this.commit_key_head > 0)
1652                 this.commit_key_head--;
1653               this.key_unhandled = true;
1654               break;
1655             }
1656           if (++count == 10)
1657             {
1658               this.reset (true);
1659               this.key_unhandled = true;
1660               break;
1661             }
1662         }
1663       if (this.keymap == this.initial_state.keymap)
1664         this.commit ();
1665
1666       if (this.commit_key_head > 0)
1667         {
1668           this.keys.val.splice (0, this.commit_key_head);
1669           this.key_head -= this.commit_key_head;
1670           this.state_key_head -= this.commit_key_head;
1671           this.commit_key_head = 0;
1672         }
1673       if (this.key_unhandled)
1674         {
1675           this.keys.val.length = 0;
1676           //this.keys.val.splice (0, this.keys.val.length);
1677           this.key_head = this.state_key_head = this.commit_key_head = 0;
1678         }
1679       if (this.changed & MIM.ChangedStatus.Candidate)
1680         {
1681           if (this.candidate_show && this.candidates)
1682             MIM.show (this);
1683           else
1684             MIM.hide (this);
1685         }
1686       return (! this.key_unhandled
1687               && this.produced.length == 0);
1688     }
1689   }
1690
1691   // Load the list of input methods.
1692   var node = Xex.Load (MIM.server, "imlist.xml");
1693   for (node = node.firstChild; node; node = node.nextSibling)
1694     if (node.nodeName == 'input-method')
1695       {
1696         var lang = null, name = null, extra_id = null, file = null;
1697
1698         for (var n = node.firstChild; n; n = n.nextSibling)
1699           {
1700             if (n.nodeName == 'language')
1701               lang = n.firstChild.nodeValue;
1702             else if (n.nodeName == 'name')
1703               name = n.firstChild.nodeValue;
1704             else if (n.nodeName == 'extra-id')
1705               extra_id = n.firstChild.nodeValue;
1706             else if (n.nodeName == 'filename')
1707               file = n.firstChild.nodeValue;
1708           }
1709         if (name && name != 'nil')
1710           {
1711             if (! MIM.imlist[lang])
1712               MIM.imlist[lang] = {};
1713             MIM.imlist[lang][name] = new MIM.IM (lang, name, extra_id, file);
1714           }
1715         else if (extra_id && extra_id != 'nil')
1716           {
1717             if (! MIM.imextra[lang])
1718               MIM.imextra[lang] = {};
1719             MIM.imextra[lang][extra_id] = new MIM.IM (lang, name, extra_id, file);
1720           }
1721       }
1722   if (MIM.imextra.t && MIM.imextra.t.global)
1723     MIM.im_global = MIM.imextra.t.global;
1724   else
1725     {
1726       MIM.im_global = new MIM.IM ('t', 'nil', 'global', null);
1727       MIM.im_global.load_status = MIM.LoadStatus.Error;
1728     }
1729   node = undefined;
1730 }) ();
1731
1732 (function () {
1733   var keys = new Array ();
1734   keys[0x09] = 'Tab';
1735   keys[0x08] = 'BackSpace';
1736   keys[0x0D] = 'Return';
1737   keys[0x1B] = 'Escape';
1738   keys[0x20] = 'space';
1739   keys[0x21] = 'Page_Up';
1740   keys[0x22] = 'Page_Down';
1741   keys[0x23] = 'End';
1742   keys[0x24] = 'Home';
1743   keys[0x25] = 'Left';
1744   keys[0x26] = 'Up';
1745   keys[0x27] = 'Right';
1746   keys[0x28] = 'Down';
1747   keys[0x2D] = 'Insert';
1748   keys[0x2E] = 'Delete';
1749   for (var i = 1; i <= 12; i++)
1750     keys[111 + i] = "f" + i;
1751   keys[0x90] = "Num_Lock";
1752   keys[0xF0] = "Caps_Lock";
1753
1754   var keyids = {};
1755   keyids['U+0008'] = 'BackSpace';
1756   keyids['U+0009'] = 'Tab';
1757   keyids['U+0018'] = 'Cancel';
1758   keyids['U+001B'] = 'Escape';
1759   keyids['U+0020'] = 'space';
1760   keyids['U+007F'] = 'Delete';
1761   keyids['PageUp'] = 'Page_Up';
1762   keyids['PageDown'] = 'Page_Down';
1763
1764   var modifiers = {}
1765   modifiers.Shift = 1;
1766   modifiers.Control = 1;
1767   modifiers.Alt = 1;
1768   modifiers.AltGraph = 1;
1769   modifiers.Meta = 1
1770
1771   MIM.decode_key_event = function (event)
1772   {
1773     var key = event.keyIdentifier;
1774
1775     if (key)                    // keydown event of Chrome
1776       {
1777         if (modifiers[key])
1778           return false;
1779         var mod = '';
1780         var shifted = event.shiftKey;
1781         if (event.ctrlKey) mod += 'C-';
1782         if (event.metaKey) mod += 'M-';
1783         if (event.altKey) mod += 'A-';
1784         var keysym = keyids[key];
1785         if (keysym)
1786           key = keysym;
1787         else if (key.match(/^U\+([0-9A-Z]+)$/))
1788           {
1789             if (mod.length == 0)
1790               return false;
1791             var c = parseInt (RegExp.$1, 16);
1792             // Chrome sets, for instance, "U+0x00C1" when the key 'A'
1793             // is typed with control or alt/meta key.
1794             if (c >= 0x80)
1795               c -= 0x80;
1796             if (c >= 0x41 && c <= 0x5A && ! event.shiftKey)
1797               c += 0x20;
1798             key = String.fromCharCode (c);
1799             shifted = false;
1800           }
1801         if (shifted) mod += 'S-';
1802         return new MIM.Key (mod + key);
1803       }
1804     else
1805       {
1806         key = ((event.type == 'keydown' || event.keyCode) ? event.keyCode
1807                : event.charCode ? event.charCode
1808                : false);
1809         if (! key)
1810           return false;
1811         if (event.type == 'keydown')
1812           {
1813             key = keys[key];
1814             if (! key)
1815               return false;
1816             if (event.shiftKey) key = "S-" + key ;
1817           }
1818         else
1819           key = String.fromCharCode (key);
1820       }
1821     if (event.altKey) key = "A-" + key ;
1822     if (event.ctrlKey) key = "C-" + key ;
1823     return new MIM.Key (key);
1824   }
1825 }) ();
1826
1827 MIM.add_event_listener
1828   = (window.addEventListener
1829      ? function (target, type, listener) {
1830        target.addEventListener (type, listener, false);
1831      }
1832      : window.attachEvent
1833      ? function (target, type, listener) {
1834        target.attachEvent ('on' + type,
1835                            function() {
1836                              listener.call (target, window.event);
1837                            });
1838      }
1839      : function (target, type, listener) {
1840        target['on' + type]
1841          = function (e) { listener.call (target, e || window.event); };
1842      });
1843
1844 MIM.debug_print = function () { };
1845
1846 MIM.get_range = function (target, ic)
1847 {
1848   var from, to;
1849   if (target.selectionStart != null) // for Mozilla
1850     {
1851       from = target.selectionStart;
1852       to = target.selectionEnd;
1853     }
1854   else                          // for IE
1855     {
1856       var r = document.selection.createRange ();
1857       var rr = r.duplicate ();
1858
1859       rr.moveToElementText (target);
1860       rr.setEndPoint ('EndToEnd', range);
1861       from = rr.text.length - r.text.length;
1862       to = rr.text.length;
1863     }
1864   if (from == to
1865       && from == ic.range[0] + ic.cursor_pos
1866       && (ic.preedit.length == 0
1867           || ic.preedit == target.value.substring (ic.range[0], ic.range[1])))
1868     return true;
1869   ic.reset (true);
1870   ic.range[0] = from;
1871   ic.range[1] = to;
1872   return false;
1873 };
1874
1875 (function () {
1876   var style_props = {
1877     width: 'width',
1878     height: 'height',
1879     padingLeft: 'padding-left',
1880     paddingRight: 'padding-right',
1881     paddingTop: 'padding-top',
1882     paddintBottom: 'padding-bottom', 
1883     marginRight: 'margin-right',
1884     marginTop: 'margin-top',
1885     borderLeftStyle: 'border-left-style',
1886     borderRightStyle: 'border-right-style',
1887     borderTopStyle: 'border-top-style',
1888     borderBottomStyle: 'border-bottom-style',
1889     borderLeftWidth: 'border-left-width',
1890     borderRightWidth: 'border-right-width',
1891     borderTopWidth: 'border-top-width',
1892     borderBottomWidth: 'border-bottom-width',
1893     fontFamily: 'font-family',
1894     fontSize: 'font-size',
1895     lineHeight: 'line-height',
1896     letterSpacing: 'letter-spacing',
1897     wordSpacing: 'word-spacing' };
1898
1899   function copy_style (from, to)
1900   {
1901     var from_style = getComputedStyle(from,'');
1902     for(var name in style_props)
1903       to.style[name] = from_style[style_props[name]];
1904     to.style.left = from.offsetLeft + 'px'; 
1905     to.style.top = from.offsetTop + 'px';
1906     to.style.width = from.offsetWidth;
1907     to.style.height = from.offsetHeight;
1908     return from_style;
1909   }
1910
1911   var temp;                     // Temporary 'div' element
1912
1913   MIM.get_preedit_pos = function (target, ic)
1914   {
1915     if (! temp)
1916       {
1917         temp = document.createElement ('div');
1918         temp.style.visibility = 'hidden';
1919         temp.style.position = 'absolute';
1920         temp.appendChild (document.createElement ('span'));
1921         temp.appendChild (document.createElement ('span'));
1922         document.getElementsByTagName ('body')[0].appendChild (temp);
1923       }
1924     if (temp.ic != ic)
1925       {
1926         var styles = copy_style (target, temp);
1927         ic.abs_top = (parseInt (styles.marginTop)
1928                       + parseInt (styles.borderTopWidth)
1929                       + parseInt (styles.paddingTop));
1930         ic.abs_left = (parseInt (styles.marginLeft)
1931                        + parseInt (styles.borderLeftWidth)
1932                        + parseInt (styles.paddingLeft));
1933         for (var elm = target.offsetParent; elm; elm = elm.offsetParent)
1934           {
1935             ic.abs_top += elm.offsetTop;
1936             ic.abs_left += elm.offsetLeft;
1937           }
1938         temp.ic = ic;
1939       }
1940     temp.firstChild.innerText = target.value.substring (0, ic.range[0]);
1941     temp.lastChild.innerText
1942       = (ic.range[0] == ic.range[1] ? "|"
1943          : target.value.substring (ic.range[0], ic.range[1]));
1944     ic.abs_y = (ic.abs_top + temp.lastChild.offsetTop
1945                 + temp.lastChild.offsetHeight - target.scrollTop);
1946     ic.abs_x0 = ic.abs_left + temp.lastChild.offsetLeft;
1947     ic.abs_x1 = ic.abs_x0 + temp.lastChild.offsetWidth;
1948   }
1949 }) ();
1950
1951 MIM.update_bar = function (target, ic)
1952 {
1953   if (ic.preedit.length > 0)
1954     {
1955       MIM.get_preedit_pos (target, ic);
1956       if (! ic.bar)
1957         {
1958           ic.bar = document.createElement ('div');
1959           ic.bar.style.position = 'absolute';
1960           ic.bar.style.backgroundColor = "black";
1961           ic.bar.style.minHeight = '1px';
1962           document.getElementsByTagName ('body')[0].appendChild (ic.bar);
1963         }
1964       ic.bar.style.display = 'block'
1965       ic.bar.style.top = ic.abs_y + 'px';
1966       ic.bar.style.left = ic.abs_x0 + 'px';
1967       ic.bar.style.minWidth = ic.bar.style.maxWidth = (ic.abs_x1 - ic.abs_x0) + 'px';
1968     }
1969   else if (ic.bar)
1970     ic.bar.style.display = 'none'
1971 };
1972
1973 MIM.update = function (target, ic, for_focus_out)
1974 {
1975   var text = target.value;
1976   target.value = (text.substring (0, ic.range[0])
1977                   + ic.produced
1978                   + ic.preedit
1979                   + text.substring (ic.range[1]));
1980   ic.range[0] += ic.produced.length;
1981   ic.range[1] = ic.range[0] + ic.preedit.length;
1982   MIM.update_bar (target, ic);
1983   if (! for_focus_out)
1984     {
1985       var pos = ic.range[0] + ic.cursor_pos;
1986       if (target.setSelectionRange) // Mozilla
1987         {
1988           var scrollTop = target.scrollTop;
1989           target.setSelectionRange (pos, pos);
1990           target.scrollTop = scrollTop;
1991         }
1992       else                      // IE
1993         {
1994           var range = target.createTextRange ();
1995           range.moveStart ('character', pos);
1996           range.moveEnd ('character', pos);
1997           range.select ();
1998         }
1999     }
2000 };
2001
2002 (function () {
2003   MIM.show = function (ic)
2004   {
2005     if (! ic.candidates)
2006       return;
2007     var target = ic.target;
2008     MIM.get_preedit_pos (target, ic);
2009     if (! ic.can_node)
2010       {
2011         ic.can_node = document.createElement ('table');
2012         ic.can_node.style.position = 'absolute';
2013         ic.can_node.style.display = 'none';
2014         ic.can_node.style.backgroundColor = "white";
2015         ic.can_node.style.border = "1px solid black";
2016         document.getElementsByTagName ('body')[0].appendChild (ic.can_node);
2017       }
2018
2019     if (ic.changed & MIM.ChangedStatus.CandidateList)
2020       {
2021         while (ic.can_node.childNodes.length > 0)
2022           ic.can_node.removeChild (ic.can_node.firstChild);
2023         var tr = document.createElement ('tr');
2024         var group = ic.candidates.CurrentGroup ();
2025         var td = document.createElement ('td');
2026         td.innerHTML = group[0][1] + '/' + group[0][0];
2027         td.style.color = 'white';
2028         td.style.backgroundColor = 'black';
2029         tr.appendChild (td);
2030         for (var i = 1; i < group.length; i++)
2031           {
2032             var td = document.createElement ('td');
2033             td.noWrap = true;
2034             td.innerHTML = (i < 10 ? i : i == 10 ? '0' : String.fromCharCode (0x60 + (i - 10))) + '.' + group[i];
2035             if (i == group[0][2] + 1)
2036               td.style.backgroundColor = 'lightblue';
2037             tr.appendChild (td);
2038           }
2039         ic.can_node.appendChild (tr);
2040         ic.can_node.style.top = (ic.abs_y + 10) + 'px';
2041         ic.can_node.style.left = ic.abs_x0 + 'px';
2042       }
2043     else
2044       {
2045         var td = ic.can_node.firstElement ().firstElement ().nextElement ();
2046         var col = ic.candidates.CurrentCol ();
2047         for (var i = 0; td; td = td.nextElement ())
2048           td.style.backgroundColor = (i++ == col ? 'lightblue' : 'white');
2049       }
2050     ic.can_node.style.display = 'block';
2051   }
2052
2053   MIM.hide = function (ic)
2054   {
2055     if (ic.can_node)
2056       ic.can_node.style.display = 'none';
2057   }
2058
2059   function real_focus_in (target, ic)
2060   {
2061     if (MIM.get_range (target, ic))
2062       ic.Filter (MIM.Key.FocusIn);
2063     MIM.update (target, ic, false);
2064   }
2065
2066   var focus_in_timer;
2067
2068   MIM.focus_in = function (event)
2069   {
2070     var target = event.target;
2071     Xex.Log ("Focus in " + target.tagName);
2072     focus_in_timer
2073       = setTimeout (function () {real_focus_in (target, target.mim_ic)} , 10);
2074     return true;
2075   }
2076
2077   MIM.click = function (event)
2078   {
2079     var target = event.target;
2080     Xex.Log ("Click in " + target.tagName);
2081     if (focus_in_timer)
2082       {
2083         clearTimeout (focus_in_timer);
2084         focus_in_timer = null;
2085       }
2086     real_focus_in (target, target.mim_ic);
2087   }
2088
2089 }) ();
2090
2091 MIM.focus_out = function (event)
2092 {
2093   var target = event.target;
2094   var ic = target.mim_ic;
2095   Xex.Log ("Focus out " + target.tagName);
2096   MIM.get_range (target, ic);
2097   MIM.debug_print (event, ic);
2098   ic.Filter (MIM.Key.FocusOut);
2099   MIM.update (target, ic, true);
2100   return true;
2101 };
2102
2103 MIM.keydown = function (event)
2104 {
2105   var target = event.target;
2106   if (! (target.type == "text" || target.type == "textarea"))
2107     return;
2108
2109   var ic = target.mim_ic;
2110   if (! ic || ic.im != MIM.current)
2111     {
2112       target.mim_ic = null;
2113       Xex.Log ('creating IC');
2114       ic = new MIM.IC (MIM.current, target);
2115       if (ic.im.load_status != MIM.LoadStatus.Loaded)
2116         return true;
2117       target.mim_ic = ic;
2118       MIM.add_event_listener (target, 'focus', MIM.focus_in);
2119       MIM.add_event_listener (target, 'blur', MIM.focus_out);
2120       MIM.add_event_listener (target, 'click', MIM.click);
2121     }
2122   MIM.get_range (target, ic)
2123   MIM.debug_print (event, ic);
2124   ic.key = MIM.decode_key_event (event);
2125   if (ic.key)
2126     {
2127       try {
2128         var result = ic.Filter (ic.key);
2129       } catch (e) {
2130         Xex.Log ('Error' + e);
2131         throw (e);
2132       }
2133       MIM.update (target, ic, false);
2134       if (! ic.key_unhandled)
2135         event.preventDefault ();
2136     }
2137 };
2138
2139 MIM.keypress = function (event)
2140 {
2141   var target = event.target;
2142   if (! (target.type == "text" || target.type == "textarea"))
2143     return;
2144
2145   var ic = target.mim_ic;
2146   var i;
2147
2148   try {
2149     if (ic.im.load_status != MIM.LoadStatus.Loaded)
2150       return;
2151     if (! ic.key)
2152       ic.key = MIM.decode_key_event (event);
2153     if (! ic.key)
2154       {
2155         ic.reset (true);
2156         return;
2157       }
2158     
2159     try {
2160       var result = ic.Filter (ic.key);
2161     } catch (e) {
2162       Xex.Log ('Error:' + e);
2163       throw (e);
2164     }
2165     MIM.update (target, ic, false);
2166     if (! ic.key_unhandled)
2167       event.preventDefault ();
2168   } catch (e) {
2169     Xex.Log ("error:" + e);
2170     event.preventDefault ();
2171   } finally {
2172     MIM.debug_print (event, ic);
2173   }
2174
2175   return;
2176 };
2177
2178 (function () {
2179   var lang_category = {
2180     European: {
2181       cs: { name: 'Czech' },
2182       da: { name: 'Danish' },
2183       el: { name: 'Greek' },
2184       en: { name: 'English' },
2185       eo: { name: 'Esperanto' },
2186       fr: { name: 'French' },
2187       grc: { name: 'ClassicGreek' },
2188       hr: { name: 'Croatian' },
2189       hy: { name: 'Armenian' },
2190       ka: { name: 'Georgian' },
2191       kk: { name: 'Kazakh' },
2192       ru: { name: 'Russian' },
2193       sk: { name: 'Slovak' },
2194       sr: { name: 'Serbian' },
2195       sv: { name: 'Swedish' },
2196       yi: { name: 'Yiddish' } },
2197     MiddleEast: {
2198       ar: { name: 'Arabic' },
2199       dv: { name: 'Divehi' },
2200       fa: { name: 'Persian' },
2201       he: { name: 'Hebrew' },
2202       kk: { name: 'Kazakh' },
2203       ps: { name: 'Pushto' },
2204       ug: { name: 'Uighur' },
2205       yi: { name: 'Yiddish' } },
2206     SouthAsia: {
2207       as: { name: 'Assamese' },
2208       bn: { name: 'Bengali' },
2209       bo: { name: 'Tibetan' },
2210       gu: { name: 'Gujarati' },
2211       hi: { name: 'Hindi' },
2212       kn: { name: 'Kannada' },
2213       ks: { name: 'Kashmiri' },
2214       ml: { name: 'Malayalam' },
2215       mr: { name: 'Marathi' },
2216       ne: { name: 'Nepali' },
2217       or: { name: 'Oriya' },
2218       pa: { name: 'Panjabi' },
2219       sa: { name: 'Sanskirit' },
2220       sd: { name: 'Sindhi' },
2221       si: { name: 'Sinhalese' },
2222       ta: { name: 'Tamil' },
2223       te: { name: 'Telugu' },
2224       ur: { name: 'Urdu' } },
2225     SouthEastAsia: {
2226       cmc: { name: 'Cham' },
2227       km: { name: 'Khmer'},
2228       lo: { name: 'Lao' },
2229       my: { name: 'Burmese' },
2230       tai: { name: 'Tai Viet' },
2231       th: { name: 'Thai' },
2232       vi: { name: 'Vietanamese' } },
2233     EastAsia: {
2234       ii: { name: 'Yii' },
2235       ja: { name: 'Japanese' },
2236       ko: { name: 'Korean' },
2237       zh: { name: 'Chinese' } },
2238     Other: {
2239       am: { name:  'Amharic' },
2240       ath: { name: 'Carrier' },
2241       bla: { name: 'Blackfoot' },
2242       cr: { name: 'Cree' },
2243       eo: { name: 'Esperanto' },
2244       iu: { name: 'Inuktitut' },
2245       nsk: { name: 'Naskapi' },
2246       oj: { name: 'Ojibwe' },
2247       t: { name: 'Generic' } }
2248   };
2249
2250   function categorize_im ()
2251   {
2252     var cat, lang, list, name;
2253     for (lang in MIM.imlist)
2254       {
2255         list = null;
2256         for (cat in lang_category)
2257           if (lang_category[cat][lang])
2258             {
2259               list = lang_category[cat][lang].list;
2260               if (! list)
2261                 list = lang_category[cat][lang].list = {};
2262               for (name in MIM.imlist[lang])
2263                 list[name] = MIM.imlist[lang][name];
2264             }
2265         if (! list)
2266           for (name in MIM.imlist[lang])
2267             Xex.Log ('no category ' + lang + '-' + name);
2268       }
2269   }
2270
2271   var destroy_timer;
2272   var last_target;
2273
2274   function destroy ()
2275   {
2276     clearTimeout (destroy_timer);
2277     destroy_timer = null;
2278     var target = document.getElementById ('mim-menu');
2279     if (target)
2280       {
2281         for (; last_target && last_target.menu_level;
2282              last_target = last_target.parentLi)
2283           last_target.style.backgroundColor = 'white';
2284         var nodes = target.getElementsByTagName ('ul');
2285         for (var i = 0; i < nodes.length; i++)
2286           nodes[i].style.visibility = 'hidden';
2287         document.getElementsByTagName ('body')[0].removeChild (target);
2288       }
2289   }    
2290
2291   function destroy_menu () {
2292     if (! destroy_timer)
2293       destroy_timer = setTimeout (destroy, 1000);
2294     return true;
2295   }
2296
2297   function show_submenu (event)
2298   {
2299     var target = event.target;
2300     if (! target.menu_level)
2301       {
2302         if (! target.parentNode || ! target.parentNode.menu_level)
2303           return true;
2304         target = target.parentNode;
2305       }
2306     if (destroy_timer)
2307       {
2308         clearTimeout (destroy_timer);
2309         destroy_timer = null;
2310       }
2311     if (last_target && target.parentLi != last_target)
2312       {
2313         last_target.style.backgroundColor = 'white';
2314         if (target.menu_level < last_target.menu_level)
2315           {
2316             last_target = last_target.parentLi;
2317             last_target.style.backgroundColor = 'white';
2318           }
2319         var uls = last_target.getElementsByTagName ('ul');
2320         for (var i = 0; i < uls.length; i++)
2321           uls[i].style.visibility = 'hidden';
2322       }
2323     last_target = target;
2324     target.style.backgroundColor = 'yellow';
2325     if (target.menu_level < 3)
2326       {
2327         target.lastChild.style.visibility = 'visible';
2328         target.lastChild.style.left = target.clientWidth + 'px';
2329       }
2330     event.preventDefault ();    
2331   }
2332
2333   function select_im (event)
2334   {
2335     var target = event.target;
2336     if (! target.im)
2337       {
2338         if (! target.parentNode || ! target.parentNode.menu_level)
2339           {
2340             event.preventDefault ();
2341             return false;
2342           }
2343         target = target.parentNode;
2344       }
2345     if (target.im)
2346       {
2347         MIM.current = target.im;
2348         destroy ();
2349       }
2350     event.preventDefault ();
2351   }
2352
2353   function create_ul (visibility)
2354   {
2355     var ul = document.createElement ('ul');
2356     ul.style.position = 'absolute';
2357     ul.style.margin = '0px';
2358     ul.style.padding = '0px';
2359     ul.style.border = '1px solid gray';
2360     ul.style.borderBottom = 'none';
2361     ul.style.top = '-1px';
2362     ul.style.backgroundColor = 'white';
2363     ul.style.visibility = visibility;
2364     return ul;
2365   }
2366
2367   function create_li (level, text)
2368   {
2369     var li = document.createElement ('li');
2370     li.style.position = 'relative';
2371     li.style.margin = '0px';
2372     li.style.padding = '1px';
2373     li.style.borderBottom = '1px solid gray';
2374     li.style.top = '0px';
2375     li.style.listStyle = 'none';
2376     li.menu_level = level;
2377     var nobr = document.createElement ('nobr');
2378     nobr.innerHTML = text;
2379     li.appendChild (nobr);
2380     return li;
2381   }
2382
2383   var menu;
2384
2385   function create_menu (event)
2386   {
2387     var target = event.target;
2388
2389     if (! ((target.type == "text" || target.type == "textarea")
2390            && event.which == 1 && event.ctrlKey))
2391       return;
2392     if (! menu)
2393       {
2394         categorize_im ();
2395         menu = create_ul ('visible');
2396         menu.style.fontFamily = 'sans-serif';
2397         menu.style.fontWeight = 'bold';
2398         menu.id = 'mim-menu';
2399         menu.onmousedown = select_im;
2400         menu.onmouseover = show_submenu;
2401         menu.onmouseout = destroy_menu;
2402         for (var catname in lang_category)
2403           {
2404             var cat = lang_category[catname];
2405             var li = create_li (1, catname);
2406             var sub = create_ul ('hidden');
2407             for (var langname in cat)
2408               {
2409                 var lang = cat[langname];
2410                 if (! lang.list)
2411                   continue;
2412                 var sub_li = create_li (2, lang.name);
2413                 sub_li.parentLi = li;
2414                 var subsub = create_ul ('hidden');
2415                 for (var name in lang.list)
2416                   {
2417                     var im = lang.list[name];
2418                     var subsub_li = create_li (3, im.name);
2419                     subsub_li.parentLi = sub_li;
2420                     subsub_li.im = im;
2421                     subsub.appendChild (subsub_li);
2422                   }
2423                 sub_li.appendChild (subsub);
2424                 sub.appendChild (sub_li);
2425               }
2426             li.appendChild (sub);
2427             menu.appendChild (li);
2428           }
2429         lang_category = null;
2430       }
2431     menu.style.left = (event.clientX - 10) + "px";
2432     menu.style.top = (event.clientY - 10) + "px";
2433     document.getElementsByTagName ('body')[0].appendChild (menu);
2434   };
2435
2436   MIM.init = function ()
2437   {
2438     MIM.add_event_listener (window, 'keydown', MIM.keydown);
2439     MIM.add_event_listener (window, 'keypress', MIM.keypress);
2440     MIM.add_event_listener (window, 'mousedown', create_menu);
2441     MIM.current = MIM.imlist['t']['latn-post'];
2442   };
2443 }) ();