1 /* ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3 *
4 * The contents of this file are subject to the Mozilla Public License Version
5 * 1.1 (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 * http://www.mozilla.org/MPL/
8 *
9 * Software distributed under the License is distributed on an "AS IS" basis,
10 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 * for the specific language governing rights and limitations under the
12 * License.
13 *
14 * The Initial Developer of the Original Code is Fireinput Inc.
15 *
16 * Portions created by the Initial Developer are Copyright (C) 2007
17 * the Initial Developer. All Rights Reserved.
18 *
19 * Contributor(s):
20 * Olly Ja <ollyja@gmail.com>
21 *
22 * Alternatively, the contents of this file may be used under the terms of
23 * either the GNU General Public License Version 2 or later (the "GPL"), or
24 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
25 * in which case the provisions of the GPL or the LGPL are applicable instead
26 * of those above. If you wish to allow use of your version of this file only
27 * under the terms of either the GPL or the LGPL, and not to allow others to
28 * use your version of this file under the terms of the MPL, indicate your
29 * decision by deleting the provisions above and replace them with the notice
30 * and other provisions required by the GPL or the LGPL. If you do not delete
31 * the provisions above, a recipient may use your version of this file under
32 * the terms of any one of the MPL, the GPL or the LGPL.
33 *
34 * ***** END LICENSE BLOCK *****
35 */
36 var imeInterfaceUI = [
37 /* all button tooltips */
38 {id: "fireinputToggleHalfButton", strKey: "fireinput.toggle.half.button", attribute: "tooltiptext"},
39 {id: "fireinputTogglePunctButton", strKey: "fireinput.toggle.punct.button", attribute: "tooltiptext"},
40 {id: "fireinputToggleIMEButton", strKey: "fireinput.toggle.ime.button", attribute: "tooltiptext"},
41 {id: "fireinputPrevSelButton", strKey: "fireinput.previous.selection", attribute: "tooltiptext"},
42 {id: "fireinputNextSelButton", strKey: "fireinput.next.selection", attribute: "tooltiptext"},
43 {id: "fireinputLongPrevSelButton", strKey: "fireinput.previous.selection", attribute: "tooltiptext"},
44 {id: "fireinputLongNextSelButton", strKey: "fireinput.next.selection", attribute: "tooltiptext"},
45 {id: "fireinputIMEBarCloseButton", strKey: "fireinput.close.IME", attribute: "tooltiptext"},
46 {id: "inputMethod", strKey: "fireinput.switch.inputmethod.button", attribute: "tooltiptext"},
47
48 {id: "fireinputContextEnhanceWordTable", strKey: "fireinput.wordtable.enhance", attribute: "label"},
49 {id: "fireinputContextEnableIME", strKey: "fireinput.show.IME", attribute: "label"},
50 {id: "fireinputContextSwitchEncoding", strKey: "fireinput.encoding.switch", attribute: "label"},
51 {id: "fireinputContextSwitchZHToBG", strKey: "fireinput.encoding.zhtobg", attribute: "label"},
52 {id: "fireinputContextSwitchBGToZH", strKey: "fireinput.encoding.bgtozh", attribute: "label"},
53 {id: "menuPinyinQuan", strKey: "fireinput.pinyin.quan.label", attribute: "label"},
54 {id: "menuPinyinShuangZiGuang", strKey: "fireinput.pinyin.shuang.ziguang.label", attribute: "label"},
55 {id: "menuPinyinShuangMS", strKey: "fireinput.pinyin.shuang.ms.label", attribute: "label"},
56 {id: "menuPinyinShuangChineseStar", strKey: "fireinput.pinyin.shuang.chinesestar.label", attribute: "label"},
57 {id: "menuPinyinShuangSmartABC", strKey: "fireinput.pinyin.shuang.smartabc.label", attribute: "label"},
58 {id: "menuWubi86", strKey: "fireinput.wubi86.label", attribute: "label"},
59 {id: "menuWubi98", strKey: "fireinput.wubi98.label", attribute: "label"},
60 {id: "menuCangjie5", strKey: "fireinput.cangjie5.label", attribute: "label"},
61 {id: "inputHistoryList", strKey: "fireinput.history.list", attribute: "label"},
62 {id: "fireinputHelp", strKey: "fireinput.help.label", attribute: "label"},
63 {id: "fireinputSearchButton", strKey: "fireinput.search.label", attribute: "value"},
64 {id: "fireinputContextSelectImage", strKey: "fireinput.context.select.image", attribute: "label"}
65
66 ];
67
68
69 var imeInputModeValues = [
70 {name: ENCODING_ZH, label: "fireinput.method.chinese.value"},
71 {name: ENCODING_BIG5, label: "fireinput.method.big5.value"},
72 {name: ENCODING_EN, label: "fireinput.method.english.value"}
73 ];
74
75
76 var Fireinput =
77 {
78 // debug: 0 disable, non-zero enable
79 debug: 0,
80 // Fireinput statusbar status
81 myRunStatus: false,
82 // IME mode. False for english mode, otherwise it's IME mode
83 myInputStatus: false,
84 // instance of IME
85 myIME: null,
86 // IME input bar stauts.
87 myIMEInputBarStatus: false,
88
89 // IME mode. The mode can ZH or EN. Chinese can only be typed under ZH mode
90 // Shortkey: type v if enable english mode, and space will resume original mode back
91 // reset by space /enter or target change
92 myIMEMode: IME_MODE_ZH,
93
94 // Input mode. It will decide which encoding will be used(Simplified Chinese or Big5)
95 myInputMode: ENCODING_ZH,
96
97 // caret focus event
98 myEvent: null,
99 // caret focus target
100 myTarget: null,
101
102 // save user typing history
103 mySaveHistory: true,
104
105 // half/full letter mode
106 myHalfMode: 0,
107
108 // half/full letter mode
109 myPunctMode: 0,
110
111 // allow Input Keys
112 myAllowInputKey: "",
113
114 // IME Schema
115 myIMESchema: SMART_PINYIN,
116
117 // long table
118 myLongTable: null,
119
120 // removed ime panel - used to position switch
121 myRemovedFireinputPanel: null,
122
123 // a list of enabled IME
124 myEnabledIME: [],
125
126 // fireinput init function.
127 initialize: function()
128 {
129 FireinputPref.addObserver(this, false);
130 this.registerFireinputObserver();
131
132 // register event listener to trigger when context menu is invoked.
133 try
134 {
135 document.getElementById('contentAreaContextMenu').addEventListener('popupshowing',
136 fireinput_onPopupShowing,
137 false);
138 } catch(e) { }
139
140 // initialize the open hotkey
141 var handle = document.getElementById("key_enableFireinput");
142 var openKey = fireinputPrefGetDefault("openKey");
143 if(/,/.test(openKey))
144 {
145 var openKeys = openKey.split(",");
146 if(handle) handle.setAttribute("modifiers", openKeys[0]);
147 if(handle) handle.setAttribute("keycode", openKeys[1]);
148 }
149
150 // initialize IME bar position
151 this.initIMEBarPosition();
152
153 // load shortkey settings
154 FireinputKeyBinding.init();
155
156 this.toggleIMEMenu();
157 // initial default IME
158 this.myIME = this.getDefaultIME();
159
160 // load long table
161 FireinputLongTable.init();
162
163 // setup tooltips
164 this.loadIMEPref();
165
166 // initialize Pref interfaces
167 fireinputPrefInit();
168
169 this.loadIMEPrefByID("fireinputStatusBar", "fireinput.statusbar.tooltip.open", "tooltiptext");
170
171 // for first run only
172 FireinputVersion.checkFirstRun();
173 },
174
175 registerFireinputObserver: function()
176 {
177 // register an observer
178 var os = FireinputXPC.getService("@mozilla.org/observer-service;1", "nsIObserverService");
179 // add new phrase from table management
180 os.addObserver(this, "fireinput-add-new-phrase", false);
181 // monitor application quit event
182 os.addObserver(this, "quit-application-requested", false);
183 },
184
185 getDefaultIME: function(schema)
186 {
187 if(typeof(schema) == 'undefined')
188 this.myIMESchema = fireinputPrefGetDefault("defaultInputMethod");
189 else
190 this.myIMESchema = schema;
191
192 var ime = null;
193
194 switch(this.myIMESchema)
195 {
196 case CANGJIE_5:
197 ime = new Cangjie();
198 if (!ime.isEnabled())
199 {
200 // if the default set is not valid, fall back to pinyin
201 if(typeof(schema) == 'undefined')
202 return this.getDefaultIME(SMART_PINYIN);
203 return null;
204 }
205 break;
206 case WUBI_86:
207 case WUBI_98:
208 ime = new Wubi();
209 if(!ime.isEnabled())
210 {
211 // if the default set is not valid, fall back to pinyin
212 if(typeof(schema) == 'undefined')
213 return this.getDefaultIME(SMART_PINYIN);
214 return null;
215 }
216 break;
217 default:
218 ime = new SmartPinyin();
219 if(!ime.isEnabled() && typeof(schema) == 'undefined')
220 {
221 // if the default set is not valid, we search for other method in an order of wubi, canjei5
222 ime = this.getDefaultIME(WUBI_86) || this.getDefaultIME(CANGJIE_5);
223 return ime;
224 }
225 break;
226 }
227
228 ime.setSchema(this.myIMESchema);
229 ime.loadTable();
230 this.myAllowInputKey = ime.getAllowedInputKey();
231 // disable conflict shortkey
232 FireinputKeyBinding.disableConflictKey(this.myAllowInputKey);
233 return ime;
234 },
235
236
237 toggleIMEMenu: function()
238 {
239 var hideIMEList = fireinputPrefGetDefault("hiddenInputMethod") || [];
240 var wime = new Wubi();
241
242 // check for hidden IME list
243 if(!wime.isEnabled() || inArray(hideIMEList,WUBI_86))
244 {
245 var handle = document.getElementById("menuWubi86");
246 if(handle) handle.style.display = "none";
247 var handle = document.getElementById("imeWubi86");
248 if(handle) handle.style.display = "none";
249 }
250 else
251 {
252 this.myEnabledIME.push(WUBI_86);
253 var handle = document.getElementById("menuWubi86");
254 if(handle) handle.style.display = "";
255 var handle = document.getElementById("imeWubi86");
256 if(handle) handle.style.display = "";
257 }
258
259 if(!wime.isEnabled() || inArray(hideIMEList,WUBI_98))
260 {
261 var handle = document.getElementById("menuWubi98");
262 if(handle) handle.style.display = "none";
263 var handle = document.getElementById("imeWubi98");
264 if(handle) handle.style.display = "none";
265
266 }
267 else
268 {
269 this.myEnabledIME.push(WUBI_98);
270 var handle = document.getElementById("menuWubi98");
271 if(handle) handle.style.display = "";
272 var handle = document.getElementById("imeWubi98");
273 if(handle) handle.style.display = "";
274
275 }
276 var cime = new Cangjie();
277 if(!cime.isEnabled() || inArray(hideIMEList,CANGJIE_5))
278 {
279 var handle = document.getElementById("menuCangjie5");
280 if(handle) handle.style.display = "none";
281 var handle = document.getElementById("imeCangjie5");
282 if(handle) handle.style.display = "none";
283 }
284 else
285 {
286 this.myEnabledIME.push(CANGJIE_5);
287 var handle = document.getElementById("menuCangjie5");
288 if(handle) handle.style.display = "";
289 var handle = document.getElementById("imeCangjie5");
290 if(handle) handle.style.display = "";
291 }
292
293 if(!wime.isEnabled() && !cime.isEnabled())
294 {
295 // autoinsert is only for wubi or cangjie
296 var handle = document.getElementById("autoInsert");
297 if(handle) handle.style.display = "none";
298 }
299 else
300 {
301 var handle = document.getElementById("autoInsert");
302 if(handle) handle.style.display = "";
303 }
304
305 var sime = new SmartPinyin();
306 if(!sime.isEnabled())
307 {
308 var handle = document.getElementById("fireinputAMB");
309 if(handle) handle.style.display = "none";
310 }
311 else
312 {
313 var handle = document.getElementById("fireinputAMB");
314 if(handle) handle.style.display = "";
315 }
316
317 if(!sime.isEnabled() || inArray(hideIMEList,SMART_PINYIN))
318 {
319 var handle = document.getElementById("menuPinyinQuan");
320 if(handle) handle.style.display = "none";
321 var handle = document.getElementById("imePinyinQuan");
322 if(handle) handle.style.display = "none";
323 }
324 else
325 {
326 this.myEnabledIME.push(SMART_PINYIN);
327 var handle = document.getElementById("menuPinyinQuan");
328 if(handle) handle.style.display = "";
329 var handle = document.getElementById("imePinyinQuan");
330 if(handle) handle.style.display = "";
331
332 }
333
334 if(!sime.isEnabled() || inArray(hideIMEList,ZIGUANG_SHUANGPIN))
335 {
336 var handle = document.getElementById("menuPinyinShuangZiGuang");
337 if(handle) handle.style.display = "none";
338 var handle = document.getElementById("imePinyinShuangZiGuang");
339 if(handle) handle.style.display = "none";
340 }
341 else
342 {
343 this.myEnabledIME.push(ZIGUANG_SHUANGPIN);
344 var handle = document.getElementById("menuPinyinShuangZiGuang");
345 if(handle) handle.style.display = "";
346 var handle = document.getElementById("imePinyinShuangZiGuang");
347 if(handle) handle.style.display = "";
348 }
349
350 if(!sime.isEnabled() || inArray(hideIMEList,MS_SHUANGPIN))
351 {
352 var handle = document.getElementById("menuPinyinShuangMS");
353 if(handle) handle.style.display = "none";
354 var handle = document.getElementById("imePinyinShuangMS");
355 if(handle) handle.style.display = "none";
356 }
357 else
358 {
359 this.myEnabledIME.push(MS_SHUANGPIN);
360 var handle = document.getElementById("menuPinyinShuangMS");
361 if(handle) handle.style.display = "";
362 var handle = document.getElementById("imePinyinShuangMS");
363 if(handle) handle.style.display = "";
364 }
365
366 if(!sime.isEnabled() || inArray(hideIMEList,CHINESESTAR_SHUANGPIN))
367 {
368 var handle = document.getElementById("menuPinyinShuangChineseStar");
369 if(handle) handle.style.display = "none";
370 var handle = document.getElementById("imePinyinShuangChineseStar");
371 if(handle) handle.style.display = "none";
372 }
373 else
374 {
375 this.myEnabledIME.push(CHINESESTAR_SHUANGPIN);
376 var handle = document.getElementById("menuPinyinShuangChineseStar");
377 if(handle) handle.style.display = "";
378 var handle = document.getElementById("imePinyinShuangChineseStar");
379 if(handle) handle.style.display = "";
380 }
381
382 if(!sime.isEnabled() || inArray(hideIMEList,SMARTABC_SHUANGPIN))
383 {
384 var handle = document.getElementById("menuPinyinShuangSmartABC");
385 if(handle) handle.style.display = "none";
386 var handle = document.getElementById("imePinyinShuangSmartABC");
387 if(handle) handle.style.display = "none";
388 }
389 else
390 {
391 this.myEnabledIME.push(SMARTABC_SHUANGPIN);
392 var handle = document.getElementById("menuPinyinShuangSmartABC");
393 if(handle) handle.style.display = "";
394 var handle = document.getElementById("imePinyinShuangSmartABC");
395 if(handle) handle.style.display = "";
396 }
397 },
398
399 // if certain IME has been disabled or enabled, we need to reload the list
400 reloadIMEMenu: function()
401 {
402 this.myEnabledIME = [];
403 this.toggleIMEMenu();
404 // if default IME has been disabled, just choose next available one
405 if(!inArray(this.myEnabledIME, this.myIMESchema))
406 {
407 this.switchInputMethod();
408 }
409 },
410
411 loadIMEPrefByID: function(id, strKey, attribute)
412 {
413 var defaultLanguage = fireinputPrefGetDefault("interfaceLanguage");
414 var value = FireinputUtils.getLocaleString(strKey + defaultLanguage);
415 var handle = document.getElementById(id);
416 if(!handle)
417 return;
418
419 // to check whether the shortcut keystring exists
420 var found =value.match(/%(.+)%/i);
421 if(found)
422 {
423 var keystring = FireinputKeyBinding.getKeyString(found[1]);
424 value = value.replace(found[0], keystring);
425 }
426
427 handle.setAttribute(attribute, value);
428 },
429
430 loadIMEPref: function(name)
431 {
432 // get default language first
433
434 if(!name || name == "interfaceLanguage")
435 {
436 var defaultLanguage = fireinputPrefGetDefault("interfaceLanguage");
437
438 // update UI
439 for(var i =imeInterfaceUI.length-1; i>=0; i--)
440 {
441 var id = imeInterfaceUI[i].id;
442
443 var handle = document.getElementById(id);
444 if(!handle)
445 continue;
446
447 var strKey = imeInterfaceUI[i].strKey;
448 var attr = imeInterfaceUI[i].attribute;
449
450 var value = FireinputUtils.getLocaleString(strKey + defaultLanguage);
451 // to check whether the shortcut keystring exists
452 var found =value.match(/%(\w+)%/ig);
453 if(found)
454 {
455 for(var n=0; n<found.length; n++)
456 {
457 var keystring = FireinputKeyBinding.getKeyString(found[n].replace(/%/g, ''));
458 value = value.replace(found[n], keystring);
459 }
460 }
461 handle.setAttribute(attr, value);
462 }
463
464 // update icon status text
465 if(this.myRunStatus)
466 this.loadIMEPrefByID("fireinputStatusBar", "fireinput.statusbar.tooltip.close", "tooltiptext");
467 else
468 this.loadIMEPrefByID("fireinputStatusBar", "fireinput.statusbar.tooltip.open", "tooltiptext");
469
470 // refresh menu if language is changed
471
472 if(name)
473 {
474 FireinputHelp.refreshMenu();
475 FireinputThemes.refreshMenu();
476 FireinputSpecialChar.refreshMenu();
477 FireinputEmotions.refreshMenu();
478 fireinputPrefInit();
479 }
480
481 this.loadIMEPrefByID("fireinputToggleIMEButton", "fireinput.method.chinese.value", "label");
482 }
483
484 //update value. The label of menu should be updated if language is changed
485 if(!name || name == "defaultInputMethod" || name == "interfaceLanguage")
486 {
487 var value = this.myIMESchema;
488 if(name == "defaultInputMethod")
489 value = fireinputPrefGetDefault("defaultInputMethod");
490
491 var element = document.getElementById("inputMethod");
492 element.setAttribute("label", FireinputUtils.getIMENameString(value));
493 element.setAttribute("value", value);
494
495 // only toggle input method if the setting has been updated
496 if(name == "defaultInputMethod")
497 this.toggleInputMethod();
498 }
499
500 if(!name || name == "saveHistory")
501 {
502 this.mySaveHistory = fireinputPrefGetDefault("saveHistory");
503 }
504
505 if(name && name == 'IMEBarPosition')
506 {
507 this.toggleIMEBarPosition();
508 }
509
510 if(!name || name == "wordselectionNum")
511 {
512 // we don't have do this when defaultIME is created as it will be initialized here anyway
513 this.myIME.setNumWordSelection(fireinputPrefGetDefault("wordselectionNum"));
514 }
515
516 // reset IMEPanel pref
517 FireinputIMEPanel.initPref();
518 },
519
520
521 toggleFireinput: function(forceOpen, forceLoad)
522 {
523 var pos = fireinputPrefGetDefault("IMEBarPosition");
524 var id = document.getElementById("fireinputIMEBar_" + pos);
525 var toggleOff = forceOpen == undefined ? !id.hidden : !forceOpen;
526 id.hidden = toggleOff;
527 this.myRunStatus = !toggleOff;
528
529 if(!toggleOff)
530 {
531 this.loadIMEPrefByID("fireinputStatusBar", "fireinput.statusbar.tooltip.close", "tooltiptext");
532
533 window.addEventListener('keypress', fireinput_onKeyPress, true);
534 // window.addEventListener('keydown', fireinput_onKeyDown, true);
535 window.addEventListener('keyup', fireinput_onKeyUp, true);
536 this.myInputStatus = true;
537 this.setInputMode(fireinputPrefGetDefault("defaultInputEncoding"));
538 this.displayAjaxService(forceLoad==undefined ? false : forceLoad);
539 }
540 else
541 {
542 // close the IME inputbar
543 if(this.myIMEInputBarStatus)
544 {
545 FireinputIMEPanel.hideAndCleanInput();
546 }
547 this.resetIME();
548
549 this.loadIMEPrefByID("fireinputStatusBar", "fireinput.statusbar.tooltip.open", "tooltiptext");
550 window.removeEventListener('keypress', fireinput_onKeyPress, true);
551 // window.removeEventListener('keydown', fireinput_onKeyDown, true);
552 window.removeEventListener('keyup', fireinput_onKeyUp, true);
553 }
554 },
555
556 disableIME: function()
557 {
558 if(!this.myRunStatus)
559 return;
560 // if it's input enabled, disable it and turn off key listener
561 if(this.myInputStatus)
562 {
563 FireinputIMEPanel.hideAndCleanInput();
564 this.resetIME();
565 window.removeEventListener('keypress', fireinput_onKeyPress, true);
566 // window.removeEventListener('keydown', fireinput_onKeyDown, true);
567 window.removeEventListener('keyup', fireinput_onKeyUp, true);
568 }
569 },
570
571 enableIME: function()
572 {
573 if(!this.myRunStatus)
574 return;
575
576 if(!this.myInputStatus)
577 {
578 this.myInputStatus = true;
579 window.addEventListener('keypress', fireinput_onKeyPress, true);
580 // window.addEventListener('keydown', fireinput_onKeyDown, true);
581 window.addEventListener('keyup', fireinput_onKeyUp, true);
582 }
583 },
584
585 toggleIME: function()
586 {
587 if(!this.myRunStatus)
588 return;
589
590 if(this.myInputStatus)
591 this.setInputMode(ENCODING_EN);
592 else
593 this.setInputMode(this.myInputMode);
594 },
595
596 resetIME: function ()
597 {
598 this.myInputStatus = false;
599 this.myIMEInputBarStatus = false;
600 },
601
602 getInputBarStatus: function()
603 {
604 return this.myIMEInputBarStatus;
605 },
606
607 // MIGHT NOT need
608 setInputBarStatus: function(status)
609 {
610 this.myIMEInputBarStatus = status;
611 },
612
613 getCurrentIME: function()
614 {
615 return this.myIME;
616 },
617
618 getTarget: function()
619 {
620 return this.myTarget;
621 },
622
623 getEvent: function()
624 {
625 return this.myEvent;
626 },
627
628 toggleInputMethod: function()
629 {
630 // close the IME inputbar
631 if(this.myIMEInputBarStatus)
632 {
633 FireinputIMEPanel.hideAndCleanInput();
634 }
635 this.myIMEInputBarStatus = false;
636
637 var method = document.getElementById("inputMethod").getAttribute("value");
638 if(this.myIMESchema == method)
639 return;
640
641 if(method == WUBI_86 || method == WUBI_98)
642 {
643 if(this.myIME)
644 this.myIME.flushUserTable();
645 this.myIME = null;
646 this.myIME = new Wubi();
647 this.myIME.setSchema(method);
648 this.myIME.loadTable();
649 }
650 else if(method == CANGJIE_5)
651 {
652 if(this.myIME)
653 this.myIME.flushUserTable();
654 this.myIME = null;
655 this.myIME = new Cangjie();
656 this.myIME.setSchema(method);
657 this.myIME.loadTable();
658 }
659 else if(this.myIMESchema == WUBI_86 ||
660 this.myIMESchema == WUBI_98 ||
661 this.myIMESchema == CANGJIE_5)
662 {
663 // we need to load table only if the current schema is not pinyin schema. Otherwise just set new schema
664 if(this.myIME)
665 this.myIME.flushUserTable();
666 this.myIME = null;
667 this.myIME = new SmartPinyin();
668 this.myIME.setSchema(method);
669 this.myIME.loadTable();
670 }
671 else
672 this.myIME.setSchema(method);
673
674 this.myIMESchema = method;
675
676 // enable zh input
677 this.setInputMode(fireinputPrefGetDefault("defaultInputEncoding"));
678
679 // set num of word choice
680 this.myIME.setNumWordSelection(fireinputPrefGetDefault("wordselectionNum"));
681
682 this.myAllowInputKey = this.myIME.getAllowedInputKey();
683 // disable conflict shortkey
684 FireinputKeyBinding.disableConflictKey(this.myAllowInputKey);
685
686 // notify to all regarding this change
687 this.notify(FIREINPUT_IME_CHANGED);
688
689 if(!this.myIME.isSchemaEnabled())
690 {
691 alert("火输中文输入: 对不起,此输入法字库没有安装,请到http://www.fireinput.com/forum/ 去下载字库");
692 return;
693 }
694 },
695
696 // loop through next enabled input method
697 switchInputMethod: function()
698 {
699 var method = document.getElementById("inputMethod").getAttribute("value");
700 var i = 0;
701 for(; i<this.myEnabledIME.length; i++)
702 {
703 if(method == this.myEnabledIME[i])
704 break;
705 }
706 if(i >= this.myEnabledIME.length-1)
707 i = -1;
708
709 // update ime/schema in pref
710 fireinputPrefSave('defaultInputMethod', this.myEnabledIME[i+1]);
711 },
712
713 getModeString: function(mode)
714 {
715 for(var i=imeInputModeValues.length-1; i>=0; i--)
716 {
717 if(mode == imeInputModeValues[i].name)
718 return imeInputModeValues[i].label;
719 }
720
721 // otherwise return first one
722 return imeInputModeValues[0].label;
723
724 },
725
726 // set IME mode - not disable keyboard listening
727 setIMEMode: function(mode)
728 {
729 if(this.myIMEMode == mode)
730 return;
731
732 this.myIMEMode = mode;
733
734 switch(mode)
735 {
736 case IME_MODE_ZH:
737 var modeString = this.getModeString(this.myInputMode);
738 this.loadIMEPrefByID("fireinputToggleIMEButton", modeString, "label");
739 break;
740 case IME_MODE_EN:
741 var modeString = this.getModeString(mode);
742 this.loadIMEPrefByID("fireinputToggleIMEButton", modeString, "label");
743 break;
744 default:
745 return;
746 }
747 },
748
749 toggleIMEMode: function()
750 {
751 if(!this.myInputStatus)
752 return;
753
754 if(this.myIMEMode == IME_MODE_ZH)
755 this.setIMEMode(IME_MODE_EN);
756 else
757 this.setIMEMode(IME_MODE_ZH);
758 },
759
760 toggleDisableIMEMode: function()
761 {
762 // if IME inputStatus is still true, disable it regardless of ime_mode
763 if(this.myInputStatus)
764 {
765 this.myInputStatus = false;
766 this.setIMEMode(IME_MODE_EN);
767 }
768 else
769 {
770 this.myInputStatus = true;
771 this.setIMEMode(IME_MODE_ZH);
772 }
773 },
774
775 setInputMode: function(mode)
776 {
777 var modeString = this.getModeString(mode);
778 this.loadIMEPrefByID("fireinputToggleIMEButton", modeString, "label");
779 switch(mode)
780 {
781 case ENCODING_ZH:
782 case ENCODING_BIG5:
783 this.myInputMode = mode;
784 this.myIMEMode = IME_MODE_ZH;
785 this.myIME.setEncoding(mode);
786 this.enableIME();
787 break;
788 case ENCODING_EN:
789 this.myIMEMode = IME_MODE_EN;
790 this.disableIME();
791 break;
792 default:
793 return;
794 }
795 },
796
797 toggleInputMode: function()
798 {
799 if(this.myIMEMode == IME_MODE_EN)
800 {
801 this.setInputMode(ENCODING_ZH);
802 fireinputPrefSave("defaultInputEncoding", ENCODING_ZH);
803 return;
804 }
805
806 switch(this.myInputMode)
807 {
808 case ENCODING_ZH:
809 this.setInputMode(ENCODING_BIG5);
810 // remember encoding
811 fireinputPrefSave("defaultInputEncoding", ENCODING_BIG5);
812 break;
813
814 case ENCODING_BIG5:
815 this.setInputMode(ENCODING_EN);
816 break;
817 default:
818 this.setInputMode(ENCODING_ZH);
819 }
820 },
821
822 toggleEncodingMode: function()
823 {
824 if(this.myIMEMode == IME_MODE_EN)
825 return;
826
827 switch(this.myInputMode)
828 {
829 case ENCODING_ZH:
830 this.setInputMode(ENCODING_BIG5);
831 // remember encoding
832 fireinputPrefSave("defaultInputEncoding", ENCODING_BIG5);
833 break;
834
835 case ENCODING_BIG5:
836 this.setInputMode(ENCODING_ZH);
837 fireinputPrefSave("defaultInputEncoding", ENCODING_ZH);
838 break;
839 }
840
841 },
842
843 toggleHalfMode: function()
844 {
845 if(this.myIME.isHalfLetterMode())
846 {
847 var id = document.getElementById("fireinputToggleHalfButton");
848 if(id)
849 {
850 id.style.listStyleImage = "url('chrome://fireinput/skin/full-letter.png')";
851 this.myIME.setFullLetterMode();
852 }
853 }
854 else
855 {
856 var id = document.getElementById("fireinputToggleHalfButton");
857 if(id)
858 {
859 id.style.listStyleImage = "url('chrome://fireinput/skin/half-letter.png')";
860 this.myIME.setHalfLetterMode();
861 }
862
863 }
864 },
865
866 togglePunctMode: function()
867 {
868 if(this.myIME.isHalfPunctMode())
869 {
870 var id = document.getElementById("fireinputTogglePunctButton");
871 if(id)
872 {
873 id.style.listStyleImage = "url('chrome://fireinput/skin/full-punct.png')";
874 this.myIME.setFullPunctMode();
875 }
876 }
877 else
878 {
879 var id = document.getElementById("fireinputTogglePunctButton");
880 if(id)
881 {
882 id.style.listStyleImage = "url('chrome://fireinput/skin/half-punct.png')";
883 this.myIME.setHalfPunctMode();
884 }
885
886 }
887 },
888
889 onClickStatusIcon: function(event)
890 {
891 if (event.button != 0)
892 return;
893 this.toggleFireinput();
894 },
895
896 fireinputContext: function()
897 {
898 document.getElementById('fireinputContextEnableIME').hidden = (!(gContextMenu.onTextInput) || Fireinput.myRunStatus);
899 document.getElementById('fireinputContextSelectImage').hidden = !(gContextMenu.onImage);
900 // init add table menu
901 var hidden = !(gContextMenu.isTextSelected);
902 if(!hidden)
903 {
904 var selectedText = FireinputLongTable.showSelection();
905 if(!selectedText)
906 {
907 document.getElementById('fireinputContextEnhanceWordTable').hidden = !hidden;
908 return;
909 }
910 var defaultLanguage = fireinputPrefGetDefault("interfaceLanguage");
911 var value = FireinputUtils.getLocaleString("fireinput.wordtable.enhance" + defaultLanguage);
912
913 var handle = document.getElementById('fireinputContextEnhanceWordTable');
914 label = value + '"' + selectedText + '"';
915 handle.setAttribute("label", label);
916 handle.hidden = hidden;
917 }
918 else
919 document.getElementById('fireinputContextEnhanceWordTable').hidden = hidden;
920 },
921
922 // all Observers
923 observe: function(subject, topic, data)
924 {
925
926 if(topic == "fireinput-add-new-phrase")
927 {
928 if(this.myIME)
929 this.myIME.storeOneUpdatePhrase(data);
930 }
931
932 if(topic == "quit-application-requested")
933 {
934 if(this.myIME)
935 this.myIME.flushUserTable();
936 FireinputLongTable.flushLongTable();
937 }
938
939 if(topic == "nsPref:changed" && data && data.indexOf(prefDomain) != -1)
940 {
941 var name = data.substr(prefDomain.length+1);
942 this.updatePref(name);
943
944
945 // we need to reload key settings just in case if anything has been changed
946 if(data && data.match(/Key$/))
947 {
948 FireinputKeyBinding.refreshKeySetting();
949 // short key might have been changed, re-disable/enable conflict shortkeys
950 FireinputKeyBinding.disableConflictKey(this.myAllowInputKey);
951 // simulate a interface language
952 this.loadIMEPref('interfaceLanguage');
953 }
954
955 // if hiddenInputMethod option is changed, reload IME menu and settings
956 if(data == prefDomain + ".hiddenInputMethod")
957 this.reloadIMEMenu();
958 }
959 },
960
961 updatePref: function(name)
962 {
963 this.loadIMEPref(name);
964 },
965
966 notify: function(nevent)
967 {
968 switch(nevent)
969 {
970 case FIREINPUT_IME_CHANGED:
971 FireinputUtils.notify(null, "fireinput-ime-changed", this.getCurrentIME().getIMEType()+'');
972 break;
973 }
974
975 return true;
976 },
977
978 initIMEBarPosition: function()
979 {
980 var pos = fireinputPrefGetDefault("IMEBarPosition");
981 var rempos = (pos == IME_BAR_BOTTOM) ? IME_BAR_TOP : IME_BAR_BOTTOM;
982
983 var el = document.getElementById("fireinputIMEBar_" + rempos);
984 if(el.firstChild)
985 {
986 this.myRemovedFireinputPanel = el.removeChild(el.firstChild);
987 }
988 },
989
990 toggleIMEBarPosition: function()
991 {
992 var pos = fireinputPrefGetDefault("IMEBarPosition");
993 var rempos = (pos == IME_BAR_BOTTOM) ? IME_BAR_TOP : IME_BAR_BOTTOM;
994
995 var oldPanel = null;
996 var el = document.getElementById("fireinputIMEBar_" + rempos);
997 if(el.firstChild)
998 {
999 oldPanel = el.removeChild(el.firstChild);
1000 }
1001 el.hidden = true;
1002
1003 // if sth wrong. don't proceed
1004 if(!oldPanel)
1005 return;
1006
1007 // new position
1008 el = document.getElementById("fireinputIMEBar_" + pos);
1009 if(!el.firstChild)
1010 {
1011 el.appendChild(this.myRemovedFireinputPanel);
1012 }
1013 el.hidden = false;
1014
1015 this.myRemovedFireinputPanel = oldPanel;
1016
1017 this.toggleIMEMenu();
1018 this.loadIMEPref();
1019
1020 // initialize Pref interfaces
1021 fireinputPrefInit();
1022
1023 this.toggleFireinput(true, true);
1024
1025 },
1026
1027 isTargetATextBox : function ( node )
1028 {
1029 if(!node)
1030 return 0;
1031
1032 if (node instanceof HTMLInputElement)
1033 return (node.type == "text" || node.type == "password")
1034
1035 return (node instanceof HTMLTextAreaElement);
1036 },
1037
1038 isValidTarget: function(event)
1039 {
1040 var documentTarget = false;
1041 var target = event.explicitOriginalTarget;
1042 if(target == null)
1043 target = event.target;
1044
1045 if(target == null || (target && target.type == "password"))
1046 return {target: target, valid: false, documentTarget: documentTarget};
1047
1048 if(target instanceof XULElement && target.id == "urlbar")
1049 return {target: target, valid: false, documentTarget: documentTarget};
1050
1051 if(target.hasAttribute('_no_cjk_input') &&
1052 (target.getAttribute('_no_cjk_input') == "true" || target.getAttribute('_no_cjk_input')=="1"))
1053 return {target: target, valid: false, documentTarget: documentTarget};
1054
1055 if(!target.setSelectionRange)
1056 {
1057 var wrappedTarget = document.commandDispatcher.focusedElement;
1058
1059 if(wrappedTarget instanceof HTMLInputElement ||
1060 wrappedTarget instanceof HTMLTextAreaElement)
1061 {
1062 if(!this.isTargetATextBox(wrappedTarget))
1063 return {target: target, valid: false, documentTarget: documentTarget};
1064 else if(wrappedTarget.tagName == 'html:input' || wrappedTarget.tagName == 'html:textarea')
1065 {
1066 //xul window input element
1067 wrappedTarget.boxObject = target.boxObject;
1068 if(wrappedTarget.type == 'password')
1069 return {target: wrappedTarget, valid: false, documentTarget: documentTarget};
1070 else
1071 return {target: wrappedTarget, valid: true, documentTarget: documentTarget};
1072 }
1073
1074 }
1075 else
1076 {
1077 var twin = document.commandDispatcher.focusedWindow;
1078 if (twin) {
1079 var editingSession = twin.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
1080 .getInterface(Components.interfaces.nsIWebNavigation)
1081 .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
1082 .getInterface(Components.interfaces.nsIEditingSession);
1083 if (!editingSession.windowIsEditable(twin))
1084 return {target: target, valid: false, documentTarget: documentTarget};
1085
1086 documentTarget = true;
1087 }
1088 // oTarget is only will be used in editor mode
1089 return {target: twin, valid: true, documentTarget: documentTarget};
1090 }
1091 }
1092 else if(target.readOnly)
1093 return {target: target, valid: false, documentTarget: documentTarget};
1094
1095 return {target: target, valid: true, documentTarget: documentTarget};
1096
1097 },
1098
1099 keyUpListener: function(event)
1100 {
1101 if(!this.myRunStatus || this.myIMEInputBarStatus)
1102 return;
1103
1104 if(event.keyCode == FireinputKeyBinding.getKeyActionCode("quickToggleIMEKey"))
1105 {
1106 if(this.myChangeIMEModeEvent)
1107 {
1108 this.myChangeIMEModeEvent = false;
1109 this.toggleIMEMode();
1110 }
1111 if(this.myDisableIMEModeEvent)
1112 {
1113 this.myDisableIMEModeEvent = false;
1114 this.toggleDisableIMEMode();
1115 }
1116 }
1117 },
1118
1119 keyDownListener: function(event)
1120 {
1121 // FireinputLog.debug(this, "this.myRunStatus: " + this.myRunStatus +", this.myIMEInputBarStatus: " + this.myIMEInputBarStatus);
1122 // monitor key which has action associated. For those keys without action associated,
1123 // it will be handled individually.
1124 if(!this.myIMEInputBarStatus)
1125 {
1126 FireinputKeyBinding.checkKeyEvent(event);
1127 }
1128
1129 // if fireinput is not enabled, just return here
1130 if(!this.myRunStatus)
1131 return;
1132
1133 var keyCode = event.keyCode;
1134
1135 if(event.keyCode == FireinputKeyBinding.getKeyActionCode("quickToggleIMEKey"))
1136 {
1137 var now = new Date().getTime();
1138 // let trigger a timer here. If two continuous quickToggleIMEKeys are pressed, it will disable fireinput in global manner.
1139 // it could only be re-enabled by another two continuous presses
1140 if(this.myChangeIMEModeEventTimer && (now - this.myChangeIMEModeEventTimer) < 500)
1141 {
1142 this.myDisableIMEModeEvent = true;
1143 }
1144 else
1145 {
1146 this.myChangeIMEModeEvent = true;
1147 }
1148 // record timer
1149 this.myChangeIMEModeEventTimer = now;
1150 }
1151
1152 // we don't use alt/shift key
1153 // should be used to handle the long list string
1154 if(event.altKey && !event.shiftKey)
1155 {
1156 // handle ctrl + alt + f to insert Fireinput Ad
1157 if(event.ctrlKey && keyCode == KeyEvent.DOM_VK_F)
1158 {
1159 this.displayADString();
1160 return;
1161 }
1162
1163 if(keyCode == KeyEvent.DOM_VK_RETURN)
1164 {
1165 FireinputWebSearch.load();
1166 return;
1167
1168 }
1169 }
1170
1171 // handle alt + ctrl + shift + D to load debug panel
1172 if(event.shiftKey && event.ctrlKey && event.altKey && keyCode == KeyEvent.DOM_VK_D)
1173 {
1174 FireinputLog.showDebug();
1175 return;
1176 }
1177
1178 // check some key if control is pressed
1179 if(event.altKey && this.myIMEInputBarStatus)
1180 {
1181 // alt + number: choose a long table
1182 if(keyCode > KeyEvent.DOM_VK_0 && keyCode <= KeyEvent.DOM_VK_5)
1183 {
1184 event.preventDefault();
1185 event.stopPropagation();
1186 FireinputLongTable.insertCharToTarget(String.fromCharCode(keyCode));
1187 }
1188 return;
1189 }
1190
1191 // return if these keys are pressed
1192 // if(event.altKey || event.ctrlKey || event.shiftKey || event.metaKey)
1193 // return;
1194
1195
1196 // ESC: close input window
1197 if(keyCode == KeyEvent.DOM_VK_ESCAPE)
1198 {
1199 if(this.myIMEInputBarStatus)
1200 {
1201 event.preventDefault();
1202 event.stopPropagation();
1203 FireinputIMEPanel.hideAndCleanInput();
1204 return;
1205 }
1206 return;
1207 }
1208
1209 // HOME: display the first set of input (first 9)
1210 if(keyCode == KeyEvent.DOM_VK_HOME)
1211 {
1212 if(!this.myIMEInputBarStatus)
1213 return;
1214
1215 if(FireinputIMEPanel.getComposeEnabled() || FireinputIMEPanel.getIMEInputFieldFocusedStatus())
1216 return;
1217
1218 event.preventDefault();
1219
1220 FireinputIMEPanel.prevSel("HOME");
1221 return;
1222 }
1223
1224 // END: display the last set of input (last 9)
1225 if(keyCode == KeyEvent.DOM_VK_END)
1226 {
1227 if(!this.myIMEInputBarStatus)
1228 return;
1229
1230 if(FireinputIMEPanel.getComposeEnabled())
1231 return;
1232
1233 if(FireinputIMEPanel.getIMEInputFieldFocusedStatus())
1234 {
1235 var idf = document.getElementById("fireinputField");
1236 if(idf.selectionEnd < idf.value.length)
1237 {
1238 FireinputIMEPanel.setInputChar(idf.value);
1239 FireinputIMEPanel.findCharWithDelay();
1240 }
1241 return;
1242 }
1243
1244 event.preventDefault();
1245 FireinputIMEPanel.nextSel("END");
1246 return;
1247 }
1248
1249
1250 // repeat last words
1251 if(keyCode == KeyEvent.DOM_VK_F2)
1252 {
1253 if(this.myIMEInputBarStatus)
1254 return;
1255
1256 event.preventDefault();
1257 // FireinputLog.debug(this,"F2: " + FireinputIMEPanel.getLastSelectedElementValue());
1258 FireinputUtils.insertCharAtCaret(this.myTarget, FireinputIMEPanel.getLastSelectedElementValue());
1259 // add into long table
1260 if(this.mySaveHistory)
1261 FireinputLongTable.addIntoLongTable(this.myTarget.target, FireinputIMEPanel.getLastSelectedElementValue());
1262
1263 return;
1264 }
1265
1266 //left arrow key.
1267 if(keyCode == KeyEvent.DOM_VK_LEFT)
1268 {
1269 if(!this.myIMEInputBarStatus)
1270 return;
1271
1272 // inputField is focused
1273 if(FireinputIMEPanel.getComposeEnabled())
1274 return;
1275 var idf = document.getElementById("fireinputField");
1276 //FireinputLog.debug(this,"idf.value:" + idf.value);
1277 if(idf.selectionStart <= 1)
1278 {
1279 // it's at the beginning
1280 if(FireinputComposer.hasSet())
1281 {
1282 // the composer is formed. Now we need to disable last composed word
1283 var key = FireinputComposer.removeLastFromPanel();
1284 if(key)
1285 {
1286 var pos = key.length+1;
1287 idf.value = key + idf.value;
1288 idf.setSelectionRange(pos, pos)
1289 FireinputIMEPanel.setInputChar(key);
1290 FireinputIMEPanel.findCharWithDelay();
1291 }
1292 }
1293 }
1294 else
1295 {
1296 // there are still some room on the left. Show the selected word from 0 until this position
1297 var subInputKeys = idf.value.substring(0, idf.selectionStart-1);
1298 // FireinputLog.debug(this,"subInputKeys:" + subInputKeys);
1299 FireinputIMEPanel.setInputChar(subInputKeys);
1300 FireinputIMEPanel.findCharWithDelay();
1301 }
1302
1303 }
1304
1305 //right arrow key.
1306 if(keyCode == KeyEvent.DOM_VK_RIGHT)
1307 {
1308 if(!this.myIMEInputBarStatus)
1309 return;
1310
1311 // inputField is focused
1312 if(FireinputIMEPanel.getComposeEnabled())
1313 return;
1314 var idf = document.getElementById("fireinputField");
1315 if((idf.selectionEnd+1) <= idf.value.length)
1316 {
1317 // there are still some room on the right. Show the selected word from 0 until this position
1318 var subInputKeys = idf.value.substring(0, idf.selectionEnd+1);
1319 FireinputIMEPanel.setInputChar(subInputKeys);
1320 FireinputIMEPanel.findCharWithDelay();
1321 }
1322 }
1323
1324
1325 // backspace: remove the input bar. If the input bar is empty, remove target value
1326 if(keyCode == KeyEvent.DOM_VK_BACK_SPACE)
1327 {
1328 // inputField is focused
1329 if(FireinputIMEPanel.getComposeEnabled())
1330 return;
1331
1332 if(this.myIMEInputBarStatus)
1333 {
1334 var id = document.getElementById("fireinputIMEContainer");
1335 var idf = document.getElementById("fireinputField");
1336 if(idf.value.length ==0 && !FireinputComposer.hasSet())
1337 {
1338 id.hidePopup();
1339 }
1340 else
1341 {
1342 event.preventDefault();
1343
1344 FireinputIMEPanel.setInputChar(idf.value);
1345
1346 if(FireinputIMEPanel.getInputChar().length > 0)
1347 {
1348 var selectionEnd = idf.selectionEnd;
1349 idf.value = idf.value.substring(0, selectionEnd -1) + idf.value.substring(selectionEnd, idf.value.length);
1350 FireinputIMEPanel.setInputChar(idf.value);
1351 FireinputUtils.setCaretTo(idf, selectionEnd-1);
1352 }
1353 // if the caret is not at the end of position, only select the char from 0 to this position
1354 if(FireinputIMEPanel.getInputChar().length <= 0 || idf.selectionStart <= 0)
1355 {
1356 var key = FireinputComposer.removeLastFromPanel();
1357 if(key)
1358 {
1359 idf.value = key + idf.value.substring(0, idf.value.length);
1360 FireinputIMEPanel.setInputChar(key);
1361 FireinputUtils.setCaretTo(idf, key.length);
1362 }
1363 }
1364 else if(idf.selectionStart != idf.value.length)
1365 {
1366 var subInputKeys = idf.value.substring(0, idf.selectionStart);
1367 FireinputIMEPanel.setInputChar(subInputKeys);
1368 }
1369
1370 if(idf.value.length ==0)
1371 {
1372 id.hidePopup();
1373 return;
1374 }
1375 FireinputIMEPanel.findCharWithDelay();
1376 }
1377 }
1378
1379 return;
1380 }
1381
1382 // page down for next
1383 if(FireinputKeyBinding.isTrue("pageDownKey", event))
1384 {
1385 if(this.myIMEInputBarStatus)
1386 {
1387 event.preventDefault();
1388 event.stopPropagation();
1389 FireinputIMEPanel.nextSel();
1390 }
1391 return;
1392 }
1393 // page up for previous
1394 if(FireinputKeyBinding.isTrue("pageUpKey", event))
1395 {
1396 if(this.myIMEInputBarStatus)
1397 {
1398 event.preventDefault();
1399 event.stopPropagation();
1400 FireinputIMEPanel.prevSel();
1401 }
1402
1403 return;
1404 }
1405
1406 this.KeyEventInsert(event);
1407
1408 },
1409
1410 KeyEventInsert: function(event)
1411 {
1412 // if it enters from keyPress but keyDown has already captured this event, don't proceed
1413 if(event.getPreventDefault())
1414 return;
1415
1416 // enter: select the highlight column. If the input bar is not activated, go to default action
1417 if(FireinputKeyBinding.isTrue("selectFirstKey", event) || event.keyCode == KeyEvent.DOM_VK_RETURN)
1418 {
1419 // here we try to trim the long sentence. Surely we should have a better way to organize this.
1420 // maybe a fast text search library or sth ?
1421 if(!this.myIMEInputBarStatus && event.keyCode == KeyEvent.DOM_VK_RETURN)
1422 {
1423 if(this.mySaveHistory)
1424 FireinputLongTable.flush();
1425 return true;
1426 }
1427
1428 if(this.myIMEInputBarStatus)
1429 {
1430 FireinputIMEPanel.insertCharByIndex(event, 1);
1431 }
1432 return true;
1433 }
1434
1435 // handle the second
1436 if(FireinputKeyBinding.isTrue("selectSecondKey", event))
1437 {
1438 if(this.myIMEInputBarStatus)
1439 {
1440 FireinputIMEPanel.insertCharByIndex(event, 2);
1441 }
1442
1443 return true;
1444 }
1445
1446 if(FireinputKeyBinding.isTrue("selectThirdKey", event))
1447 {
1448 if(this.myIMEInputBarStatus)
1449 {
1450 FireinputIMEPanel.insertCharByIndex(event, 3);
1451 }
1452 return true;
1453 }
1454
1455 return false;
1456 },
1457
1458 keyPressListener: function(event)
1459 {
1460 if(!this.myRunStatus)
1461 return;
1462
1463 var keyCode = event.keyCode;
1464 var charCode = event.charCode;
1465
1466 // if shift key and other key has been pressed together, reset the IMEmode back
1467 if((event.shiftKey || event.altKey || event.ctrlKey) && (keyCode ||charCode))
1468 {
1469 this.myChangeIMEModeEvent = false;
1470 }
1471
1472 // return if these keys are pressed
1473 if(event.altKey || event.ctrlKey || event.metaKey || (event.shiftKey && !(keyCode || charCode)))
1474 return;
1475
1476 // return here if the mode is non-chinese mode
1477 if(this.myIMEMode != IME_MODE_ZH)
1478 return;
1479
1480 // somehow if the key is pressed too fast following a valid inputchar, the keydown event might not be triggered
1481 // we need to check them here again. Please note that the test shows the issue only show up in one-char press,
1482 // so we are safe to just checking keyCode or charCode here
1483
1484 if(this.KeyEventInsert(event))
1485 return;
1486
1487 // if it's not printable char, just return here
1488 if(charCode ==0)
1489 return;
1490
1491 var targetInfo = this.isValidTarget(event);
1492 if(!targetInfo.valid)
1493 return;
1494
1495 var target = targetInfo.target;
1496 var documentTarget = targetInfo.documentTarget;
1497
1498 // the IME mode needs to be reset if the target has changed
1499 // if(this.myTarget && this.myTarget.target != target)
1500 // this.setIMEMode(IME_MODE_ZH);
1501 // keep the real event and target if inputfield has been focused
1502 if(!FireinputIMEPanel.getIMEInputFieldFocusedStatus() && !FireinputIMEPanel.getComposeEnabled() &&(!this.myTarget || (this.myTarget.target != target)))
1503 {
1504 this.myEvent = event;
1505 this.myTarget = {target: target, documentTarget: documentTarget};
1506 }
1507
1508 // remember the caret position before focus switch
1509 // only for HTMLInputElement or TextAreaElement
1510 if(target.setSelectionRange)
1511 {
1512 this.myTarget.selectionStart = this.myTarget.target.selectionStart;
1513 this.myTarget.selectionEnd = this.myTarget.target.selectionEnd;
1514 if(typeof(this.myTarget.focused) == 'undefined')
1515 this.myTarget.focused = 0;
1516 }
1517
1518
1519 var key = String.fromCharCode(charCode);
1520
1521 // 1..2..9
1522 if(charCode > KeyEvent.DOM_VK_0 && charCode <= KeyEvent.DOM_VK_9)
1523 {
1524 if(this.myIMEInputBarStatus)
1525 {
1526 FireinputIMEPanel.insertCharByIndex(event, key);
1527 }
1528
1529 return;
1530 }
1531
1532 // small case a-z
1533 if(this.myAllowInputKey.indexOf(key) >= 0)
1534 {
1535 // don't relay on input event. It's slow.
1536 // return if compose is enabled
1537 if(FireinputIMEPanel.getComposeEnabled())
1538 {
1539 return;
1540 }
1541
1542 event.preventDefault();
1543 // open IME input window
1544
1545 if(!this.myIMEInputBarStatus)
1546 {
1547 // reset popupNode to fix inputbar popup position issue
1548 document.popupNode = null;
1549 var xpos = 0;
1550 var ypos = 0;
1551 if(target.boxObject)
1552 {
1553 var id = document.getElementById("fireinputIMEContainer");
1554 xpos = target.boxObject.screenX;
1555 ypos = target.boxObject.screenY + target.boxObject.height+10;
1556 id.showPopup(document.documentElement, xpos, ypos, "popup", null, null);
1557 }
1558 else if(!documentTarget)
1559 {
1560 // HTML input/textarea element
1561 xpos = FireinputUtils.findPosX(target) -
1562 FireinputUtils.getDocumentScrollLeft(document.commandDispatcher.focusedWindow.document);
1563 ypos = FireinputUtils.findPosY(target) -
1564 FireinputUtils.getDocumentScrollTop(document.commandDispatcher.focusedWindow.document);
1565
1566 ypos += target.clientHeight;
1567
1568 // get FF header height/position
1569 var h = document.getElementById("navigator-toolbox");
1570 xpos += h.boxObject.screenX;
1571 ypos += h.boxObject.screenY + h.boxObject.height + 10;
1572
1573 // care about tab header
1574 if(gBrowser.getStripVisibility())
1575 {
1576 ypos += gBrowser.mStrip.boxObject.height;
1577 }
1578
1579 // if(ypos > (window.innerHeight - 20))
1580 // ypos = window.innerHeight - 20;
1581
1582 if(ypos <= 20)
1583 ypos = 20;
1584 //FireinputLog.debug(this,"xpos:" + xpos);
1585 //FireinputLog.debug(this,"ypos:" + ypos);
1586 //FireinputLog.debug(this,"window.screenY:" + window.screenY);
1587 //FireinputLog.debug(this,"window.screenX: " + window.screenX);
1588 //FireinputLog.debug(this,"window.innerHeight:" + window.innerHeight);
1589 //FireinputLog.debug(this,"window.innerWidth:" + window.innerWidth);
1590 //FireinputLog.debug(this,"window.outerHeight:" + window.outerHeight);
1591 //FireinputLog.debug(this,"window.outerWidth:" + window.outerWidth);
1592 var id = document.getElementById("fireinputIMEContainer");
1593 id.showPopup(document.documentElement, xpos, ypos, "popup", null, null);
1594
1595 }
1596 else
1597 {
1598 // rich editor
1599 var p = target.frameElement;
1600
1601 xpos = FireinputUtils.findPosX(p);
1602 ypos = FireinputUtils.findPosY(p);
1603
1604 // FireinputLog.debug(this, "p: " + p + ", tagname: " + p.tagName + ", id: " + p.id);
1605 // some iframes are inside of another iframe. To get the top iframe, we need to
1606 // loop through the parentNode to find out whic one is first iframe. Not sure what the
1607 // best way to do here
1608 var parentNode = p ? p.parentNode : null;
1609 while(parentNode)
1610 {
1611 // document node
1612 if (parentNode.nodeType == 9)
1613 {
1614 parentNode = parentNode.defaultView.frameElement;
1615 if(parentNode && parentNode.tagName == 'IFRAME')
1616 {
1617 xpos += FireinputUtils.findPosX(parentNode);
1618 ypos += FireinputUtils.findPosY(parentNode);
1619 p = parentNode;
1620 }
1621 else
1622 break;
1623 }
1624 else
1625 parentNode = parentNode.parentNode;
1626
1627 }
1628
1629 // gmail main body is built of iframe. So we need to check both ownerDocument and contentDocument
1630 // scroll attribute to ajust popup position
1631
1632 if(p != target.frameElement)
1633 {
1634 xpos -= (FireinputUtils.getDocumentScrollLeft(p.ownerDocument) + FireinputUtils.getDocumentScrollLeft(p.contentDocument));
1635 ypos -= (FireinputUtils.getDocumentScrollTop(p.ownerDocument) + FireinputUtils.getDocumentScrollTop(p.contentDocument));
1636 }
1637 else
1638 {
1639 xpos -= FireinputUtils.getDocumentScrollLeft(p.ownerDocument);
1640 ypos -= FireinputUtils.getDocumentScrollTop(p.ownerDocument);
1641 }
1642
1643 // var frameHeight = p.contentDocument.height;
1644 // ypos += p.clientHeight;
1645
1646 // get FF header height/position
1647 var h = document.getElementById("navigator-toolbox");
1648 xpos += h.boxObject.screenX;
1649 ypos += h.boxObject.screenY + h.boxObject.height;
1650
1651 // care about tab header
1652 if(gBrowser.getStripVisibility())
1653 {
1654 ypos += gBrowser.mStrip.boxObject.height;
1655 }
1656
1657 // most rich editor has toolbar on top, put popup on top of toolbar
1658 ypos -= 30;
1659
1660 if(ypos <= 20)
1661 ypos = 20;
1662
1663 var id = document.getElementById("fireinputIMEContainer");
1664 id.showPopup(document.documentElement, xpos, ypos, "popup", null, null);
1665 }
1666
1667 // we have to set this true immediately after showPopup as the onpopupshown handler might be slow to catch
1668 // initial key event (which is trggered when fireinput was initialized first time)
1669 this.myIMEInputBarStatus = true;
1670
1671 // The reason I have put here is because textbox won't be able to initialize correctly without first assign the value
1672 // thus either idf.selectionEnd or idf.value will throw exception. It only happens when fireinput is loaded first time
1673 var idf = document.getElementById("fireinputField");
1674 FireinputIMEPanel.setInputChar(key);
1675 idf.value = key;
1676 }
1677 else
1678 {
1679 var idf = document.getElementById("fireinputField");
1680 // see whether the caret is
1681 if(idf.selectionEnd < idf.value.length)
1682 {
1683 var selectionEnd = idf.selectionEnd;
1684 var fvalue = idf.value.substring(0, selectionEnd) + key;
1685 idf.value = fvalue + idf.value.substring(selectionEnd, idf.value.length);
1686 FireinputIMEPanel.setInputChar(fvalue);
1687 FireinputUtils.setCaretTo(idf, selectionEnd+1);
1688 }
1689 else
1690 {
1691 FireinputIMEPanel.setInputChar(FireinputIMEPanel.getInputChar() + key);
1692 idf.value += key;
1693 }
1694 }
1695
1696 // FireinputLog.debug(this,"idf.value:" + idf.value);
1697 FireinputLog.debug(this,"call findChar when idf.value:" + idf.value);
1698 //The findchar has to invoked here to resolve the performance issue
1699 FireinputIMEPanel.findChar();
1700 return;
1701 }
1702
1703 // use single quot to separate pinyin input
1704 if(this.myIMEInputBarStatus)
1705 {
1706 if(key == "'" && !FireinputIMEPanel.getComposeEnabled())
1707 {
1708 event.preventDefault();
1709 var idf = document.getElementById("fireinputField");
1710 // only one is allowed
1711 if(idf.value.length > 0 && idf.value.substr(idf.value.length-1, 1) != "'")
1712 {
1713 if(idf.selectionEnd < idf.value.length)
1714 {
1715 var selectionEnd = idf.selectionEnd;
1716 var fvalue = idf.value.substring(0, selectionEnd) + key;
1717 idf.value = fvalue + idf.value.substring(selectionEnd, idf.value.length);
1718 FireinputIMEPanel.setInputChar(fvalue);
1719 FireinputUtils.setCaretTo(idf, selectionEnd+1);
1720 }
1721 else
1722 {
1723 FireinputIMEPanel.setInputChar(FireinputIMEPanel.getInputChar() + key);
1724 idf.value += key;
1725 }
1726 return;
1727 }
1728 }
1729
1730 // won't allow any other chars if IME inputbar is opened
1731 if(key != "'") // && (FireinputIMEPanel.getIMEInputFieldFocusedStatus() || FireinputIMEPanel.getComposeEnabled()))
1732 {
1733 event.preventDefault();
1734 event.stopPropagation();
1735 }
1736 }
1737 // convert to Full width letters. If the event has getPreventDefault true which might be set in keydown listener
1738 // we will ignore too
1739 if(!this.myIMEInputBarStatus && !event.getPreventDefault())
1740 {
1741 var fullLetter = this.myIME.convertLetter(charCode);
1742 if(typeof(fullLetter) == "object")
1743 {
1744 event.preventDefault();
1745 for (var s in fullLetter)
1746 {
1747 FireinputUtils.insertCharAtCaret(this.myTarget, fullLetter[s]);
1748 }
1749 }
1750 else if(typeof(fullLetter) != "undefined")
1751 {
1752 event.preventDefault();
1753 FireinputUtils.insertCharAtCaret(this.myTarget, fullLetter);
1754 }
1755
1756 // time to flush long table
1757 if(this.mySaveHistory)
1758 FireinputLongTable.flush();
1759 }
1760
1761 return;
1762 },
1763
1764 IMEWindowHidden: function()
1765 {
1766 // restore the focus to target if inputfield has been focused
1767 if(FireinputIMEPanel.getIMEInputFieldFocusedStatus() || FireinputIMEPanel.getComposeEnabled())
1768 {
1769 FireinputUtils.setFocus(this.myTarget.target);
1770 this.myTarget.focused = 1;
1771 }
1772
1773 FireinputComposer.reset();
1774 FireinputLongTable.hidePanel();
1775 this.myIMEInputBarStatus = false;
1776 FireinputIMEPanel.setIMEInputFieldFocusedStatus(false);
1777 FireinputIMEPanel.hideAndCleanInput();
1778 },
1779
1780 IMEWindowShown: function()
1781 {
1782 // if the first few input key too fast, the hidden event is ahead of shown event(because of GUI time consuming).
1783 // In this case, this.myIMEInputBarStatus might be false, and we certainly don't want to change focus again
1784 if(this.myIMEInputBarStatus)
1785 FireinputUtils.setFocus(document.getElementById("fireinputField"));
1786 },
1787
1788 IMEWindowShowing: function()
1789 {
1790 // the showing event is ahead of hidden event and shown event. It's safe to
1791 // set inputbar status here
1792 this.myIMEInputBarStatus = true;
1793 },
1794
1795 displayAjaxService: function(forceLoad)
1796 {
1797 FireinputSpecialChar.load(forceLoad);
1798 FireinputThemes.load(forceLoad);
1799 FireinputEmotions.load(forceLoad);
1800 FireinputHelp.load(forceLoad);
1801 FireinputTable.load(forceLoad);
1802 },
1803
1804 insertSpecialCharAt: function(event, sourceType, insertMode)
1805 {
1806 var clickTarget = event.target;
1807 // FireinputLog.debug(this, "value=" + clickTarget.value);
1808
1809 var target = document.commandDispatcher.focusedElement;
1810 var documentTarget = false;
1811 if(target)
1812 {
1813 if(!this.isTargetATextBox(target))
1814 return;
1815 }
1816 else
1817 {
1818 // editable DOM document(iframe:designMode=On)
1819 target = document.commandDispatcher.focusedWindow;
1820 if (target) {
1821 var editingSession = target.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
1822 .getInterface(Components.interfaces.nsIWebNavigation)
1823 .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
1824 .getInterface(Components.interfaces.nsIEditingSession);
1825 if (!editingSession.windowIsEditable(target))
1826 return;
1827
1828 documentTarget = true;
1829 }
1830 }
1831
1832 var inputTarget = {target: target, documentTarget: documentTarget, focused: 0};
1833
1834 var value = clickTarget.getAttribute("hiddenvalue");
1835 if(!value)
1836 value = clickTarget.getAttribute("value");
1837
1838 if(target.setSelectionRange)
1839 {
1840 inputTarget.selectionStart = target.selectionStart;
1841 inputTarget.selectionEnd = target.selectionEnd;
1842 }
1843
1844 FireinputUtils.insertCharAtCaret(inputTarget, value, sourceType, insertMode);
1845 },
1846
1847 showConfig: function(module, param)
1848 {
1849 window.openDialog('chrome://fireinput/content/fireinputConfig.xul',
1850 'fireinputConfigWindow',
1851 'chrome,modal=no,resizable=no',
1852 module, param);
1853 },
1854
1855 showInputMethodSetting: function()
1856 {
1857 var param = this.myEnabledIME.join(",");
1858 this.showConfig('inputmethod', param);
1859 },
1860
1861 showkeyConfigWindow: function()
1862 {
1863 var param = this.myEnabledIME.join(",");
1864 this.showConfig('keyconfig', param);
1865 },
1866
1867 showInputSettingWindow: function()
1868 {
1869 var param = this.myEnabledIME.join(",");
1870 this.showConfig('inputwindow', param);
1871 },
1872
1873 displayADString: function()
1874 {
1875 var ADString = "\n——————————————————————————\n";
1876 ADString += "火输中文输入法(Fireinput.com)";
1877 FireinputUtils.insertCharAtCaret(this.myTarget, ADString);
1878 }
1879
1880
1881 };
1882
1883 // Create event listener.
1884 window.addEventListener('load', fireinput_onLoad, false);
1885 window.addEventListener('keydown', fireinput_onKeyDown, true);
1886
1887
1888 // event handlers
1889 function fireinput_onLoad()
1890 {
1891 Fireinput.initialize();
1892 }
1893
1894 function fireinput_onKeyUp(event)
1895 {
1896 Fireinput.keyUpListener(event);
1897 }
1898
1899 function fireinput_onKeyDown(event)
1900 {
1901 Fireinput.keyDownListener(event);
1902 }
1903 function fireinput_onKeyPress(event)
1904 {
1905 Fireinput.keyPressListener(event);
1906 }
1907
1908 function fireinput_onPopupShowing(event)
1909 {
1910 Fireinput.fireinputContext(event);
1911 }
syntax highlighted by Code2HTML, v. 0.9.1