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