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