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