PageRenderTime 80ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/python/engine/PinYin/PinYin.py

http://scim-python.googlecode.com/
Python | 1225 lines | 1151 code | 32 blank | 42 comment | 21 complexity | 087597563130f2de943ec45e8e962f2b MD5 | raw file
  1. # -*- coding: utf-8 -*-
  2. # vim: set noet ts=4:
  3. #
  4. # scim-python
  5. #
  6. # Copyright (c) 2007-2008 Huang Peng <shawn.p.huang@gmail.com>
  7. #
  8. #
  9. # This library is free software; you can redistribute it and/or
  10. # modify it under the terms of the GNU Lesser General Public
  11. # License as published by the Free Software Foundation; either
  12. # version 2 of the License, or (at your option) any later version.
  13. #
  14. # This library is distributed in the hope that it will be useful,
  15. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. # GNU Lesser General Public License for more details.
  18. #
  19. # You should have received a copy of the GNU Lesser General Public
  20. # License along with this program; if not, write to the
  21. # Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  22. # Boston, MA 02111-1307 USA
  23. #
  24. # $Id: $
  25. #
  26. import scim
  27. from scim import IMEngine
  28. from scim import IMEngineFactory
  29. from scim import KeyCode
  30. from scim import KeyMask
  31. from scim import Property
  32. from scim import Attribute
  33. import PYParser
  34. import PYSQLiteDB
  35. import SpecialTable
  36. import SpecialPhrase
  37. import PYUtil
  38. import PYDict
  39. import scim.ascii as ascii
  40. from gettext import dgettext
  41. _ = lambda a : dgettext ("scim-python", a)
  42. N_ = lambda a : a
  43. __MAX_LEN__ = 64 # Max length of preedit pinyin
  44. # Define colours
  45. RGB = lambda r, g, b : (((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff))
  46. try:
  47. import enchant
  48. __EN_DICT__ = enchant.Dict ("en_US")
  49. except:
  50. class MY_DICT:
  51. def __init__ (self):
  52. pass
  53. def suggest (self, word):
  54. return []
  55. def check (self, word):
  56. return True
  57. __EN_DICT__ = MY_DICT ()
  58. class PinYinEngine (IMEngine):
  59. # create pinyin database
  60. _pydb = PYSQLiteDB.PYSQLiteDB (user_db = "user.db")
  61. # create special table
  62. _special_phrase = SpecialPhrase.SpecialPhrase ()
  63. _special_table = SpecialTable.SpecialTable ()
  64. # shuang pin
  65. _shuangpin = False
  66. _shuangpin_schema = "MSPY"
  67. # gbk
  68. _gbk = True
  69. # fuzzy pinyin & auto correct
  70. _fuzzy_pinyin = [False] * 8
  71. _auto_correct = True
  72. _spell_check = True
  73. # colors
  74. _phrase_color = RGB (0, 0, 0)
  75. _user_phrase_color = RGB (0, 0, 0xef)
  76. _new_phrase_color = RGB (0xef, 0, 0)
  77. _special_phrase_color = RGB (0, 0xbf, 0)
  78. _english_phrase_color = RGB (0, 0xbf, 0)
  79. _error_eng_phrase_color = RGB (0xef, 0, 0)
  80. # lookup table page size
  81. _page_size = 5
  82. # press [u] or [v] to temp English mode
  83. _uv_to_temp = True
  84. # press [shift] to select candidates
  85. _shift_select_candidates = True
  86. # press [-] [=] to page down & up candidates
  87. _equal_page_down_up = True
  88. # press [,] [.] to page down & up candidates
  89. _comma_page_down_up = True
  90. # auto commit
  91. _auto_commit = False
  92. def __init__ (self, factory, config, encoding, id):
  93. IMEngine.__init__ (self, factory, config, encoding, id)
  94. self._config = config
  95. self._lookup_table = scim.LookupTable (PinYinEngine._page_size)
  96. self._lookup_table.fix_page_size (True)
  97. self._py_parser = PYParser.PinYinParser ()
  98. self._user_input = UserInput (self._py_parser)
  99. # 0 = english input mode
  100. # 1 = chinese input mode
  101. self._mode = 1
  102. self._full_width_letter = [False, False]
  103. self._full_width_punct = [False, True]
  104. self._full_width_punct[1] = config.read ("/IMEngine/Python/PinYin/FullWidthPunct", True)
  105. self._committed_phrases = PhraseList ()
  106. self._preedit_phrases = PhraseList ()
  107. self.reset ()
  108. self._status_property = Property ("status", "")
  109. self._letter_property = Property ("full_letter", "")
  110. self._punct_property = Property ("full_punct", "")
  111. # self._shuangpin_property = Property ("shuangpin", "")
  112. # self._gbk_property = Property ("gbk", "")
  113. self._setup_property = Property ("setup", "", "/usr/share/scim/icons/setup.png", _("Setup"))
  114. def _init_properties (self):
  115. properties = (
  116. self._status_property,
  117. self._letter_property,
  118. self._punct_property,
  119. # self._shuangpin_property,
  120. # self._gbk_property,
  121. self._setup_property
  122. )
  123. self.register_properties (properties)
  124. self._refresh_properties ()
  125. def _refresh_properties (self):
  126. if self._mode == 1: # refresh mode
  127. self._status_property.label = _("CN")
  128. self._status_property.tip = _("Switch to English mode")
  129. else:
  130. self._status_property.label = _("EN")
  131. self._status_property.tip = _("Switch to Chinese mode")
  132. if self._full_width_letter[self._mode]:
  133. self._letter_property.icon = "/usr/share/scim/icons/full-letter.png"
  134. self._letter_property.tip = _("Switch to half letter mode")
  135. else:
  136. self._letter_property.icon = "/usr/share/scim/icons/half-letter.png"
  137. self._letter_property.tip = _("Switch to full letter mode")
  138. if self._full_width_punct[self._mode]:
  139. self._punct_property.icon = "/usr/share/scim/icons/full-punct.png"
  140. self._punct_property.tip = _("Switch to half punctuation mode")
  141. else:
  142. self._punct_property.icon = "/usr/share/scim/icons/half-punct.png"
  143. self._punct_property.tip = _("Switch to full punctuation mode")
  144. # if PinYinEngine._shuangpin:
  145. # self._shuangpin_property.label = _("SHUANG")
  146. # self._shuangpin_property.tip = _("Switch to QUAN PIN")
  147. # else:
  148. # self._shuangpin_property.label = _("QUAN")
  149. # self._shuangpin_property.tip = _("Switch to SHUANG PIN")
  150. # if PinYinEngine._gbk:
  151. # self._gbk_property.label = _("GBK")
  152. # self._gbk_property.tip = _("Switch to GB2312 codeset")
  153. # else:
  154. # self._gbk_property.label = _("GB")
  155. # self._gbk_property.tip = _("Switch to GBK codeset")
  156. properties = (
  157. self._status_property,
  158. self._letter_property,
  159. self._punct_property,
  160. # self._shuangpin_property,
  161. # self._gbk_property,
  162. )
  163. for prop in properties:
  164. self.update_property (prop)
  165. def _change_mode (self):
  166. self._mode = (self._mode + 1) % 2
  167. self._refresh_properties ()
  168. def _is_input_english (self):
  169. return self._mode == 0
  170. def _update_candidates (self):
  171. if self._temp_english_mode:
  172. if self._spell_check == False:
  173. return
  174. self._english_candidates = []
  175. string = "".join (self._user_input.get_chars ())
  176. if string[0] in u"uv":
  177. string = string [1:]
  178. words = string.split ()
  179. if not words:
  180. return
  181. word = words[-1]
  182. if len (words) == 1 and word[0:1] in u"uv":
  183. word = word[1:]
  184. if not word.isalpha ():
  185. return
  186. if __EN_DICT__.check (word):
  187. return
  188. self._current_word = word
  189. candidates = __EN_DICT__.suggest (word)
  190. is_same = lambda x : x[0].isupper () == word[0].isupper ()
  191. self._english_candidates = filter (is_same, candidates)[:10]
  192. return
  193. if self._i_mode:
  194. chars = self._user_input.get_chars()[1:]
  195. self._candidates = self._special_phrase.lookup (u"".join (chars))
  196. self._candidates += self._special_table.lookup (u"".join (chars))
  197. return
  198. self._preedit_phrases.clear ()
  199. if len (self._user_input.get_pinyin_list ()) == 0:
  200. self._candidates = []
  201. self._special_candidates = []
  202. return
  203. if len (self._committed_phrases) == 0:
  204. self._special_candidates = self._special_phrase.lookup (u"".join (self._user_input.get_chars ()))
  205. else:
  206. self._special_candidates = []
  207. pinyin_list = self._user_input.get_pinyin_list ()
  208. pinyin_list = pinyin_list [self._committed_phrases.length_of_chars ():]
  209. if pinyin_list:
  210. self._candidates = self._get_candidates (pinyin_list)
  211. self._preedit_phrases.append (self._candidates[0])
  212. count = self._preedit_phrases.length_of_chars ()
  213. while count < len (pinyin_list):
  214. candidate = self._get_a_candidate (pinyin_list[count:])
  215. self._preedit_phrases.append (candidate)
  216. count += candidate[PYSQLiteDB.YLEN]
  217. def _update_ui (self):
  218. if self._i_mode:
  219. preedit_string = u"".join (self._user_input.get_chars ())
  220. self.update_preedit_string (preedit_string)
  221. self.update_preedit_caret (len (preedit_string))
  222. self.show_preedit_string ()
  223. self.update_aux_string (u"")
  224. self.hide_aux_string ()
  225. self._lookup_table.clear ()
  226. self._lookup_table.show_cursor (True)
  227. if not self._candidates:
  228. self.hide_lookup_table ()
  229. else:
  230. for c in self._candidates:
  231. attrs = []
  232. self._lookup_table.append_candidate (c, attrs)
  233. self.update_lookup_table (self._lookup_table)
  234. self.show_lookup_table ()
  235. return
  236. if self._temp_english_mode:
  237. preedit_string = u"".join (self._user_input.get_chars ())
  238. if preedit_string [0:1] in (u"v", u"u"):
  239. preedit_string = " " + preedit_string[1:]
  240. else:
  241. preedit_string = " " + preedit_string
  242. words = preedit_string.split()
  243. if words:
  244. aux_string = words[-1]
  245. else:
  246. aux_string = u""
  247. if preedit_string and self._spell_check:
  248. self.update_preedit_string (preedit_string)
  249. self.update_preedit_caret (len (preedit_string))
  250. self.show_preedit_string ()
  251. if not __EN_DICT__.check (aux_string):
  252. attrs = [Attribute (0, len (aux_string), scim.ATTR_FOREGROUND, PinYinEngine._error_eng_phrase_color)]
  253. else:
  254. attrs = None
  255. self.update_aux_string (aux_string, attrs)
  256. self.show_aux_string ()
  257. else:
  258. self.update_preedit_string (u"")
  259. self.update_preedit_caret (0)
  260. self.hide_preedit_string ()
  261. self.update_aux_string (u"")
  262. self.hide_aux_string ()
  263. self._lookup_table.clear ()
  264. self._lookup_table.show_cursor (False)
  265. if not self._english_candidates:
  266. self.hide_lookup_table ()
  267. else:
  268. for c in self._english_candidates:
  269. attrs = [Attribute (0, len (c), scim.ATTR_FOREGROUND, PinYinEngine._english_phrase_color)]
  270. self._lookup_table.append_candidate (c, attrs)
  271. self.update_lookup_table (self._lookup_table)
  272. self.show_lookup_table ()
  273. return
  274. if len (self._candidates) == 0:
  275. self.hide_lookup_table ()
  276. else:
  277. self._lookup_table.clear ()
  278. candidates = self._candidates[:]
  279. if len (self._preedit_phrases) != 1: # we need display the automatically created new phrase
  280. preedit_string = self._preedit_phrases.get_string ()
  281. attrs = [Attribute (0, len (preedit_string), scim.ATTR_FOREGROUND, PinYinEngine._new_phrase_color)]
  282. self._lookup_table.append_candidate (preedit_string, attrs)
  283. else:
  284. c = candidates[0]
  285. if c[PYSQLiteDB.FREQ] == None: # This phrase was created by user.
  286. attrs = [Attribute (0, c[PYSQLiteDB.YLEN], scim.ATTR_FOREGROUND, PinYinEngine._user_phrase_color)]
  287. else:
  288. attrs = [Attribute (0, c[PYSQLiteDB.YLEN], scim.ATTR_FOREGROUND, PinYinEngine._phrase_color)]
  289. self._lookup_table.append_candidate (c[PYSQLiteDB.PHRASE], attrs)
  290. del candidates[0]
  291. for c in self._special_candidates:
  292. attrs = [Attribute (0, len (c), scim.ATTR_FOREGROUND, PinYinEngine._special_phrase_color)]
  293. self._lookup_table.append_candidate (c, attrs)
  294. for c in candidates:
  295. if c[PYSQLiteDB.FREQ] == None: # This phrase was created by user.
  296. attrs = [Attribute (0, c[PYSQLiteDB.YLEN], scim.ATTR_FOREGROUND, PinYinEngine._user_phrase_color)]
  297. else:
  298. attrs = [Attribute (0, c[PYSQLiteDB.YLEN], scim.ATTR_FOREGROUND, PinYinEngine._phrase_color)]
  299. self._lookup_table.append_candidate (c[PYSQLiteDB.PHRASE], attrs)
  300. self._lookup_table.show_cursor (True)
  301. self._lookup_table.set_cursor_pos (0)
  302. self.update_lookup_table (self._lookup_table)
  303. self.show_lookup_table ()
  304. committed_string = self._committed_phrases.get_string ()
  305. invalid_pinyin = self._user_input.get_invalid_string ()
  306. preedit_string = " ".join ([committed_string, self._preedit_phrases.get_string (), invalid_pinyin])
  307. preedit_string = preedit_string.strip ()
  308. if preedit_string:
  309. self.update_preedit_string (preedit_string)
  310. self.update_preedit_caret (len (preedit_string))
  311. self.show_preedit_string ()
  312. else:
  313. self.update_preedit_string (u"")
  314. self.update_preedit_caret (0)
  315. self.hide_preedit_string ()
  316. if committed_string or len (self._user_input) != 0:
  317. pinyin_list = self._user_input.get_pinyin_list ()
  318. pinyin_list = pinyin_list [len (committed_string):]
  319. pinyin_list = map (str, pinyin_list)
  320. if committed_string:
  321. aux_string = u"".join ([committed_string, u" ", u"'".join (pinyin_list)])
  322. else:
  323. aux_string = u"'".join (pinyin_list)
  324. if aux_string:
  325. self.update_aux_string (aux_string)
  326. self.show_aux_string ()
  327. else:
  328. self.update_aux_string (u"")
  329. self.hide_aux_string ()
  330. else:
  331. self.update_aux_string (u"")
  332. self.hide_aux_string ()
  333. def _update (self):
  334. self._update_candidates ()
  335. self._update_ui ()
  336. def _is_gb2312 (self, record):
  337. try:
  338. record[PYSQLiteDB.PHRASE].encode ("gb2312")
  339. except:
  340. return False
  341. return True
  342. def _get_candidates (self, pinyin_list):
  343. candidates = []
  344. for i in range (len (pinyin_list), 0, -1):
  345. candidates += self._pydb.select_words_by_pinyin_list (pinyin_list[:i], PinYinEngine._fuzzy_pinyin)
  346. if not PinYinEngine._gbk:
  347. candidates = filter (self._is_gb2312, candidates)
  348. return candidates
  349. def _get_a_candidate (self, pinyin_list):
  350. for i in range (len (pinyin_list), 0, -1):
  351. candidates = self._pydb.select_words_by_pinyin_list (pinyin_list[:i], PinYinEngine._fuzzy_pinyin)
  352. if not PinYinEngine._gbk:
  353. candidates = filter (self._is_gb2312, candidates)
  354. if candidates:
  355. return candidates[0]
  356. return None
  357. def _append_char (self, char):
  358. self._user_input.append (char)
  359. self._committed_phrases.clear ()
  360. self._update ()
  361. return True
  362. def _pop_char (self):
  363. if len (self._user_input) == 0:
  364. return False
  365. if len (self._committed_phrases) != 0:
  366. self._committed_phrases.pop ()
  367. else:
  368. self._user_input.pop ()
  369. self._update ()
  370. return True
  371. def _match_hotkey (self, key, code, mask):
  372. if key.code == code and key.mask == mask:
  373. if self._prev_key and key.code == self._prev_key.code and key.mask & KeyMask.ReleaseMask:
  374. return True
  375. if not key.mask & KeyMask.ReleaseMask:
  376. return True
  377. return False
  378. def _internal_process_key_event (self, key):
  379. # When CapsLock is lock, we ignore all key events
  380. if key.mask & KeyMask.CapsLockMask:
  381. return False
  382. # Match mode switch hotkey
  383. if self._match_hotkey (key, KeyCode.KEY_Shift_L, KeyMask.ShiftMask + KeyMask.ReleaseMask) or \
  384. self._match_hotkey (key, KeyCode.KEY_Shift_R, KeyMask.ShiftMask + KeyMask.ReleaseMask):
  385. if self._candidates and not self._is_input_english () and PinYinEngine._shift_select_canidates:
  386. index = self._lookup_table.get_current_page_start ()
  387. if key.code == KeyCode.KEY_Shift_L:
  388. index += 1
  389. else:
  390. index += 2
  391. result = self._commit_candidate (index)
  392. if result:
  393. if self._committed_special_phrase:
  394. self.commit_string (self._committed_special_phrase)
  395. else:
  396. commit_phrases = self._committed_phrases.get_phrases ()
  397. commit_string = self._committed_phrases.get_string ()
  398. self.commit_string (commit_string + self._user_input.get_invalid_string ())
  399. # adjust phrase freq and create new phrase
  400. self._pydb.commit_phrases (commit_phrases)
  401. if len (commit_phrases) > 1:
  402. self._pydb.new_phrase (commit_phrases)
  403. return True
  404. else:
  405. self.trigger_property ("status")
  406. self.reset ()
  407. return True
  408. # Match full half letter mode switch hotkey
  409. if self._match_hotkey (key, KeyCode.KEY_space, KeyMask.ShiftMask):
  410. self.trigger_property ("full_letter")
  411. return True
  412. # Match full half punct mode switch hotkey
  413. if self._match_hotkey (key, KeyCode.KEY_period, KeyMask.ControlMask):
  414. self.trigger_property ("full_punct")
  415. return True
  416. # Match remove user phrase hotkeys
  417. for code in xrange (KeyCode.KEY_1, KeyCode.KEY_1 + PinYinEngine._page_size):
  418. if self._match_hotkey (key, code, KeyMask.ControlMask):
  419. index = code - KeyCode.KEY_1 + self._lookup_table.get_current_page_start ()
  420. return self._remove_candidate (index)
  421. # we ignore all hotkeys
  422. if key.mask & (KeyMask.ControlMask + KeyMask.AltMask):
  423. return False
  424. # Ignore key release event
  425. if key.mask & KeyMask.ReleaseMask:
  426. return True
  427. if self._is_input_english ():
  428. return self._english_mode_process_key_event (key)
  429. else:
  430. if self._temp_english_mode:
  431. return self._temp_english_mode_process_key_event (key)
  432. elif self._i_mode:
  433. return self._i_mode_process_key_event (key)
  434. else:
  435. return self._chinese_mode_process_key_event (key)
  436. def _convert_to_full_width (self, c):
  437. if c == u".":
  438. return u"\u3002"
  439. elif c == u"\\":
  440. return u"\u3001"
  441. elif c == u"^":
  442. return u"\u2026\u2026"
  443. elif c == u"_":
  444. return u"\u2014\u2014"
  445. elif c == u"$":
  446. return u"\uffe5"
  447. elif c == u"\"":
  448. self._double_quotation_state = not self._double_quotation_state
  449. if self._double_quotation_state:
  450. return u"\u201c"
  451. else:
  452. return u"\u201d"
  453. elif c == u"'":
  454. self._single_quotation_state = not self._single_quotation_state
  455. if self._single_quotation_state:
  456. return u"\u2018"
  457. else:
  458. return u"\u2019"
  459. elif c == u"<":
  460. if not self._is_input_english ():
  461. return u"\u300a"
  462. elif c == u">":
  463. if not self._is_input_english ():
  464. return u"\u300b"
  465. return scim.unichar_half_to_full (c)
  466. def _english_mode_process_key_event (self, key):
  467. # ignore if key code is not a normal ascii char
  468. if key.code >= 128:
  469. return False
  470. c = unichr (key.code)
  471. if ascii.ispunct (key.code): # if key code is a punctation
  472. if self._full_width_punct[self._mode]:
  473. self.commit_string (self._convert_to_full_width (c))
  474. return True
  475. else:
  476. self.commit_string (c)
  477. return True
  478. if self._full_width_letter[self._mode]: # if key code is a letter or digit
  479. self.commit_string (self._convert_to_full_width (c))
  480. return True
  481. else:
  482. self.commit_string (c)
  483. return True
  484. # should not reach there
  485. return False
  486. def _i_mode_process_key_event (self, key):
  487. if key.code in (KeyCode.KEY_Return, KeyCode.KEY_KP_Enter):
  488. commit_string = u"".join (self._user_input.get_chars ())
  489. self.commit_string (commit_string)
  490. return True
  491. elif key.code == KeyCode.KEY_BackSpace and len (self._user_input) != 0:
  492. self._user_input.pop ()
  493. if len (self._user_input) == 0:
  494. self._i_mode = False
  495. self._update ()
  496. return True
  497. elif key.code == KeyCode.KEY_Escape:
  498. self._user_input.clear ()
  499. self._i_mode = False
  500. self._update ()
  501. return True
  502. elif key.code >= KeyCode.KEY_1 and key.code <= KeyCode.KEY_9:
  503. if not self._candidates:
  504. return True
  505. index = key.code - KeyCode.KEY_1
  506. if index >= PinYinEngine._page_size:
  507. return True
  508. index += self._lookup_table.get_current_page_start ()
  509. if index >= len (self._candidates):
  510. return True
  511. self.commit_string (self._candidates[index])
  512. return True
  513. elif key.code in (KeyCode.KEY_KP_Space, KeyCode.KEY_space):
  514. if not self._candidates:
  515. return True
  516. index = self._lookup_table.get_cursor_pos ()
  517. if index >= len (self._candidates):
  518. return True
  519. self.commit_string (self._candidates[index])
  520. return True
  521. elif key.code == KeyCode.KEY_Down:
  522. self.lookup_table_cursor_down ()
  523. return True
  524. elif key.code == KeyCode.KEY_Up:
  525. self.lookup_table_cursor_up ()
  526. return True
  527. elif key.code == KeyCode.KEY_Page_Down and self._candidates: # press PageDown
  528. self.lookup_table_page_down ()
  529. return True
  530. elif key.code == KeyCode.KEY_Page_Up and self._candidates: # press PageUp
  531. self.lookup_table_page_up ()
  532. return True
  533. elif key.code == KeyCode.KEY_period and self._candidates and PinYinEngine._comma_page_down_up: # press .
  534. self.lookup_table_page_down ()
  535. return True
  536. elif key.code == KeyCode.KEY_comma and self._candidates and PinYinEngine._comma_page_down_up: # press ,
  537. self.lookup_table_page_up ()
  538. return True
  539. elif key.code == KeyCode.KEY_equal and self._candidates and PinYinEngine._equal_page_down_up: # press =
  540. self.lookup_table_page_down ()
  541. return True
  542. elif key.code == KeyCode.KEY_minus and self._candidates and PinYinEngine._equal_page_down_up: # press -
  543. self.lookup_table_page_up ()
  544. return True
  545. if key.code >= 128:
  546. return True
  547. self._user_input.append (unichr (key.code))
  548. self._update ()
  549. return True
  550. def _temp_english_mode_process_key_event (self, key):
  551. if key.code in (KeyCode.KEY_Return, KeyCode.KEY_KP_Enter):
  552. commit_string = u"".join (self._user_input.get_chars ())
  553. if commit_string[0] in (u"v", u"u"):
  554. commit_string = commit_string[1:]
  555. self.commit_string (commit_string)
  556. return True
  557. elif key.code == KeyCode.KEY_BackSpace and len (self._user_input) != 0:
  558. self._user_input.pop ()
  559. if len (self._user_input) == 0:
  560. self._temp_english_mode = False
  561. self._update ()
  562. return True
  563. elif key.code == KeyCode.KEY_Escape:
  564. self._user_input.clear ()
  565. self._temp_english_mode = False
  566. self._update ()
  567. return True
  568. elif key.code >= KeyCode.KEY_1 and key.code <= KeyCode.KEY_9 and self._english_candidates:
  569. index = key.code - KeyCode.KEY_1
  570. if index >= PinYinEngine._page_size:
  571. return False
  572. index += self._lookup_table.get_current_page_start ()
  573. if index >=0 and index < len (self._english_candidates):
  574. for i in xrange (0, len (self._current_word)):
  575. self._user_input.pop ()
  576. for c in self._english_candidates[index]:
  577. self._user_input.append (c)
  578. self._update ()
  579. return True
  580. return False
  581. elif key.code in (KeyCode.KEY_Page_Down, ) and self._english_candidates:
  582. self.lookup_table_page_down ()
  583. return True
  584. elif key.code in (KeyCode.KEY_Page_Up, ) and self._english_candidates:
  585. self.lookup_table_page_up ()
  586. return True
  587. if key.code >= 128:
  588. return True
  589. self._user_input.append (unichr (key.code))
  590. self._update ()
  591. return True
  592. def _chinese_mode_process_key_event (self, key):
  593. # define a condition half to full width translate functions
  594. cond_letter_translate = lambda (c): \
  595. self._convert_to_full_width (c) if self._full_width_letter [self._mode] else c
  596. cond_punct_translate = lambda (c): \
  597. self._convert_to_full_width (c) if self._full_width_punct [self._mode] else c
  598. if key.code in (KeyCode.KEY_Return, KeyCode.KEY_KP_Enter):
  599. if len (self._user_input) == 0: # forward Return if inputed chars is empty
  600. return False
  601. chars = map (cond_letter_translate, self._user_input.get_chars ())
  602. commit_string = u"".join (chars)
  603. self.commit_string (commit_string)
  604. return True
  605. elif key.code == KeyCode.KEY_Escape:
  606. if len (self._user_input) != 0:
  607. self.reset ()
  608. return True
  609. return False
  610. elif key.code == KeyCode.KEY_Down:
  611. return self.lookup_table_cursor_down ()
  612. elif key.code == KeyCode.KEY_Up:
  613. return self.lookup_table_cursor_up ()
  614. elif key.code == KeyCode.KEY_BackSpace:
  615. return self._pop_char ()
  616. elif key.code >= KeyCode.KEY_1 and key.code <= KeyCode.KEY_9:
  617. if not self._candidates:
  618. self.commit_string (cond_letter_translate (unichr (key.code)))
  619. else:
  620. index = key.code - KeyCode.KEY_1
  621. if index >= PinYinEngine._page_size:
  622. return True
  623. index += self._lookup_table.get_current_page_start ()
  624. result = self._commit_candidate (index)
  625. if result:
  626. if self._committed_special_phrase:
  627. self.commit_string (self._committed_special_phrase)
  628. else:
  629. commit_phrases = self._committed_phrases.get_phrases ()
  630. commit_string = self._committed_phrases.get_string ()
  631. self.commit_string (commit_string + self._user_input.get_invalid_string ())
  632. # adjust phrase freq and create new phrase
  633. self._pydb.commit_phrases (commit_phrases)
  634. if len (commit_phrases) > 1:
  635. self._pydb.new_phrase (commit_phrases)
  636. return True
  637. elif key.code in (KeyCode.KEY_KP_Space, KeyCode.KEY_space):
  638. if not self._candidates:
  639. self.commit_string (cond_letter_translate (u" "))
  640. else:
  641. index = self._lookup_table.get_cursor_pos ()
  642. result = self._commit_candidate (index)
  643. if result:
  644. if self._committed_special_phrase:
  645. self.commit_string (self._committed_special_phrase)
  646. else:
  647. commit_phrases = self._committed_phrases.get_phrases ()
  648. commit_string = self._committed_phrases.get_string ()
  649. self.commit_string (commit_string + self._user_input.get_invalid_string ())
  650. # adjust phrase freq and create new phrase
  651. self._pydb.commit_phrases (commit_phrases)
  652. if len (commit_phrases) > 1:
  653. self._pydb.new_phrase (commit_phrases)
  654. return True
  655. elif key.code == KeyCode.KEY_Page_Down and self._candidates: # press PageDown
  656. self.lookup_table_page_down ()
  657. return True
  658. elif key.code == KeyCode.KEY_equal and self._candidates and PinYinEngine._equal_page_down_up: # press equal
  659. self.lookup_table_page_down ()
  660. return True
  661. elif key.code == KeyCode.KEY_period and self._candidates and PinYinEngine._comma_page_down_up: # press period
  662. self.lookup_table_page_down ()
  663. return True
  664. elif key.code == KeyCode.KEY_Page_Up and self._candidates: # press PageUp
  665. self.lookup_table_page_up ()
  666. return True
  667. elif key.code == KeyCode.KEY_minus and self._candidates and PinYinEngine._equal_page_down_up: # press minus
  668. self.lookup_table_page_up ()
  669. return True
  670. elif key.code == KeyCode.KEY_comma and self._candidates and PinYinEngine._comma_page_down_up: #press comma
  671. self.lookup_table_page_up ()
  672. return True
  673. elif key.code in (KeyCode.KEY_bracketleft, KeyCode.KEY_bracketright) and self._candidates:
  674. cursor_pos = self._lookup_table.get_cursor_pos ()
  675. candidate = self._candidates[cursor_pos]
  676. if key.code == KeyCode.KEY_bracketleft:
  677. i = 0
  678. else:
  679. i = len (candidate[PYSQLiteDB.PHRASE]) - 1
  680. char = candidate[PYSQLiteDB.PHRASE][i]
  681. if i < 4:
  682. pinyin_id = candidate[PYSQLiteDB.Y0 + i]
  683. shengmu_id = candidate[PYSQLiteDB.S0 + i]
  684. else:
  685. pinyin = candidate[PYSQLiteDB.YX].split ("'")[-1]
  686. word = PYUtil.PinYinWord (pinyin)
  687. pinyin_id = word.get_pinyin_id ()
  688. shengmu_id = word.get_sheng_mu_id ()
  689. self._pydb.commit_char (char, pinyin_id, shengmu_id)
  690. self.commit_string (char)
  691. return True
  692. elif PinYinEngine._uv_to_temp and not PinYinEngine._shuangpin \
  693. and len (self._user_input) == 0 \
  694. and key.code in (KeyCode.KEY_v, KeyCode.KEY_u):
  695. self._user_input.append (unichr (key.code))
  696. self._temp_english_mode = True
  697. self._update ()
  698. return True
  699. elif key.code >= KeyCode.KEY_A and key.code <= KeyCode.KEY_Z and len (self._user_input) == 0:
  700. self._user_input.append (unichr (key.code))
  701. self._temp_english_mode = True
  702. self._update ()
  703. return True
  704. elif not PinYinEngine._shuangpin \
  705. and len (self._user_input) == 0 \
  706. and key.code == KeyCode.KEY_i:
  707. self._user_input.append (unichr (key.code))
  708. self._i_mode = True
  709. self._update ()
  710. return True
  711. elif (key.code >= KeyCode.KEY_a and key.code <= KeyCode.KEY_z) or \
  712. (key.code == KeyCode.KEY_apostrophe and len (self._user_input) != 0) or \
  713. (key.code == KeyCode.KEY_semicolon and len (self._user_input) != 0 and PinYinEngine._shuangpin) :
  714. return self._append_char (unichr (key.code))
  715. elif key.code <= 127:
  716. if len (self._user_input) != 0:
  717. if PinYinEngine._auto_commit:
  718. self._chinese_mode_process_key_event (scim.KeyEvent (KeyCode.KEY_space, 0, key.mask))
  719. else:
  720. return True
  721. c = chr (key.code)
  722. if c == "." and self._prev_char and self._prev_char.isdigit () \
  723. and self._prev_key and chr (self._prev_key.code) == self._prev_char:
  724. self.commit_string (u".")
  725. elif ascii.ispunct (key.code):
  726. self.commit_string (cond_punct_translate (unichr (key.code)))
  727. else:
  728. self.commit_string (cond_letter_translate (unichr (key.code)))
  729. return True
  730. return False
  731. def _commit_candidate (self, i):
  732. if i == 0:
  733. for phrase in self._preedit_phrases.get_phrases ():
  734. self._committed_phrases.append (phrase)
  735. return True
  736. if i >=1 and i <= len (self._special_candidates):
  737. self._committed_special_phrase = self._special_candidates [i - 1]
  738. return True
  739. if len (self._preedit_phrases) != 1:
  740. i -= 1
  741. i -= len (self._special_candidates)
  742. if i >= len (self._candidates):
  743. return False
  744. self._committed_phrases.append ( self._candidates[i])
  745. pinyin_list = self._user_input.get_pinyin_list ()
  746. if self._committed_phrases.length_of_chars () == len (pinyin_list):
  747. return True
  748. self._update ()
  749. return False
  750. def _remove_candidate (self, i):
  751. if i >= 1:
  752. i -= len (self._special_candidates)
  753. if len (self._preedit_phrases) != 1:
  754. i -= 1
  755. if i >= len (self._candidates) or i < 0:
  756. return False
  757. if self._candidates[i][PYSQLiteDB.FREQ] != None: # This phrase was not create by user.
  758. return False
  759. candidate = self._candidates.pop (i)
  760. self._pydb.remove_phrase (candidate)
  761. self._update ()
  762. return True
  763. def process_key_event (self, key):
  764. result = self._internal_process_key_event (key)
  765. self._prev_key = key
  766. return result
  767. def commit_string (self, string):
  768. self._temp_english_mode = False
  769. self._i_mode = False
  770. self._candidates = []
  771. self._english_candidates = []
  772. self._cursor = 0
  773. self._user_input.clear ()
  774. self._preedit_string = u""
  775. self._committed_phrases.clear ()
  776. self._committed_special_phrase = u""
  777. IMEngine.commit_string (self, string)
  778. self._prev_char = string[-1]
  779. self._update ()
  780. def move_preedit_caret (self, pos):
  781. IMEngine.move_preedit_caret (self, pos)
  782. def select_candidate (self, index):
  783. IMEngine.select_candidate (self, index)
  784. def update_lookup_table_page_size (self, page_size):
  785. IMEngine.update_lookup_table_page_size (self, page_size)
  786. def lookup_table_page_up (self):
  787. if self._lookup_table.page_up ():
  788. self.update_lookup_table (self._lookup_table)
  789. return True
  790. IMEngine.lookup_table_page_up (self)
  791. return True
  792. def lookup_table_page_down (self):
  793. if self._lookup_table.page_down ():
  794. self.update_lookup_table (self._lookup_table)
  795. return True
  796. IMEngine.lookup_table_page_down (self)
  797. return True
  798. def lookup_table_cursor_up (self):
  799. if len (self._candidates) == 0:
  800. return False
  801. if self._lookup_table.cursor_up ():
  802. self.update_lookup_table (self._lookup_table)
  803. return True
  804. def lookup_table_cursor_down (self):
  805. if len (self._candidates) == 0:
  806. return False
  807. if self._lookup_table.cursor_down ():
  808. self.update_lookup_table (self._lookup_table)
  809. return True
  810. def reset (self):
  811. self._temp_english_mode = False
  812. self._i_mode = False
  813. self._user_input.clear ()
  814. self._committed_phrases.clear ()
  815. self._committed_special_phrase = u""
  816. self._preedit_string = u""
  817. self._special_candidates = []
  818. self._candidates = []
  819. self._english_candidates = []
  820. self._cursor = 0
  821. self._double_quotation_state = False
  822. self._single_quotation_state = False
  823. self._prev_key = None
  824. self._prev_char = None
  825. self._update ()
  826. IMEngine.reset (self)
  827. def focus_in (self):
  828. self._init_properties ()
  829. if PinYinEngine._shuangpin:
  830. self._py_parser = PYParser.ShuangPinParser (PinYinEngine._shuangpin_schema)
  831. else:
  832. self._py_parser = PYParser.PinYinParser ()
  833. self._user_input.set_parser (self._py_parser)
  834. self._user_input.set_gbk (PinYinEngine._gbk)
  835. self._user_input.set_auto_correct (PinYinEngine._auto_correct)
  836. IMEngine.focus_in (self)
  837. self._update ()
  838. def focus_out (self):
  839. self.reset ()
  840. IMEngine.focus_out (self)
  841. def trigger_property (self, property):
  842. if property == "status":
  843. self._change_mode ()
  844. elif property == "full_letter":
  845. self._full_width_letter [self._mode] = not self._full_width_letter [self._mode]
  846. self._refresh_properties ()
  847. elif property == "full_punct":
  848. self._full_width_punct [self._mode] = not self._full_width_punct [self._mode]
  849. self._refresh_properties ()
  850. elif property == "shuangpin":
  851. PinYinEngine._shuangpin = not PinYinEngine._shuangpin
  852. self.reset ()
  853. if PinYinEngine._shuangpin:
  854. self._py_parser = PYParser.ShuangPinParser (PinYinEngine._shuangpin_schema)
  855. else:
  856. self._py_parser = PYParser.PinYinParser ()
  857. self._user_input.set_parser (self._py_parser)
  858. self._config.write ("/IMEngine/Python/PinYin/ShuangPin", PinYinEngine._shuangpin)
  859. self._refresh_properties ()
  860. elif property == "gbk":
  861. PinYinEngine._gbk = not PinYinEngine._gbk
  862. self.reset ()
  863. self._config.write ("/IMEngine/Python/PinYin/SupportGBK", PinYinEngine._gbk)
  864. self._refresh_properties ()
  865. elif property == "setup":
  866. self.start_helper ("eebeecd7-cb22-48f4-8ced-70e42dad1a79")
  867. IMEngine.trigger_property (self, property)
  868. def process_helper_event (self, helper_uuid, trans):
  869. IMEngine.process_helper_event (self, helper_uuid, trans)
  870. def update_client_capabilities (self, cap):
  871. IMEngine.update_client_capabilities (self, cap)
  872. def reload_config (self, config):
  873. self._lookup_table.set_page_size (PinYinEngine._page_size)
  874. self.focus_in ()
  875. class UserInput:
  876. "UserInput holds user input chars"
  877. def __init__ (self, parser, max_length = __MAX_LEN__):
  878. self._parser = parser
  879. self._max_length = max_length
  880. self._auto_correct = True
  881. self._gbk = True
  882. self._chars = ([], [])
  883. self._pinyin_list = []
  884. def clear (self):
  885. self._chars = ([], [])
  886. self._pinyin_list = []
  887. def set_parser (self, parser):
  888. self.clear ()
  889. self._parser = parser
  890. def set_gbk (self, gbk):
  891. self._gbk = gbk
  892. self.clear ()
  893. def set_auto_correct (self, auto_correct):
  894. self._auto_correct = auto_correct
  895. self.clear ()
  896. def get_pinyin_list (self):
  897. return self._pinyin_list
  898. def get_chars (self):
  899. return self._chars[0] + self._chars[1]
  900. def get_invalid_chars (self):
  901. return self._chars[1]
  902. def get_invalid_string (self):
  903. return "".join (self._chars[1])
  904. def append (self, c):
  905. if len (self._chars[0]) + len (self._chars[1]) == self._max_length:
  906. return
  907. if self._chars[1]:
  908. self._chars[1].append (c)
  909. else:
  910. try:
  911. self._pinyin_list = self._parser.parse ("".join (self._chars[0] + [c]), self._auto_correct, self._gbk)
  912. self._chars[0].append (c)
  913. except:
  914. self._chars[1].append (c)
  915. def pop (self):
  916. resutl = []
  917. if len (self._chars[1]) != 0:
  918. return self._chars[1].pop ()
  919. elif len (self._chars[0]) != 0:
  920. c = self._chars[0].pop ()
  921. if len (self._chars[0]) != 0:
  922. self._pinyin_list = self._parser.parse ("".join (self._chars[0]), self._auto_correct, self._gbk)
  923. else:
  924. self._pinyin_list = []
  925. return c
  926. else:
  927. return ""
  928. def __len__ (self):
  929. return len (self._chars[0]) + len (self._chars[1])
  930. class PhraseList:
  931. """PhraseList contains phrases"""
  932. def __init__ (self):
  933. self._list = []
  934. self._length_of_chars = 0
  935. def clear (self):
  936. """Remove all phrases from the list"""
  937. self._list = []
  938. self._length_of_chars = 0
  939. def append (self, phrase):
  940. """Append a phrase into the list"""
  941. self._list.append (phrase)
  942. self._length_of_chars += phrase[PYSQLiteDB.YLEN]
  943. def pop (self):
  944. phrase = self._list.pop ()
  945. self._length_of_chars -= phrase[PYSQLiteDB.YLEN]
  946. return phrase
  947. def count (self):
  948. """Return count of phrases in the list"""
  949. return len (self._list)
  950. def length_of_chars (self):
  951. """Return number of chars in all phrases in the list"""
  952. return self._length_of_chars
  953. def get_phrases (self):
  954. """Return all phrases"""
  955. return self._list
  956. def get_string (self):
  957. """Join all phrases into a string object and return it."""
  958. get_phrase = lambda x: x[PYSQLiteDB.PHRASE]
  959. return u"".join (map (get_phrase, self._list))
  960. def __str__ (self):
  961. return self.get_string ().encode ("utf8")
  962. def __len__ (self):
  963. return len (self._list)
  964. class Factory (IMEngineFactory):
  965. """PinYin IM Engine Factory"""
  966. def __init__ (self, config):
  967. IMEngineFactory.__init__ (self, config)
  968. # define factory properties
  969. self.name = _(u"Python Pin Yin")
  970. self.uuid = "29ab338a-5a27-46b8-96cd-abbe86f17132"
  971. self.authors = u"Huang Peng <shawn.p.huang@gmail.com>"
  972. self.icon_file = "/usr/share/scim/icons/scim-python.png"
  973. self.credits = u"GPL"
  974. self.help = _(u"Help For Python PinYin\n\tPlease read http://code.google.com/p/scim-python/wiki/PinYinUserGuide")
  975. # init factory
  976. self._config = config
  977. self.set_languages ("zh")
  978. self.reload_config (config)
  979. def create_instance (self, encoding, id):
  980. engine = PinYinEngine (self, self._config, encoding, id)
  981. return engine
  982. def destroy (self):
  983. PinYinEngine._pydb.flush (True)
  984. print "PinYin destroy"
  985. def reload_config (self, config):
  986. PinYinEngine._shuangpin_schema = \
  987. config.read ("/IMEngine/Python/PinYin/ShuangPinSchema", PinYinEngine._shuangpin_schema)
  988. if PinYinEngine._shuangpin_schema not in PYDict.SHUANGPIN_SCHEMAS:
  989. PinYinEngine._shuangpin_schema = "MSPY"
  990. PinYinEngine._fuzzy_pinyin[0] = \
  991. config.read ("/IMEngine/Python/PinYin/FuzzyPinYin", PinYinEngine._fuzzy_pinyin[0])
  992. PinYinEngine._fuzzy_pinyin[1] = \
  993. config.read ("/IMEngine/Python/PinYin/FuzzyS_Sh", PinYinEngine._fuzzy_pinyin[1])
  994. PinYinEngine._fuzzy_pinyin[2] = \
  995. config.read ("/IMEngine/Python/PinYin/FuzzyC_Ch", PinYinEngine._fuzzy_pinyin[2])
  996. PinYinEngine._fuzzy_pinyin[3] = \
  997. config.read ("/IMEngine/Python/PinYin/FuzzyZ_Zh", PinYinEngine._fuzzy_pinyin[3])
  998. PinYinEngine._fuzzy_pinyin[4] = \
  999. config.read ("/IMEngine/Python/PinYin/FuzzyL_N", PinYinEngine._fuzzy_pinyin[4])
  1000. PinYinEngine._fuzzy_pinyin[5] = \
  1001. config.read ("/IMEngine/Python/PinYin/FuzzyIn_Ing", PinYinEngine._fuzzy_pinyin[5])
  1002. PinYinEngine._fuzzy_pinyin[6] = \
  1003. config.read ("/IMEngine/Python/PinYin/FuzzyEn_Eng", PinYinEngine._fuzzy_pinyin[6])
  1004. PinYinEngine._fuzzy_pinyin[7] = \
  1005. config.read ("/IMEngine/Python/PinYin/FuzzyAn_Ang", PinYinEngine._fuzzy_pinyin[7])
  1006. PinYinEngine._auto_correct = \
  1007. config.read ("/IMEngine/Python/PinYin/AutoCorrect", PinYinEngine._auto_correct)
  1008. PinYinEngine._spell_check = \
  1009. config.read ("/IMEngine/Python/PinYin/SpellCheck", PinYinEngine._spell_check)
  1010. PinYinEngine._page_size = \
  1011. config.read ("/IMEngine/Python/PinYin/PageSize", PinYinEngine._page_size)
  1012. if PinYinEngine._page_size < 1 or PinYinEngine._page_size > 9:
  1013. PinYinEngine._page_size = 5
  1014. PinYinEngine._gbk = \
  1015. config.read ("/IMEngine/Python/PinYin/SupportGBK", PinYinEngine._gbk)
  1016. PinYinEngine._shuangpin = \
  1017. config.read ("/IMEngine/Python/PinYin/ShuangPin", PinYinEngine._shuangpin)
  1018. PinYinEngine._phrase_color = \
  1019. config.read ("/IMEngine/Python/PinYin/PhraseColor", PinYinEngine._phrase_color)
  1020. PinYinEngine._new_phrase_color = \
  1021. config.read ("/IMEngine/Python/PinYin/NewPhraseColor", PinYinEngine._new_phrase_color)
  1022. PinYinEngine._user_phrase_color = \
  1023. config.read ("/IMEngine/Python/PinYin/UserPhraseColor", PinYinEngine._user_phrase_color)
  1024. PinYinEngine._special_phrase_color = \
  1025. config.read ("/IMEngine/Python/PinYin/SpecialPhraseColor", PinYinEngine._special_phrase_color)
  1026. PinYinEngine._english_phrase_color = \
  1027. config.read ("/IMEngine/Python/PinYin/EnglishPhraseColor", PinYinEngine._english_phrase_color)
  1028. PinYinEngine._error_eng_phrase_color = \
  1029. config.read ("/IMEngine/Python/PinYin/ErrorEnglishPhraseColor", PinYinEngine._error_eng_phrase_color)
  1030. PinYinEngine._uv_to_temp = \
  1031. config.read ("/IMEngine/Python/PinYin/UVToTemp", PinYinEngine._uv_to_temp)
  1032. PinYinEngine._shift_select_canidates = \
  1033. config.read ("/IMEngine/Python/PinYin/ShiftSelectCandidates", PinYinEngine._shift_select_candidates)
  1034. PinYinEngine._comma_page_down_up = \
  1035. config.read ("/IMEngine/Python/PinYin/CommaPageDownUp", PinYinEngine._comma_page_down_up)
  1036. PinYinEngine._equal_page_down_up = \
  1037. config.read ("/IMEngine/Python/PinYin/EqualPageDownUp", PinYinEngine._equal_page_down_up)
  1038. PinYinEngine._auto_commit = \
  1039. config.read ("/IMEngine/Python/PinYin/AutoCommit", PinYinEngine._auto_commit)