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 * OllyJa <ollyja@gmail.com>
21 * Chun-Kwong Wong <chunkwong.wong@gmail.com>
22 *
23 * Alternatively, the contents of this file may be used under the terms of
24 * either the GNU General Public License Version 2 or later (the "GPL"), or
25 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
26 * in which case the provisions of the GPL or the LGPL are applicable instead
27 * of those above. If you wish to allow use of your version of this file only
28 * under the terms of either the GPL or the LGPL, and not to allow others to
29 * use your version of this file under the terms of the MPL, indicate your
30 * decision by deleting the provisions above and replace them with the notice
31 * and other provisions required by the GPL or the LGPL. If you do not delete
32 * the provisions above, a recipient may use your version of this file under
33 * the terms of any one of the MPL, the GPL or the LGPL.
34 *
35 * ***** END LICENSE BLOCK *****
36 */
37 var Cangjie = function(){};
38
39 Cangjie.prototype = extend(new FireinputIME(),
40 {
41 // 0 to disable debug or non zero to enable debug
42 debug: 0,
43
44 // the name of IME
45 name: IME_CANGJIE,
46
47 // array to keep all matched words
48 charArray: null,
49
50 // current position of charArray
51 charIndex: 0,
52
53 // invalid input key
54 validInputKey: null,
55
56 // the hash table for cangjie key=> word
57 keyCangjieHash: null,
58
59 // the hash table for cangjie word=>key for learning
60 wordCangjieHash: null,
61
62 // the hash table for user frequency
63 userCodeHash: null,
64
65 // use code hash table event
66 userTableChanged: false,
67
68 // full/half letter converter
69 letterConverter: null,
70
71 // pinyin Schema
72 cangjieSchema: null,
73
74 // encoding mode
75 encodingMode: ENCODING_ZH,
76
77 // engine enabled
78 engineDisabled: false,
79
80 // can auto insertion
81 autoInsertion: false,
82
83 // number of selection word/phrase that will be sent back to IME panel for display
84 numSelection: 9,
85
86 // the entrance function to load all related tables
87 loadTable: function()
88 {
89 letterConverter = new FullLetterConverter();
90
91 // setTimeout to not block firefox start
92 var self = this;
93 setTimeout(function(){return self.loadCangjieTable();}, 500);
94
95 // init encoding table
96 FireinputEncoding.init();
97 },
98
99 getCodeLine: function(str)
100 {
101 var strArray = str.split(':');
102 if (strArray.length < 2)
103 return;
104
105 // initKey:key=>word
106 this.keyCangjieHash.setItem(strArray[0],strArray[1]);
107 },
108
109 loadCangjieTable: function()
110 {
111 var ios = FireinputXPC.getIOService();
112 var fileHandler = ios.getProtocolHandler("file")
113 .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
114
115 var path = this.getDataPath();
116 var datafile = "";
117 if (this.cangjieSchema == CANGJIE_5)
118 {
119 datafile = fileHandler.getFileFromURLSpec(path + this.getCangjie5File());
120 }
121
122 if (!datafile.exists())
123 {
124 this.engineDisabled = true;
125 return;
126 }
127
128 this.keyCangjieHash = new FireinputHash();
129
130 var options =
131 {
132 caller: this,
133 oncomplete: this.loadUserTable,
134 onavailable: this.getCodeLine
135 };
136
137 FireinputStream.loadDataAsync(datafile, options);
138 },
139
140 updateUserCodeValue: function(key, word, freq)
141 {
142 if(this.keyCangjieHash.hasItem(key))
143 {
144
145 var phrase = this.keyCangjieHash.getItem(key);
146 var regex = new RegExp(word + "\\d+", "g");
147 var oldWordFreq = phrase.match(regex);
148 if(oldWordFreq)
149 {
150 phrase = phrase.replace(key + "=>" + oldWordFreq, "");
151 }
152
153 phrase = key+"=>"+word + freq + "," + phrase;
154 this.keyCangjieHash.setItem(key, phrase);
155
156 return;
157 }
158
159 //the initKey is not in hash. Add it in
160 this.keyCangjieHash.setItem(key, key+"=>"+word+freq);
161 },
162
163 getUserCodeLine: function(str)
164 {
165 var strArray = str.split(':');
166
167 if (strArray.length < 4) return;
168
169 // user data format: word: freq key initKey
170 // new user data format: schema: word: freq initKey
171 if(isNaN(parseInt(strArray[0])))
172 {
173 // hash as word:key
174 this.userCodeHash.setItem(strArray[0]+":"+strArray[2], {freq: strArray[1], initKey: strArray[3], schema: this.cangjieSchema});
175 this.updateUserCodeValue(strArray[2], strArray[0], strArray[1]);
176 }
177 else
178 {
179 this.userCodeHash.setItem(strArray[1]+":"+strArray[3], {freq: strArray[2], initKey: strArray[4], schema: strArray[0]});
180 if(strArray[0] == this.cangjieSchema)
181 this.updateUserCodeValue(strArray[3], strArray[1], strArray[2]);
182 }
183 },
184
185 loadUserTable: function()
186 {
187 var ios = FireinputXPC.getIOService();
188 var fileHandler = ios.getProtocolHandler("file")
189 .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
190 var path = FireinputUtils.getAppRootPath();
191 var datafile = fileHandler.getFileFromURLSpec(path + this.getUserDataFile());
192
193 if (!datafile.exists()) return;
194
195 this.userCodeHash = new FireinputHash();
196
197 var options =
198 {
199 caller: this,
200 onavailable: this.getUserCodeLine
201 };
202
203 FireinputStream.loadDataAsync(datafile, options);
204 },
205
206 isEnabled: function()
207 {
208 if (this.engineDisabled) return false;
209
210 var ios = FireinputXPC.getIOService();
211 var fileHandler = ios.getProtocolHandler("file")
212 .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
213 var path = this.getDataPath();
214 var datafile = fileHandler.getFileFromURLSpec(path + this.getCangjie5File());
215
216 if (datafile.exists())
217 return true;
218 else
219 return false;
220 },
221
222 isSchemaEnabled: function()
223 {
224 if (this.engineDisabled) return false;
225
226 var ios = FireinputXPC.getIOService();
227 var fileHandler = ios.getProtocolHandler("file")
228 .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
229 var path = this.getDataPath();
230
231 if (this.cangjieSchema == CANGJIE_5)
232 {
233 datafile = fileHandler.getFileFromURLSpec(path + this.getCangjie5File());
234 }
235
236 if (datafile.exists())
237 return true;
238 else
239 return false;
240 },
241
242 canComposeNew: function()
243 {
244 return false;
245 },
246
247 setNumWordSelection: function(num)
248 {
249 this.numSelection = num > 9 ? 9 : (num < 1 ? 1: num);
250 },
251
252 getIMEType: function()
253 {
254 return this.cangjieSchema;
255 },
256
257 setSchema: function(schema)
258 {
259 //FireinputLog.debug(this, "Set schema: " + schema);
260 this.cangjieSchema = schema;
261 },
262
263 getAllowedInputKey: function()
264 {
265 return "abcdefghijklmnopqrstuvwxyz";
266 },
267
268 setEncoding: function(encoding)
269 {
270 this.encodingMode = encoding;
271 },
272
273 convertLetter: function(code)
274 {
275 // Full: number + alpha character
276 // Punct: any printable character which is not a space or an alphanumeric character
277 if((!this.isHalfLetterMode() &&
278 ((code > 47 && code < 58) ||
279 (code > 64 && code < 91))) ||
280 (!this.isHalfPunctMode() &&
281 !((code > 47 && code < 58) ||
282 (code > 64 && code < 91))))
283 return letterConverter.toFullLetter(String.fromCharCode(code));
284
285 return String.fromCharCode(code);
286 },
287
288 find: function(inputChar)
289 {
290 var s = inputChar;
291 var retArray = null;
292
293 this.autoInsertion = false;
294 // here we will do searching on inputChar by length -1 every time if retArray is null
295 while(s.length > 0 && (retArray=this.searchAll(s)) == null)
296 {
297 s = s.substr(0, s.length - 1);
298 // remove last single quot if it presents.
299 if (s.substr(s.length - 1, 1) == "'")
300 {
301 s = s.substr(0, s.length - 1);
302 }
303 }
304
305 this.validInputKey = s;
306 return {charArray:retArray, validInputKey: this.validInputKey};
307 },
308
309 searchAll: function(inputChar)
310 {
311 this.charArray = null;
312 this.charIndex = 0;
313
314 this.charArray = this.codeLookup(inputChar);
315 if (this.charArray != null)
316 return this.charArray.slice(0, this.numSelection);
317
318 return null;
319 },
320
321 codeLookup: function(keys)
322 {
323 var charArray = null;
324
325 if (keys == null || keys.length <= 0)
326 return null;
327
328 // a valid charArray consist of {key:key, word: word}
329 charArray = this.getValidWord(keys);
330 if (! charArray)
331 return null;
332
333 return charArray;
334 },
335
336 next: function (endFlag)
337 {
338 if (! this.charArray)
339 return null;
340
341 // FireinputLog.debug(this,"this.charIndex: " + this.charIndex);
342 // if the next this.numSelection are already displayed, return null
343 if ((this.charIndex+this.numSelection) >= this.charArray.length)
344 return null;
345
346 var i = this.charIndex;
347 if (! endFlag)
348 {
349 this.charIndex += this.numSelection;
350 }
351 else
352 {
353 i = this.charArray.length-this.numSelection;
354 i -= this.numSelection;
355 this.charIndex = (i > 0) ? i : 0;
356 }
357 // FireinputLog.debug(this,"this.charIndex: " + this.charIndex);
358 return {charArray:this.charArray.slice(this.charIndex, this.charIndex+this.numSelection), validInputKey: this.validInputKey};
359 },
360
361 prev: function (homeFlag)
362 {
363 if (! this.charArray)
364 return null;
365 // FireinputLog.debug(this,"this.charIndex: " + this.charIndex);
366 // if the previous this.numSelection are already displayed, return null
367 if ((this.charIndex - this.numSelection) < 0)
368 return null;
369
370 if (! homeFlag)
371 this.charIndex -= this.numSelection;
372 else
373 this.charIndex = 0;
374
375 // FireinputLog.debug(this,"this.charIndex: " + this.charIndex);
376 return {charArray: this.charArray.slice(this.charIndex, this.charIndex+this.numSelection), validInputKey: this.validInputKey};
377 },
378
379 isBeginning: function()
380 {
381 return this.charIndex == 0;
382 },
383
384 isEnd: function()
385 {
386 return (this.charIndex + this.numSelection) >= this.charArray.length;
387 },
388
389 canAutoInsert: function()
390 {
391 return this.autoInsertion;
392 },
393
394 getValidWord: function(key)
395 {
396 var wordArray = null;
397 var userArray = new Array();
398 var wordList = new Array();
399
400 if (! key) return null;
401
402 var keyInitial = key;
403
404 if (! this.keyCangjieHash.hasItem(keyInitial))
405 return null;
406
407 // only enable autoinsertion for 4 keys
408 if (key.length >= 5)
409 this.autoInsertion = true;
410
411 var cangjieWordList = this.keyCangjieHash.getItem(keyInitial);
412
413 var cangjieWordArray = cangjieWordList.split(",");
414
415 wordArray = new Array();
416
417 for (var i = 0; i < cangjieWordArray.length; i ++)
418 {
419 var cangjieWord = cangjieWordArray[i].split("=>");
420
421 if (cangjieWord[0].search(new RegExp("^" + key)))
422 continue;
423
424 var word = "";
425 try
426 {
427 word = cangjieWord[1].match(/[\D\.]+/g)[0];
428 }
429 catch(e) { }
430
431 if (word.length <= 0)
432 continue;
433
434
435 var encodedWord = FireinputEncoding.getEncodedString(word, this.encodingMode);
436 if (typeof(wordList[encodedWord]) != 'undefined')
437 continue;
438
439 wordList[encodedWord] = 1;
440
441 if (this.userCodeHash && this.userCodeHash.hasItem(word+":"+cangjieWord[0]))
442 {
443 var ufreq = this.userCodeHash.getItem(word+":"+cangjieWord[0]);
444 /* use this way other than push to have better performance
445 * http://aymanh.com/9-javascript-tips-you-may-not-know
446 */
447 userArray[userArray.length] ={key: cangjieWord[0], word: word + ufreq.freq, encodedWord: encodedWord + ufreq.freq};
448 }
449 else
450 {
451 var freq = cangjieWord[1].match(/[\d\.]+/g);
452 if(freq) freq = freq[0];
453 else freq = 0;
454
455 wordArray[wordArray.length] = {key: cangjieWord[0], word: word+freq, encodedWord: encodedWord + freq};
456 }
457 }
458
459 // free it
460 wordList = null;
461
462 // FireinputLog.debug(this,"wordArray: " + this.getKeyWord(wordArray));
463 // FireinputLog.debug(this,"userArray: " + this.getKeyWord(userArray));
464 if (userArray.length <= 0)
465 return wordArray;
466
467 // sort the first this.numSelection items
468 if (userArray.length < (this.numSelection+1))
469 {
470 arrayInsert(userArray, userArray.length, wordArray.slice(0, this.numSelection));
471 userArray.sort(this.sortCodeArray);
472 arrayInsert(userArray, userArray.length, wordArray.slice(this.numSelection+1, wordArray.length));
473 }
474 else
475 {
476 userArray.sort(this.sortCodeArray);
477 arrayInsert(userArray, userArray.length, wordArray.slice(0, wordArray.length));
478 }
479
480 return userArray;
481 },
482
483 flushUserTable: function()
484 {
485 if (this.userCodeHash && this.userTableChanged)
486 {
487 FireinputSaver.save(this.userCodeHash);
488 }
489 },
490
491 updateFrequency: function(word, key, initKey)
492 {
493 var freq = word.match(/[\d\.]+/g)[0];
494 var chars = word.match(/[\D\.]+/g)[0];
495
496 if (! this.userCodeHash)
497 this.userCodeHash = new FireinputHash();
498
499 var newfreq = 0;
500 if (this.userCodeHash.hasItem(chars + ":" + key))
501 {
502 var charopt = this.userCodeHash.getItem(chars + ":" + key);
503 var freq1 = charopt.freq;
504 newfreq = parseInt("0xFFFFFFFF", 16) - freq1;
505
506 if (typeof(initKey) == "undefined")
507 initKey = charopt.initKey;
508 }
509 else
510 {
511 newfreq = parseInt("0xFFFFFFFF", 16) - freq;
512
513 if (typeof(initKey) == "undefined")
514 {
515 initKey = key.substring(0, 1);
516 if (key.length >= 3)
517 initKey = key.substring(0, 3);
518 }
519 }
520
521 if (newfreq)
522 {
523 newfreq /= Math.pow(2, 16);
524 if (newfreq < 1) newfreq = 1;
525 }
526
527 freq = Math.round(newfreq) + parseInt(freq);
528
529 this.userCodeHash.setItem(chars+":"+key, {freq: freq, initKey: initKey, schema: this.cangjieSchema});
530
531 this.userTableChanged = true;
532
533 // FireinputLog.debug(this,"word: " + word);
534 // FireinputLog.debug(this,"chars: " + chars + ", freq: " + freq);
535 // FireinputLog.debug(this,"chars: " + chars + ", key: " + key + ", initKey: " + initKey);
536 return freq;
537 },
538
539 storeUserPhrases: function(userPhrases)
540 {
541 return;
542 },
543
544 storeOneUserPhrase: function(userPhrase)
545 {
546 return;
547 }
548 });
syntax highlighted by Code2HTML, v. 0.9.1