3df31e331d3ba53253b22d596310edc3804668c0
[m17n/m17n-lib-js.git] / mim2.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             alert ("getting global var of vname:"+vname);
1055             var vari = get_global_var (vname);
1056             if (vari != null)
1057               this.domain.Defvar (vname, vari.desc, vari.val, vari.range);
1058           }
1059         vname = Xex.Term.Parse (this.domain, node)
1060       }
1061   }
1062   parsers['command-list'] = function (node)
1063   {
1064   }
1065   parsers['macro-list'] = function (node)
1066   {
1067     for (var n = node.firstElement (); n; n = n.nextElement ())
1068       if (n.nodeName == 'xi:include')
1069         {
1070           var im = include (n);
1071           if (! im)
1072             alert ('inclusion fail');
1073           else
1074             for (var macro in im.domain.functions)
1075               {
1076                 var func = im.domain.functions[macro];
1077                 if (func instanceof Xex.Macro)
1078                   im.domain.CopyFunc (this.domain, macro);
1079               }
1080           n = n.previousSibling;
1081           node.removeChild (n.nextSibling);
1082         }
1083     Xex.Term.Parse (this.domain, node.firstElement (), null);
1084   }
1085   parsers['title'] = function (node)
1086   {
1087     this.title = node.firstChild.nodeValue;
1088   }
1089   parsers['map-list'] = function (node)
1090   {
1091     for (node = node.firstElement (); node; node = node.nextElement ())
1092       {
1093         if (node.nodeName == 'xi:include')
1094           {
1095             var im = include (node);
1096             if (! im)
1097               {
1098                 alert ('inclusion fail');
1099                 continue;
1100               }
1101             for (var mname in im.map_list)
1102               this.map_list[mname] = im.map_list[mname];
1103           }
1104         else
1105           {
1106             var map = Xex.Term.Parse (this.domain, node);
1107             this.map_list[map.name] = map;
1108           }
1109       }
1110   }
1111   parsers['state-list'] = function (node)
1112   {
1113     this.domain.map_list = this.map_list;
1114     for (node = node.firstElement (); node; node = node.nextElement ())
1115       {
1116         if (node.nodeName == 'xi:include')
1117           {
1118             var im = include (node);
1119             if (! im)
1120               alert ('inclusion fail');
1121             for (var sname in im.state_list)
1122               {
1123                 state = im.state_list[sname];
1124                 if (! this.initial_state)
1125                   this.initial_state = state;
1126                 this.state_list[sname] = state;
1127               }
1128           }
1129         else if (node.nodeName == 'state')
1130           {
1131             var state = Xex.Term.Parse (this.domain, node);
1132             if (! state.title)
1133               state.title = this.title;
1134             if (! this.initial_state)
1135               this.initial_state = state;
1136             this.state_list[state.name] = state;
1137           }
1138       }
1139     delete this.domain.map_list;
1140   }
1141
1142   MIM.IM = function (lang, name, extra_id, file)
1143   {
1144     this.lang = lang;
1145     this.name = name;
1146     this.extra_id = extra_id;
1147     this.file = file;
1148     this.load_status = MIM.LoadStatus.NotLoaded;
1149     this.domain = new Xex.Domain (this.lang + '-'
1150                                   + (this.name != 'nil'
1151                                      ? this.name : this.extra_id),
1152                                   MIM.im_domain, null);
1153   };
1154
1155   function load_im (node)
1156   {
1157     this.map_list = {};
1158     this.initial_state = null;
1159     this.state_list = {};
1160     for (node = node.firstElement (); node; node = node.nextElement ())
1161       {
1162         var name = node.nodeName;
1163         var parser = parsers[name];
1164         if (parser)
1165           parser.call (this, node);
1166       }
1167     this.load_status = MIM.LoadStatus.Loaded;
1168   }
1169
1170   MIM.IM.prototype.Load = function ()
1171   {
1172     this.load_status = MIM.LoadStatus.Loading;
1173     Xex.Load (MIM.server, this.file, load_im);
1174   }
1175
1176   MIM.IC = function (im, target)
1177   {
1178     if (im.load_status == MIM.LoadStatus.NotLoaded)
1179       im.Load ();
1180     if (im.load_status != MIM.LoadStatus.Loaded)
1181       alert ('im:' + im.name + ' error:' + im.load_status);
1182     this.im = im;
1183     this.target = target;
1184     this.domain = new Xex.Domain ('context', im.domain, this);
1185     this.active = true;
1186     this.range = new Array ();
1187     this.range[0] = this.range[1] = 0;
1188     this.state = null;
1189     this.initial_state = this.im.initial_state;
1190     this.keys = new MIM.KeySeq ();
1191     this.marker_positions = new Array ();
1192     this.candidate_table = new MIM.CandidateTable ();
1193     this.reset (false);
1194   }
1195
1196   MIM.CandidateTable = function ()
1197   {
1198     this.table = new Array ();
1199   }
1200
1201   MIM.CandidateTable.prototype.get = function (pos)
1202   {
1203     for (var i = 0; i < this.table.length; i++)
1204       {
1205         var elt = this.table[i];
1206         if (elt.from < pos && pos <= elt.to)
1207           return elt.val;
1208       }
1209   }
1210
1211   MIM.CandidateTable.prototype.put = function (from, to, candidates)
1212   {
1213     for (var i = 0; i < this.table.length; i++)
1214       {
1215         var elt = this.table[i];
1216         if (elt.from < to && elt.to > from)
1217           {
1218             elt.from = from;
1219             elt.to = to;
1220             elt.val = candidates;
1221             return;
1222           }
1223       }
1224     this.table.push ({ from: from, to: to, val: candidates });
1225   }
1226
1227   MIM.CandidateTable.prototype.adjust = function (from, to, inserted)
1228   {
1229     var diff = inserted - (to - from);
1230     if (diff == 0)
1231       return;
1232     for (var i = 0; i < this.table.length; i++)
1233       {
1234         var elt = this.table[i];
1235         if (elt.from >= to)
1236           {
1237             elt.from += diff;
1238             elt.to += diff;
1239           }
1240       }
1241   }
1242
1243   MIM.CandidateTable.prototype.clear = function ()
1244   {
1245     this.table.length = 0;
1246   }
1247
1248   function set_cursor (prefix, pos)
1249   {
1250     this.cursor_pos = pos;
1251     var candidates = this.candidate_table.get (pos);
1252     if (this.candidates != candidates)
1253       {
1254         this.candidates = candidates;
1255         this.changed |= MIM.ChangedStatus.CandidateList;
1256       }
1257   }
1258
1259   function save_state ()
1260   {
1261     this.state_var_values = this.domain.SaveValues ();
1262     this.state_preedit = this.preedit;
1263     this.state_key_head = this.key_head;
1264     this.state_pos = this.cursor_pos;
1265   }
1266
1267   function restore_state ()
1268   {
1269     this.domain.RestoreValues (this.state_var_values);
1270     this.preedit = this.state_preedit;
1271     set_cursor.call (this, "restore", this.state_pos);
1272   }
1273
1274   function handle_key ()
1275   {
1276     Xex.Log ('Key(' + this.key_head + ') "' + this.keys.val[this.key_head]
1277              + '" in ' + this.state.name + ':' + this.keymap.name
1278              + " key/state/commit-head/len:"
1279              + this.key_head + '/' + this.state_key_head + '/' + this.commit_key_head + '/' + this.keys.val.length);
1280     var out = this.state.keymap.Lookup (this.keys, this.state_key_head);
1281     var sub = out.map;
1282
1283     if (out.index > this.key_head)
1284       {
1285         this.key_head = out.index;
1286         Xex.Log (' with submap', -1);
1287         restore_state.call (this);
1288         this.keymap = sub;
1289         if (sub.map_actions)
1290           {
1291             Xex.Log ('taking map actions:');
1292             if (! this.take_actions (sub.map_actions))
1293               return false;
1294           }
1295         else if (sub.submaps)
1296           {
1297             Xex.Log ('no map actions');
1298             for (var i = this.state_key_head; i < this.key_head; i++)
1299               {
1300                 Xex.Log ('inserting key:' + this.keys.val[i].key);
1301                 this.ins (this.keys.val[i].key, null);
1302               }
1303           }
1304         if (! sub.submaps)
1305           {
1306             Xex.Log ('terminal:');
1307             if (this.keymap.branch_actions)
1308               {
1309                 Xex.Log ('branch actions:');
1310                 if (! this.take_actions (this.keymap.branch_actions))
1311                   return false;
1312               }
1313             if (sub != this.state.keymap)
1314               this.shift (this.state);
1315           }
1316       }
1317     else
1318       {
1319         Xex.Log (' without submap', -1);
1320         this.keymap = sub;
1321         var current_state = this.state;
1322         var map = this.keymap;
1323
1324         if (map.branch_actions)
1325           {
1326             Xex.Log ('branch actions:');
1327             if (! this.take_actions (map.branch_actions))
1328               return false;
1329           }
1330
1331         if (map == this.keymap)
1332           {
1333             Xex.Log ('no state change');
1334             if (map == this.initial_state.keymap
1335                 && this.key_head < this.keys.val.length)
1336               {
1337                 Xex.Log ('unhandled');
1338                 return false;
1339               }
1340             if (map != current_state.keymap)
1341               this.shift (current_state);
1342             else if (this.keymap.actions == null)
1343               this.shift (this.initial_state);
1344           }
1345       }
1346     return true;
1347   }
1348
1349   MIM.IC.prototype = {
1350     reset: function (clear_keys)
1351     {
1352       this.cursor_pos = 0;
1353       this.prev_state = null;
1354       this.title = this.initial_state.title;
1355       this.state_preedit = '';
1356       this.state_key_head = 0;
1357       this.state_var_values = {};
1358       this.state_pos = 0;
1359       this.key_head = 0;
1360       if (clear_keys)
1361         this.keys.val.length = 0;
1362       this.commit_key_head = 0;
1363       this.key_unhandled = false;
1364       this.unhandled_key = null;
1365       this.changed = MIM.ChangedStatus.None;
1366       this.error_message = '';
1367       this.title = this.initial_state.title;
1368       this.produced = '';
1369       this.preedit = '';
1370       this.preedit_saved = '';
1371       if (this.candidate_show)
1372         MIM.hide (this);
1373       this.candidate_table.clear ();
1374       this.candidates = null;
1375       this.candidate_show = false;
1376       for (var elt in this.marker_positions)
1377         this.marker_positions[elt] = 0;
1378       this.shift (this.initial_state);
1379     },
1380
1381     catch_args: new Array (Xex.CatchTag._mimtag, null),
1382
1383     take_actions: function (actions)
1384     {
1385       if (actions.length == 0)
1386         return true;;
1387       var func_progn = this.domain.GetFunc ('progn');
1388       var func_catch = this.domain.GetFunc ('catch');
1389       this.catch_args[1] = new Xex.Funcall (func_progn, null, actions);
1390       var term = new Xex.Funcall (func_catch, null, this.catch_args);
1391       term = term.Eval (this.domain);
1392       return (! term.IsSymbol || term.val != '@mimtag');
1393     },
1394
1395     GetSurroundingChar: function (pos)
1396     {
1397       if (pos < 0)
1398         {
1399           pos += this.range[0];
1400           if (pos < 0)
1401             return -1;
1402         }
1403       else
1404         {
1405           pos += this.range[1];
1406           if (pos >= this.target.value.length)
1407             return -1;
1408         }
1409       return this.target.value.charCodeAt (pos);
1410     },
1411     
1412     DelSurroundText: function (pos)
1413     {
1414       var text;
1415       if (pos < 0)
1416         {
1417           pos += this.range[0];
1418           if (pos <= 0)
1419             {
1420               pos = 0; text = '';
1421             }
1422           else
1423             text = this.target.value.substring (0, pos);
1424           if (this.range[0] < this.target.value.length)
1425             text += this.target.value.substring (this.range[0]);
1426           this.target.value = text;
1427           this.range[1] -= this.range[0] - pos;
1428           this.range[0] = pos;
1429         }
1430       else
1431         {
1432           pos += this.range[1];
1433           text = this.target.value.substring (0, this.range[1]);
1434           if (pos >= this.target.value.length)
1435             pos = this.target.value.length;
1436           else
1437             text += this.target.value.substring (pos);
1438           this.target.value = text;
1439         }
1440     },
1441
1442     adjust_markers: function (from, to, inserted)
1443     {
1444       var diff = inserted - (to - from);
1445
1446       for (var name in this.marker_positions)
1447         {
1448           var pos = this.marker_positions[name];
1449           if (pos > from)
1450             {
1451               if (pos >= to)
1452                 this.marker_positions[name] += diff;
1453               else if (pos > from)
1454                 this.marker_positions[name] = from;
1455             }
1456         }
1457     },
1458
1459     preedit_replace: function (from, to, text, candidates)
1460     {
1461       var newlen = text.length;
1462       this.preedit = (this.preedit.substring (0, from)
1463                       + text + this.preedit.substring (to));
1464       this.changed |= MIM.ChangedStatus.Preedit | MIM.ChangedStatus.CursorPos;
1465       this.adjust_markers (from, to, newlen);
1466       this.candidate_table.adjust (from, to, newlen);
1467       if (candidates)
1468         this.candidate_table.put (from, from + newlen, candidates)
1469       if (this.cursor_pos >= to)
1470         set_cursor.call (this, 'adjust', this.cursor_pos + text.length - (to - from));
1471       else if (this.cursor_pos > from)
1472         set_cursor.call (this, 'adjust', from)
1473     },
1474
1475     ins: function (text, candidates)
1476     {
1477       this.preedit_replace (this.cursor_pos, this.cursor_pos, text, candidates);
1478     },
1479
1480     rep: function (old_text, new_text, candidates)
1481     {
1482       this.preedit_replace (this.cursor_pos - old_text.length,
1483                             this.cursor_pos, new_text, candidates);
1484     },
1485
1486     del: function (pos)
1487     {
1488       var deleted = pos - this.cursor_pos;
1489       if (pos < this.cursor_pos)
1490         {
1491           if (pos < 0)
1492             {
1493               this.DelSurroundText (pos);
1494               deleted = - this.cursor_pos;
1495               pos = 0;
1496             }
1497           if (pos < this.cursor_pos)
1498             this.preedit_replace (pos, this.cursor_pos, '', null);
1499         }
1500       else
1501         {
1502           if (pos > this.preedit.length)
1503             {
1504               this.DelSurroundText (pos - this.preedit.length);
1505               deleted = this.preedit.length - this.cursor_pos;
1506               pos = this.preedit.length;
1507             }
1508           if (pos > this.cursor_pos)
1509             this.preedit_replace (this.cursor_pos, pos, '', null);
1510         }
1511       return deleted;
1512     },
1513
1514     show: function ()
1515     {
1516       this.candidate_show = true;
1517       this.changed |= MIM.ChangedStatus.CandidateShow;
1518     },
1519
1520     hide: function ()
1521     {
1522       this.candidate_show = false;
1523       this.changed |= MIM.ChangedStatus.CandidateShow;
1524     },
1525
1526     move: function (pos)
1527     {
1528       if (pos < 0)
1529         pos = 0;
1530       else if (pos > this.preedit.length)
1531         pos = this.preedit.length;
1532       if (pos != this.cursor_pos)
1533         {
1534           set_cursor.call (this, 'move', pos);
1535           this.changed |= MIM.ChangedStatus.Preedit;
1536         }
1537     },
1538
1539     pushback: function (n)
1540     {
1541       if (n instanceof MIM.KeySeq)
1542         {
1543           if (this.key_head > 0)
1544             this.key_head--;
1545           if (this.key_head < this.keys.val.length)
1546             this.keys.val.splice (this.key_head,
1547                                   this.keys.val.length - this.key_head);
1548           for (var i = 0; i < n.val.length; i++)
1549             this.keys.val.push (n.val[i]);
1550           return;
1551         }
1552       if (n > 0)
1553         {
1554           this.key_head -= n;
1555           if (this.key_head < 0)
1556             this.key_head = 0;
1557         }
1558       else if (n == 0)
1559         this.key_head = 0;
1560       else
1561       {
1562         this.key_head = - n;
1563         if (this.key_head > this.keys.val.length)
1564           this.key_head = this.keys.val.length;
1565       }
1566     },
1567
1568     pop: function ()
1569     {
1570       if (this.key_head < this.keys.val.length)
1571         this.keys.val.splice (this.key_head, 1);
1572     },
1573
1574     commit: function ()
1575     {
1576       if (this.preedit.length > 0)
1577       {
1578         this.candidate_table.clear ();
1579         this.produced += this.preedit;
1580         this.preedit_replace.call (this, 0, this.preedit.length, '', null);
1581         this.preedit_saved = '';
1582         this.state_pos = 0;
1583         this.commit_key_head = this.key_head;
1584       }
1585     },
1586
1587     shift: function (state)
1588     {
1589       if (state == null)
1590         {
1591           if (this.prev_state == null)
1592             return;
1593           state = this.prev_state;
1594         }
1595
1596       if (state == this.initial_state)
1597         {
1598           if (this.state)
1599             {
1600               this.commit ();
1601               this.keys.val.splice (0, this.key_head);
1602               this.key_head = this.state_key_head = this.commit_key_head = 0;
1603               this.prev_state = null;
1604             }
1605         }
1606       else
1607         {
1608           if (state != this.state)
1609             this.prev_state = this.state;
1610         }
1611       if (state != this.state && state.keymap.map_actions)
1612         this.take_actions (state.keymap.map_actions);
1613       if (! this.state || this.state.title != state.title)
1614         this.changed |= MIM.ChangedStatus.StateTitle;
1615       this.state = state;
1616       this.keymap = state.keymap;
1617       save_state.call (this);
1618     },
1619
1620     Filter: function (key)
1621     {
1622       if (! this.active)
1623         {
1624           Xex.Log ("active = false");
1625           this.key_unhandled = true;
1626           this.unhandled_key = key;
1627           return false;
1628         }
1629       if (key.key == '_reload')
1630         return true;
1631       this.changed = MIM.ChangedStatus.None;
1632       this.produced = '';
1633       this.key_unhandled = false;
1634       this.keys.val.push (key);
1635       var count = 0;
1636       while (this.key_head < this.keys.val.length)
1637         {
1638           if (! handle_key.call (this))
1639             {
1640               if (this.key_head < this.keys.val.length)
1641                 {
1642                   this.unhandled_key = this.keys.val[this.key_head];
1643                   this.keys.val.splice (this.key_head, this.key_head + 1);
1644                 }
1645               if (this.state_key_head > 0)
1646                 this.state_key_head--;
1647               if (this.commit_key_head > 0)
1648                 this.commit_key_head--;
1649               this.key_unhandled = true;
1650               break;
1651             }
1652           if (++count == 10)
1653             {
1654               this.reset (true);
1655               this.key_unhandled = true;
1656               break;
1657             }
1658         }
1659       if (this.keymap == this.initial_state.keymap)
1660         this.commit ();
1661
1662       if (this.commit_key_head > 0)
1663         {
1664           this.keys.val.splice (0, this.commit_key_head);
1665           this.key_head -= this.commit_key_head;
1666           this.state_key_head -= this.commit_key_head;
1667           this.commit_key_head = 0;
1668         }
1669       if (this.key_unhandled)
1670         {
1671           this.keys.val.length = 0;
1672           //this.keys.val.splice (0, this.keys.val.length);
1673           this.key_head = this.state_key_head = this.commit_key_head = 0;
1674         }
1675       if (this.changed & MIM.ChangedStatus.Candidate)
1676         {
1677           if (this.candidate_show && this.candidates)
1678             MIM.show (this);
1679           else
1680             MIM.hide (this);
1681         }
1682       return (! this.key_unhandled
1683               && this.produced.length == 0);
1684     }
1685   };
1686
1687   MIM.create_list = function (node)
1688   {
1689     // Load the list of input methods.
1690     for (node = node.firstChild; node; node = node.nextSibling)
1691       if (node.nodeName == 'input-method')
1692         {
1693           var lang = null, name = null, extra_id = null, file = null;
1694
1695           for (var n = node.firstChild; n; n = n.nextSibling)
1696             {
1697               if (n.nodeName == 'language')
1698                 lang = n.firstChild.nodeValue;
1699               else if (n.nodeName == 'name')
1700                 name = n.firstChild.nodeValue;
1701               else if (n.nodeName == 'extra-id')
1702                 extra_id = n.firstChild.nodeValue;
1703               else if (n.nodeName == 'filename')
1704                 file = n.firstChild.nodeValue;
1705             }
1706           if ((lang == 'ja' && name == 'anthy')
1707               || (lang == 'en' && name == 'ispell'))
1708             continue;
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       {
1724         MIM.im_global = MIM.imextra.t.global;
1725         MIM.im_global.Load ();
1726       }
1727     else
1728       {
1729         MIM.im_global = new MIM.IM ('t', 'nil', 'global', null);
1730         MIM.im_global.load_status = MIM.LoadStatus.Error;
1731       }
1732     node = undefined;
1733     MIM.current = MIM.imlist['t']['latn-post'];
1734     MIM.current.Load ();
1735   }
1736 }) ();
1737
1738 (function () {
1739   var keys = new Array ();
1740   keys[0x09] = 'Tab';
1741   keys[0x08] = 'BackSpace';
1742   keys[0x0D] = 'Return';
1743   keys[0x1B] = 'Escape';
1744   keys[0x20] = 'space';
1745   keys[0x21] = 'Page_Up';
1746   keys[0x22] = 'Page_Down';
1747   keys[0x23] = 'End';
1748   keys[0x24] = 'Home';
1749   keys[0x25] = 'Left';
1750   keys[0x26] = 'Up';
1751   keys[0x27] = 'Right';
1752   keys[0x28] = 'Down';
1753   keys[0x2D] = 'Insert';
1754   keys[0x2E] = 'Delete';
1755   for (var i = 1; i <= 12; i++)
1756     keys[111 + i] = "f" + i;
1757   keys[0x90] = "Num_Lock";
1758   keys[0xF0] = "Caps_Lock";
1759
1760   var keyids = {};
1761   keyids['U+0008'] = 'BackSpace';
1762   keyids['U+0009'] = 'Tab';
1763   keyids['U+0018'] = 'Cancel';
1764   keyids['U+001B'] = 'Escape';
1765   keyids['U+0020'] = 'space';
1766   keyids['U+007F'] = 'Delete';
1767   keyids['PageUp'] = 'Page_Up';
1768   keyids['PageDown'] = 'Page_Down';
1769
1770   var modifiers = {}
1771   modifiers.Shift = 1;
1772   modifiers.Control = 1;
1773   modifiers.Alt = 1;
1774   modifiers.AltGraph = 1;
1775   modifiers.Meta = 1
1776
1777   MIM.decode_key_event = function (event)
1778   {
1779     var key = event.keyIdentifier;
1780
1781     if (key)                    // keydown event of Chrome
1782       {
1783         if (modifiers[key])
1784           return false;
1785         var mod = '';
1786         var shifted = event.shiftKey;
1787         if (event.ctrlKey) mod += 'C-';
1788         if (event.metaKey) mod += 'M-';
1789         if (event.altKey) mod += 'A-';
1790         var keysym = keyids[key];
1791         if (keysym)
1792           key = keysym;
1793         else if (key.match(/^U\+([0-9A-Z]+)$/))
1794           {
1795             if (mod.length == 0)
1796               return false;
1797             var c = parseInt (RegExp.$1, 16);
1798             // Chrome sets, for instance, "U+0x00C1" when the key 'A'
1799             // is typed with control or alt/meta key.
1800             if (c >= 0x80)
1801               c -= 0x80;
1802             if (c >= 0x41 && c <= 0x5A && ! event.shiftKey)
1803               c += 0x20;
1804             key = String.fromCharCode (c);
1805             shifted = false;
1806           }
1807         if (shifted) mod += 'S-';
1808         return new MIM.Key (mod + key);
1809       }
1810     else
1811       {
1812         key = ((event.type == 'keydown' || event.keyCode) ? event.keyCode
1813                : event.charCode ? event.charCode
1814                : false);
1815         if (! key)
1816           return false;
1817         if (event.type == 'keydown')
1818           {
1819             key = keys[key];
1820             if (! key)
1821               return false;
1822             if (event.shiftKey) key = "S-" + key ;
1823           }
1824         else
1825           key = String.fromCharCode (key);
1826       }
1827     if (event.altKey) key = "A-" + key ;
1828     if (event.ctrlKey) key = "C-" + key ;
1829     return new MIM.Key (key);
1830   }
1831 }) ();
1832
1833 MIM.add_event_listener
1834   = (window.addEventListener
1835      ? function (target, type, listener) {
1836        target.addEventListener (type, listener, false);
1837      }
1838      : window.attachEvent
1839      ? function (target, type, listener) {
1840        target.attachEvent ('on' + type,
1841                            function() {
1842                              listener.call (target, window.event);
1843                            });
1844      }
1845      : function (target, type, listener) {
1846        target['on' + type]
1847          = function (e) { listener.call (target, e || window.event); };
1848      });
1849
1850 MIM.debug_print = function () { };
1851
1852 MIM.get_range = function (target, ic)
1853 {
1854   var from, to;
1855   if (target.selectionStart != null) // for Mozilla
1856     {
1857       from = target.selectionStart;
1858       to = target.selectionEnd;
1859     }
1860   else                          // for IE
1861     {
1862       var r = document.selection.createRange ();
1863       var rr = r.duplicate ();
1864
1865       rr.moveToElementText (target);
1866       rr.setEndPoint ('EndToEnd', range);
1867       from = rr.text.length - r.text.length;
1868       to = rr.text.length;
1869     }
1870   if (from == to
1871       && from == ic.range[0] + ic.cursor_pos
1872       && (ic.preedit.length == 0
1873           || ic.preedit == target.value.substring (ic.range[0], ic.range[1])))
1874     return true;
1875   ic.reset (true);
1876   ic.range[0] = from;
1877   ic.range[1] = to;
1878   return false;
1879 };
1880
1881 (function () {
1882   var style_props = {
1883     width: 'width',
1884     height: 'height',
1885     padingLeft: 'padding-left',
1886     paddingRight: 'padding-right',
1887     paddingTop: 'padding-top',
1888     paddintBottom: 'padding-bottom', 
1889     marginRight: 'margin-right',
1890     marginTop: 'margin-top',
1891     borderLeftStyle: 'border-left-style',
1892     borderRightStyle: 'border-right-style',
1893     borderTopStyle: 'border-top-style',
1894     borderBottomStyle: 'border-bottom-style',
1895     borderLeftWidth: 'border-left-width',
1896     borderRightWidth: 'border-right-width',
1897     borderTopWidth: 'border-top-width',
1898     borderBottomWidth: 'border-bottom-width',
1899     fontFamily: 'font-family',
1900     fontSize: 'font-size',
1901     lineHeight: 'line-height',
1902     letterSpacing: 'letter-spacing',
1903     wordSpacing: 'word-spacing' };
1904
1905   function copy_style (from, to)
1906   {
1907     var from_style = getComputedStyle(from,'');
1908     for(var name in style_props)
1909       to.style[name] = from_style[style_props[name]];
1910     to.style.left = from.offsetLeft + 'px'; 
1911     to.style.top = from.offsetTop + 'px';
1912     to.style.width = from.offsetWidth;
1913     to.style.height = from.offsetHeight;
1914     return from_style;
1915   }
1916
1917   var temp;                     // Temporary 'div' element
1918
1919   MIM.get_preedit_pos = function (target, ic)
1920   {
1921     if (! temp)
1922       {
1923         temp = document.createElement ('div');
1924         temp.style.visibility = 'hidden';
1925         temp.style.position = 'absolute';
1926         temp.appendChild (document.createElement ('span'));
1927         temp.appendChild (document.createElement ('span'));
1928         document.getElementsByTagName ('body')[0].appendChild (temp);
1929       }
1930     if (temp.ic != ic)
1931       {
1932         var styles = copy_style (target, temp);
1933         ic.abs_top = (parseInt (styles.marginTop)
1934                       + parseInt (styles.borderTopWidth)
1935                       + parseInt (styles.paddingTop));
1936         ic.abs_left = (parseInt (styles.marginLeft)
1937                        + parseInt (styles.borderLeftWidth)
1938                        + parseInt (styles.paddingLeft));
1939         for (var elm = target.offsetParent; elm; elm = elm.offsetParent)
1940           {
1941             ic.abs_top += elm.offsetTop;
1942             ic.abs_left += elm.offsetLeft;
1943           }
1944         temp.ic = ic;
1945       }
1946     temp.firstChild.innerText = target.value.substring (0, ic.range[0]);
1947     temp.lastChild.innerText
1948       = (ic.range[0] == ic.range[1] ? "|"
1949          : target.value.substring (ic.range[0], ic.range[1]));
1950     ic.abs_y = (ic.abs_top + temp.lastChild.offsetTop
1951                 + temp.lastChild.offsetHeight - target.scrollTop);
1952     ic.abs_x0 = ic.abs_left + temp.lastChild.offsetLeft;
1953     ic.abs_x1 = ic.abs_x0 + temp.lastChild.offsetWidth;
1954   }
1955 }) ();
1956
1957 MIM.update_bar = function (target, ic)
1958 {
1959   if (ic.preedit.length > 0)
1960     {
1961       MIM.get_preedit_pos (target, ic);
1962       if (! ic.bar)
1963         {
1964           ic.bar = document.createElement ('div');
1965           ic.bar.style.position = 'absolute';
1966           ic.bar.style.backgroundColor = "black";
1967           ic.bar.style.minHeight = '1px';
1968           document.getElementsByTagName ('body')[0].appendChild (ic.bar);
1969         }
1970       ic.bar.style.display = 'block'
1971       ic.bar.style.top = ic.abs_y + 'px';
1972       ic.bar.style.left = ic.abs_x0 + 'px';
1973       ic.bar.style.minWidth = ic.bar.style.maxWidth = (ic.abs_x1 - ic.abs_x0) + 'px';
1974     }
1975   else if (ic.bar)
1976     ic.bar.style.display = 'none'
1977 };
1978
1979 MIM.update = function (target, ic, for_focus_out)
1980 {
1981   var text = target.value;
1982   target.value = (text.substring (0, ic.range[0])
1983                   + ic.produced
1984                   + ic.preedit
1985                   + text.substring (ic.range[1]));
1986   ic.range[0] += ic.produced.length;
1987   ic.range[1] = ic.range[0] + ic.preedit.length;
1988   MIM.update_bar (target, ic);
1989   if (! for_focus_out)
1990     {
1991       var pos = ic.range[0] + ic.cursor_pos;
1992       if (target.setSelectionRange) // Mozilla
1993         {
1994           var scrollTop = target.scrollTop;
1995           target.setSelectionRange (pos, pos);
1996           target.scrollTop = scrollTop;
1997         }
1998       else                      // IE
1999         {
2000           var range = target.createTextRange ();
2001           range.moveStart ('character', pos);
2002           range.moveEnd ('character', pos);
2003           range.select ();
2004         }
2005     }
2006 };
2007
2008 (function () {
2009   MIM.show = function (ic)
2010   {
2011     if (! ic.candidates)
2012       return;
2013     var target = ic.target;
2014     MIM.get_preedit_pos (target, ic);
2015     if (! ic.can_node)
2016       {
2017         ic.can_node = document.createElement ('table');
2018         ic.can_node.style.position = 'absolute';
2019         ic.can_node.style.display = 'none';
2020         ic.can_node.style.backgroundColor = "white";
2021         ic.can_node.style.border = "1px solid black";
2022         document.getElementsByTagName ('body')[0].appendChild (ic.can_node);
2023       }
2024
2025     if (ic.changed & MIM.ChangedStatus.CandidateList)
2026       {
2027         while (ic.can_node.childNodes.length > 0)
2028           ic.can_node.removeChild (ic.can_node.firstChild);
2029         var tr = document.createElement ('tr');
2030         var group = ic.candidates.CurrentGroup ();
2031         var td = document.createElement ('td');
2032         td.innerHTML = group[0][1] + '/' + group[0][0];
2033         td.style.color = 'white';
2034         td.style.backgroundColor = 'black';
2035         tr.appendChild (td);
2036         for (var i = 1; i < group.length; i++)
2037           {
2038             var td = document.createElement ('td');
2039             td.noWrap = true;
2040             td.innerHTML = (i < 10 ? i : i == 10 ? '0' : String.fromCharCode (0x60 + (i - 10))) + '.' + group[i];
2041             if (i == group[0][2] + 1)
2042               td.style.backgroundColor = 'lightblue';
2043             tr.appendChild (td);
2044           }
2045         ic.can_node.appendChild (tr);
2046         ic.can_node.style.top = (ic.abs_y + 10) + 'px';
2047         ic.can_node.style.left = ic.abs_x0 + 'px';
2048       }
2049     else
2050       {
2051         var td = ic.can_node.firstElement ().firstElement ().nextElement ();
2052         var col = ic.candidates.CurrentCol ();
2053         for (var i = 0; td; td = td.nextElement ())
2054           td.style.backgroundColor = (i++ == col ? 'lightblue' : 'white');
2055       }
2056     ic.can_node.style.display = 'block';
2057   }
2058
2059   MIM.hide = function (ic)
2060   {
2061     if (ic.can_node)
2062       ic.can_node.style.display = 'none';
2063   }
2064
2065   function real_focus_in (target, ic)
2066   {
2067     if (MIM.get_range (target, ic))
2068       ic.Filter (MIM.Key.FocusIn);
2069     MIM.update (target, ic, false);
2070   }
2071
2072   var focus_in_timer;
2073
2074   MIM.focus_in = function (event)
2075   {
2076     var target = event.target;
2077     Xex.Log ("Focus in " + target.tagName);
2078     focus_in_timer
2079       = setTimeout (function () {real_focus_in (target, target.mim_ic)} , 10);
2080     return true;
2081   }
2082
2083   MIM.click = function (event)
2084   {
2085     var target = event.target;
2086     Xex.Log ("Click in " + target.tagName);
2087     if (focus_in_timer)
2088       {
2089         clearTimeout (focus_in_timer);
2090         focus_in_timer = null;
2091       }
2092     real_focus_in (target, target.mim_ic);
2093   }
2094
2095 }) ();
2096
2097 MIM.focus_out = function (event)
2098 {
2099   var target = event.target;
2100   var ic = target.mim_ic;
2101   Xex.Log ("Focus out " + target.tagName);
2102   MIM.get_range (target, ic);
2103   MIM.debug_print (event, ic);
2104   ic.Filter (MIM.Key.FocusOut);
2105   MIM.update (target, ic, true);
2106   return true;
2107 };
2108
2109 MIM.keydown = function (event)
2110 {
2111   var target = event.target;
2112   if (! (target.type == "text" || target.type == "textarea"))
2113     return;
2114
2115   var ic = target.mim_ic;
2116   if (! ic || ic.im != MIM.current)
2117     {
2118       target.mim_ic = null;
2119       Xex.Log ('creating IC');
2120       ic = new MIM.IC (MIM.current, target);
2121       if (ic.im.load_status != MIM.LoadStatus.Loaded)
2122         return true;
2123       target.mim_ic = ic;
2124       MIM.add_event_listener (target, 'focus', MIM.focus_in);
2125       MIM.add_event_listener (target, 'blur', MIM.focus_out);
2126       MIM.add_event_listener (target, 'click', MIM.click);
2127     }
2128   MIM.get_range (target, ic)
2129   MIM.debug_print (event, ic);
2130   ic.key = MIM.decode_key_event (event);
2131   if (ic.key)
2132     {
2133       try {
2134         var result = ic.Filter (ic.key);
2135       } catch (e) {
2136         Xex.Log ('Error' + e);
2137         throw (e);
2138       }
2139       MIM.update (target, ic, false);
2140       if (! ic.key_unhandled)
2141         event.preventDefault ();
2142     }
2143 };
2144
2145 MIM.keypress = function (event)
2146 {
2147   var target = event.target;
2148   if (! (target.type == "text" || target.type == "textarea"))
2149     return;
2150
2151   var ic = target.mim_ic;
2152   var i;
2153
2154   try {
2155     if (ic.im.load_status != MIM.LoadStatus.Loaded)
2156       return;
2157     if (! ic.key)
2158       ic.key = MIM.decode_key_event (event);
2159     if (! ic.key)
2160       {
2161         ic.reset (true);
2162         return;
2163       }
2164     
2165     try {
2166       var result = ic.Filter (ic.key);
2167     } catch (e) {
2168       Xex.Log ('Error:' + e);
2169       throw (e);
2170     }
2171     MIM.update (target, ic, false);
2172     if (! ic.key_unhandled)
2173       event.preventDefault ();
2174   } catch (e) {
2175     Xex.Log ("error:" + e);
2176     event.preventDefault ();
2177   } finally {
2178     MIM.debug_print (event, ic);
2179   }
2180
2181   return;
2182 };
2183
2184 (function () {
2185   var lang_category = {
2186     European: {
2187       cs: { name: 'Czech' },
2188       da: { name: 'Danish' },
2189       el: { name: 'Greek' },
2190       en: { name: 'English' },
2191       eo: { name: 'Esperanto' },
2192       fr: { name: 'French' },
2193       grc: { name: 'ClassicGreek' },
2194       hr: { name: 'Croatian' },
2195       hy: { name: 'Armenian' },
2196       ka: { name: 'Georgian' },
2197       kk: { name: 'Kazakh' },
2198       ru: { name: 'Russian' },
2199       sk: { name: 'Slovak' },
2200       sr: { name: 'Serbian' },
2201       sv: { name: 'Swedish' },
2202       yi: { name: 'Yiddish' } },
2203     MiddleEast: {
2204       ar: { name: 'Arabic' },
2205       dv: { name: 'Divehi' },
2206       fa: { name: 'Persian' },
2207       he: { name: 'Hebrew' },
2208       kk: { name: 'Kazakh' },
2209       ps: { name: 'Pushto' },
2210       ug: { name: 'Uighur' },
2211       yi: { name: 'Yiddish' } },
2212     SouthAsia: {
2213       as: { name: 'Assamese' },
2214       bn: { name: 'Bengali' },
2215       bo: { name: 'Tibetan' },
2216       gu: { name: 'Gujarati' },
2217       hi: { name: 'Hindi' },
2218       kn: { name: 'Kannada' },
2219       ks: { name: 'Kashmiri' },
2220       ml: { name: 'Malayalam' },
2221       mr: { name: 'Marathi' },
2222       ne: { name: 'Nepali' },
2223       or: { name: 'Oriya' },
2224       pa: { name: 'Panjabi' },
2225       sa: { name: 'Sanskirit' },
2226       sd: { name: 'Sindhi' },
2227       si: { name: 'Sinhalese' },
2228       ta: { name: 'Tamil' },
2229       te: { name: 'Telugu' },
2230       ur: { name: 'Urdu' } },
2231     SouthEastAsia: {
2232       cmc: { name: 'Cham' },
2233       km: { name: 'Khmer'},
2234       lo: { name: 'Lao' },
2235       my: { name: 'Burmese' },
2236       tai: { name: 'Tai Viet' },
2237       th: { name: 'Thai' },
2238       vi: { name: 'Vietanamese' } },
2239     EastAsia: {
2240       ii: { name: 'Yii' },
2241       ja: { name: 'Japanese' },
2242       ko: { name: 'Korean' },
2243       zh: { name: 'Chinese' } },
2244     Other: {
2245       am: { name:  'Amharic' },
2246       ath: { name: 'Carrier' },
2247       bla: { name: 'Blackfoot' },
2248       cr: { name: 'Cree' },
2249       eo: { name: 'Esperanto' },
2250       iu: { name: 'Inuktitut' },
2251       nsk: { name: 'Naskapi' },
2252       oj: { name: 'Ojibwe' },
2253       t: { name: 'Generic' } }
2254   };
2255
2256   function categorize_im ()
2257   {
2258     var cat, lang, list, name;
2259     for (lang in MIM.imlist)
2260       {
2261         list = null;
2262         for (cat in lang_category)
2263           if (lang_category[cat][lang])
2264             {
2265               list = lang_category[cat][lang].list;
2266               if (! list)
2267                 list = lang_category[cat][lang].list = {};
2268               for (name in MIM.imlist[lang])
2269                 list[name] = MIM.imlist[lang][name];
2270             }
2271         if (! list)
2272           for (name in MIM.imlist[lang])
2273             Xex.Log ('no category ' + lang + '-' + name);
2274       }
2275   }
2276
2277   var destroy_timer;
2278   var last_target;
2279
2280   function destroy ()
2281   {
2282     clearTimeout (destroy_timer);
2283     destroy_timer = null;
2284     var target = document.getElementById ('mim-menu');
2285     if (target)
2286       {
2287         for (; last_target && last_target.menu_level;
2288              last_target = last_target.parentLi)
2289           last_target.style.backgroundColor = 'white';
2290         var nodes = target.getElementsByTagName ('ul');
2291         for (var i = 0; i < nodes.length; i++)
2292           nodes[i].style.visibility = 'hidden';
2293         nodes = target.getElementsByTagName ('pre');
2294         for (var i = 0; i < nodes.length; i++)
2295           nodes[i].style.visibility = 'hidden';
2296         document.getElementsByTagName ('body')[0].removeChild (target);
2297       }
2298   }    
2299
2300   function destroy_menu () {
2301     if (! destroy_timer)
2302       destroy_timer = setTimeout (destroy, 1000);
2303     return true;
2304   }
2305
2306   function show_submenu (event)
2307   {
2308     var target = event.target;
2309     if (! target.menu_level)
2310       {
2311         if (! target.parentNode || ! target.parentNode.menu_level)
2312           return true;
2313         target = target.parentNode;
2314       }
2315     if (destroy_timer)
2316       {
2317         clearTimeout (destroy_timer);
2318         destroy_timer = null;
2319       }
2320     if (last_target && target.parentLi != last_target)
2321       {
2322         last_target.style.backgroundColor = 'white';
2323         while (target.menu_level < last_target.menu_level)
2324           {
2325             last_target = last_target.parentLi;
2326             last_target.style.backgroundColor = 'white';
2327           }
2328         var nodes = last_target.getElementsByTagName ('ul');
2329         for (var i = 0; i < nodes.length; i++)
2330           nodes[i].style.visibility = 'hidden';
2331         nodes = last_target.getElementsByTagName ('pre');
2332         for (var i = 0; i < nodes.length; i++)
2333           nodes[i].style.visibility = 'hidden';
2334       }
2335     last_target = target;
2336     target.style.backgroundColor = 'yellow';
2337     if (target.menu_level < 3)
2338       {
2339         if (false)
2340           {
2341             target.lastChild.style.visibility = 'visible';
2342             target.lastChild.style.left = target.clientWidth + 'px';
2343           }
2344         else
2345           {
2346         var left = target.clientWidth;
2347         for (var n = target.firstElement ().nextElement (); n; n = n.nextElement ()) 
2348           {
2349             n.style.visibility = 'visible';
2350             n.style.left = left + 'px';
2351             left += n.clientWidth;
2352           }
2353           }
2354       }
2355     event.preventDefault ();    
2356   }
2357
2358   function select_im (event)
2359   {
2360     var target = event.target;
2361     if (! target.im)
2362       {
2363         if (! target.parentNode || ! target.parentNode.menu_level)
2364           {
2365             event.preventDefault ();
2366             return false;
2367           }
2368         target = target.parentNode;
2369       }
2370     if (target.im)
2371       {
2372         MIM.current = target.im;
2373         MIM.current.Load ();
2374         destroy ();
2375       }
2376     event.preventDefault ();
2377   }
2378
2379   function create_ul (visibility)
2380   {
2381     var ul = document.createElement ('ul');
2382     ul.name = 'elm';
2383     ul.style.position = 'absolute';
2384     ul.style.margin = '0px';
2385     ul.style.padding = '0px';
2386     ul.style.border = '1px solid gray';
2387     ul.style.borderBottom = 'none';
2388     ul.style.top = '-1px';
2389     ul.style.backgroundColor = 'white';
2390     ul.style.visibility = visibility;
2391     return ul;
2392   }
2393
2394   function create_li (level, text)
2395   {
2396     var li = document.createElement ('li');
2397     li.style.position = 'relative';
2398     li.style.margin = '0px';
2399     li.style.padding = '1px';
2400     li.style.borderBottom = '1px solid gray';
2401     li.style.top = '0px';
2402     li.style.listStyle = 'none';
2403     li.menu_level = level;
2404     var nobr = document.createElement ('nobr');
2405     nobr.innerHTML = text;
2406     li.appendChild (nobr);
2407     return li;
2408   }
2409
2410   function create_sep ()
2411   {
2412     var sep = document.createElement ('pre');
2413     sep.name = 'elm';
2414     sep.innerHTML = ' ';
2415     sep.style.position = 'absolute';
2416     sep.style.margin = '0px';
2417     sep.style.padding = '2px';
2418     sep.style.border = '2px solid black';
2419     sep.style.top = '-1px';
2420     sep.style.backgroundColor = 'black';
2421     sep.style.visibility = 'hidden';
2422     return sep;
2423   }
2424
2425   var menu;
2426
2427   function create_menu (event)
2428   {
2429     var target = event.target;
2430
2431     if (! ((target.type == "text" || target.type == "textarea")
2432            && event.which == 1 && event.ctrlKey))
2433       return;
2434     if (! menu)
2435       {
2436         categorize_im ();
2437         menu = create_ul ('visible');
2438         menu.style.fontFamily = 'sans-serif';
2439         menu.style.fontWeight = 'bold';
2440         menu.id = 'mim-menu';
2441         menu.onmousedown = select_im;
2442         menu.onmouseover = show_submenu;
2443         menu.onmouseout = destroy_menu;
2444         for (var catname in lang_category)
2445           {
2446             var cat = lang_category[catname];
2447             var li = create_li (1, catname);
2448             li.appendChild (create_sep ());
2449             var sub = create_ul ('hidden');
2450             for (var langname in cat)
2451               {
2452                 var lang = cat[langname];
2453                 if (! lang.list)
2454                   continue;
2455                 var sub_li = create_li (2, lang.name);
2456                 sub_li.parentLi = li;
2457                 sub_li.appendChild (create_sep ());
2458                 var subsub = create_ul ('hidden');
2459                 for (var name in lang.list)
2460                   {
2461                     var im = lang.list[name];
2462                     var subsub_li = create_li (3, im.name);
2463                     subsub_li.parentLi = sub_li;
2464                     subsub_li.im = im;
2465                     subsub.appendChild (subsub_li);
2466                   }
2467                 sub_li.appendChild (subsub);
2468                 sub.appendChild (sub_li);
2469               }
2470             li.appendChild (sub);
2471             menu.appendChild (li);
2472           }
2473         lang_category = null;
2474       }
2475     menu.style.left = (event.clientX - 10) + "px";
2476     menu.style.top = (event.clientY - 10) + "px";
2477     document.getElementsByTagName ('body')[0].appendChild (menu);
2478   };
2479
2480   MIM.init = function ()
2481   {
2482     MIM.add_event_listener (window, 'keydown', MIM.keydown);
2483     MIM.add_event_listener (window, 'keypress', MIM.keypress);
2484     MIM.add_event_listener (window, 'mousedown', create_menu);
2485     Xex.Load (MIM.server, "imlist.xml", MIM.create_list);
2486   };
2487 }) ();