PageRenderTime 929ms CodeModel.GetById 161ms app.highlight 644ms RepoModel.GetById 111ms app.codeStats 0ms

/python/engine/PinYin/PinYin.py

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