# -*- coding: utf-8 -*- # # # Fun Input Toy for scim-python # # Copyright (c) 2007-2008 Huajun Feng # # $Id: # # Author: fenghuajun@gmail.com (Feng Huajun) """Description of this module""" __author__ = "fenghuajun@gmail.com (Feng Huajun)" import atexit import os import random import sys import time import scim from scim import KeyCode from scim import KeyMask from scim import Property from scim import SCTC import scim.ascii as ascii import FitxConfig from EnglishHelper import EnglishHelper from SpecialTable import SymbolTable #import logging as log #log.basicConfig(level=log.DEBUG, # format='%(asctime)s %(levelname)s %(message)s', # filename='/home/loya/fitx.log') from gettext import dgettext _ = lambda a : dgettext("fitx", a) N_ = lambda a : a from FunInputClient import * from FitxUtil import * IMEngine = scim.IMEngine IMEngineFactory = scim.IMEngineFactory def goodbye(toy): """Shutdown the FIT frontend""" toy.release_fitx() class FunInputToy(IMEngine): """FIT engine inherited from IMEngine""" def __init__(self, factory, config, encoding, tid): """Constructor to init FIT object. Note: Args: factory: config: encoding: tid: """ IMEngine.__init__(self, factory, config, encoding, tid) self._config = config self._english_helper = EnglishHelper() self._lookup_table = scim.LookupTable(5) self._input_string = u"" self._display_string = u"" self._candidate_words = [] self._selected_words = [] self._selected_count = 0 self._v_mode=0 self._i_mode=0 self._preedit_caret_pos=0 self._yins = [] self._double_quotation_state=False self._single_quotation_state=False self._last_add_words=None #1- full, 0-half self._punct_mode=1 #1-full, 0-half self._full_letter=0 self._english_mode=0 self._punct_property = Property ("full_punct", "") self._inputmode_property=Property("input_mode","") self._full_letter_property=Property("full_letter","") def _init_properties (self): properties = [self._punct_property,\ self._full_letter_property,self._inputmode_property] self.register_properties (properties) self._refresh_properties () def _refresh_properties (self): if self._punct_mode:prop_punct_full(self._punct_property) else:prop_punct_half(self._punct_property) if self._english_mode: self._inputmode_property.label=_('EN') self._inputmode_property.tip=_('English input mode') else: if self._fitx_config._input_mode=='BH': self._inputmode_property.label=_('BH') self._inputmode_property.tip=_('BiHua mode') elif self._fitx_config._input_mode=='WBX': self._inputmode_property.label=_('WBX') self._inputmode_property.tip=_('WuBi mode') else: self._inputmode_property.label=_('PY') self._inputmode_property.tip=_('PinYin mode') prop_full_letter(self._full_letter_property,self._full_letter) properties = [self._punct_property,self._inputmode_property,self._full_letter_property] for prop in properties: self.update_property (prop) def set_fitx_client(self,eng): self._fengine=eng def set_specail_table(self,st): self._special_table=st def set_fitx_config(self, config): self._fitx_config=config self._lookup_table.set_page_size(self._fitx_config._lookup_table_page_size) def reset(self): """reset the FIT engine internal state""" self._input_string = u"" self._display_string = u"" self._candidate_words = [] self._selected_words = [] self._selected_count = 0 self._v_mode=0 self._i_mode=0 self._yins = [] self._preedit_caret_pos=0 self.update() IMEngine.reset(self) def _filter(self,srcstr): if (not self._v_mode) and self._fitx_config._trad_chinese: return SCTC.sc_to_tc(srcstr) else: return srcstr def _get_caret_pos_for_display(self,display): if len(self._input_string)==self._preedit_caret_pos: return len(display) pos=0 p=0 if self._selected_words: nm_str="'".join(self._yins[0:self._selected_count]) while p 0: if self._english_helper.check(self._display_string): self._candidate_words=[] else: self._candidate_words=self._english_helper.search(self._display_string) else:self._candidate_words=[] self._selected_words = [] self._selected_count = 0 self._yins=[self._display_string] elif self._fitx_config._enable_i_mode and self._input_string[0]=='i': self._i_mode=1 self._display_string=self._input_string[1:] if len(self._display_string)>0: sym_word_list=self._special_table.get_symbol_list(self._display_string) if sym_word_list: self._candidate_words=sym_word_list else: self._candidate_words=[] else:self._candidate_words=[] self._selected_words = [] self._selected_count = 0 self._yins=[self._display_string] else: self._display_string = self._fengine.fix(self._input_string) self._candidate_words = self._fengine.search(self._display_string.strip("'")) self._yins = self._display_string.strip("'").split("'") self._selected_words = [] self._selected_count = 0 if self._fitx_config._input_mode=='WBX' and self._fitx_config._wbxmode=='Normal' and self._candidate_words and len(self._candidate_words)==1 and len(self._input_string)==4: #when only one candidate, commit it self.commit_string(self._filter(self._candidate_words[0]._string)) return else: self._input_string = u"" self._display_string = u"" self._candidate_words = [] self._selected_words = [] self._selected_count = 0 self._yins = [] #if(self._candidate_words): # tmp_display_string=self._display_string #else : # if self._v_mode or self._i_mode: # tmp_display_string=self._display_string # else:tmp_display_string=self._input_string tmp_display_string=self._display_string attrs = [] attrs.append(scim.Attribute(0, len(tmp_display_string), scim.ATTR_FOREGROUND, 0x00ff0000)) attrs.append(scim.Attribute(0, len(tmp_display_string), scim.ATTR_DECORATE, scim.ATTR_DECORATE_UNDERLINE)) self.update_preedit_string(tmp_display_string, attrs) self.update_preedit_caret(self._get_caret_pos_for_display(self._display_string)) if self._display_string: self.show_preedit_string() else: self.hide_preedit_string() if not self._candidate_words: self.hide_lookup_table() self._lookup_table.clear() for w in self._candidate_words: if self._fitx_config._trad_chinese: self._lookup_table.append_candidate(self._filter(w._string)) else:self._lookup_table.append_candidate(w._string) self.update_lookup_table(self._lookup_table) if self._candidate_words: self.show_lookup_table() def select(self, index): """Perform selected word action by looking up index Args: index: the word index that is selected. """ if index < len(self._candidate_words): if self._v_mode or self._i_mode: self._selected_count+=1 else:self._selected_count += len(self._candidate_words[index]._code.split("'")) self._selected_words.append(self._candidate_words[index]) self.update() def backward(self): """Perform the backward lookup""" if self._selected_words: w = self._selected_words.pop() self._selected_count -= len(w._string) self.update() def toggle_english_mode(self): self._english_mode = not self._english_mode if self._english_mode: self._punct_mode=0 self._full_letter=0 else: self._punct_mode=1 self._full_letter=0 self._refresh_properties() self.reset() return True def key_input_it(self,c): self._input_string=self._input_string[0:self._preedit_caret_pos]+c+self._input_string[self._preedit_caret_pos:] self._preedit_caret_pos+=1 def _find_pos_no_sep(self,src,dest,src_pos): dest_pos=0 p=0 while p 0 and self._preedit_caret_pos==minlen: self.backward() if self._preedit_caret_poslen(self._input_string):self._preedit_caret_pos=len(self._input_string) self.update_preedit_caret(self._get_caret_pos_for_display(self._display_string)) return True def key_right(self): if not self._input_string: return False self._preedit_caret_pos+=1 #if self._preedit_caret_pos<0:self._preedit_caret_pos=0 if self._preedit_caret_pos>len(self._input_string):self._preedit_caret_pos=len(self._input_string) self.update_preedit_caret(self._get_caret_pos_for_display(self._display_string)) return True def process_key_event(self, key): r=self.process_key_event_internal(key) self._last_key=key if not key.mask & KeyMask.ReleaseMask: if not r and (key.code >=KeyCode.KEY_0 and key.code<=KeyCode.KEY_9): self._last_is_num_key=True else: self._last_is_num_key=False return r def process_key_event_internal(self, key): """Process the key event Args: key: the key code object that has been pressed. Returns: boolean whether the event has been processed successfully. """ if (key.code ==KeyCode.KEY_Shift_L or key.code ==KeyCode.KEY_Shift_R) and key.mask & KeyMask.ShiftMask and key.mask & KeyMask.ReleaseMask: if (not self._input_string) or (not self._candidate_words): if self._prev_key and key.code==self._prev_key.code: return self.toggle_english_mode() elif not self._prev_key:return self.toggle_english_mode() if key.mask & KeyMask.ReleaseMask: # Ignore release event return False self._prev_key = key if key.mask & KeyMask.AltMask: return False if key.mask & KeyMask.ControlMask: if key.code==KeyCode.KEY_period: #Ctrl+. switch punct mode self._punct_mode = not self._punct_mode self._refresh_properties() return True elif key.code >= KeyCode.KEY_1 and key.code <= KeyCode.KEY_9: #remove user word i=key.code-KeyCode.KEY_1+self._lookup_table.get_current_page_start() self.remove_user_word(i) return True return False if key.code == KeyCode.KEY_space and key.mask & KeyMask.ShiftMask: self.trigger_property('full_letter') return True if key.mask & KeyMask.CapsLockMask: return False if self._english_mode: if key.code <=127: if self._punct_mode and ascii.ispunct(key.code): self.commit_string_no_add(self._convert_to_full_width(unichr(key.code))) return True if self._full_letter: self.commit_string_no_add(self._convert_to_full_width(unichr(key.code))) return True return False if key.code in (KeyCode.KEY_bracketleft, KeyCode.KEY_bracketright): if self._input_string and self._candidate_words: w=self._candidate_words[self._lookup_table.get_current_page_start()] word=w._string if key.code == KeyCode.KEY_bracketleft: if self._fitx_config._enable_bracket_page : self.lookup_table_page_up() else: IMEngine.commit_string (self, word[0]) self.reset() else: if self._fitx_config._enable_bracket_page: self.lookup_table_page_down() else: IMEngine.commit_string (self, word[(len(word)-1)]) self.reset() return True if (key.code <=127) and ascii.ispunct(key.code): if self._input_string and (not self._fitx_config._disable_punct_commit): can_commit=True if key.code==KeyCode.KEY_apostrophe or (key.code==KeyCode.KEY_semicolon and self._fitx_config._input_mode=='PY' and self._fitx_config._pymode<>'QP'): can_commit=False if (key.code==KeyCode.KEY_comma or key.code==KeyCode.KEY_period): if (not self._fitx_config._enable_comma_period_commit): can_commit=False if (key.code==KeyCode.KEY_minus or key.code==KeyCode.KEY_equal): if (not self._fitx_config._enable_minus_equal_commit): can_commit=False if can_commit and self._candidate_words: self.select(self._lookup_table.get_current_page_start()) if not self._input_string and self._punct_mode: IMEngine.commit_string (self, self._convert_to_full_width (unichr(key.code))) return True return False if can_commit:return False else: if not self._input_string: if(self._punct_mode): if self._fitx_config._half_punct_after_number and self._last_is_num_key: return False IMEngine.commit_string (self, self._convert_to_full_width (unichr(key.code))) self.reset() return True return False if (key.code >= KeyCode.KEY_a and key.code <= KeyCode.KEY_z) or \ (key.code >= KeyCode.KEY_A and key.code <= KeyCode.KEY_Z) or \ (key.code == KeyCode.KEY_apostrophe and len(self._input_string) != 0): self.key_input_it(unichr(key.code)) self.update() return True elif key.code==KeyCode.KEY_semicolon and len(self._input_string) != 0 and self._fitx_config._pymode<>'QP': self.key_input_it(unichr(key.code)) self.update() return True elif key.code == KeyCode.KEY_Left: return self.key_left() elif key.code == KeyCode.KEY_Right: return self.key_right() elif key.code in (KeyCode.KEY_Page_Up, KeyCode.KEY_minus, KeyCode.KEY_comma): if not self._input_string: return False if self._candidate_words: self.lookup_table_page_up() else: self.update() return True elif key.code in (KeyCode.KEY_Page_Down, KeyCode.KEY_equal, KeyCode.KEY_period): if not self._input_string: return False if self._candidate_words: self.lookup_table_page_down() else: self.update() return True elif (key.code >= KeyCode.KEY_1 and key.code <= KeyCode.KEY_9) or \ (key.code in (KeyCode.KEY_KP_Space, KeyCode.KEY_space, KeyCode.KEY_Shift_L, KeyCode.KEY_Shift_R)) : if not self._input_string: if self._full_letter and key.code <> KeyCode.KEY_Shift_L and key.code <> KeyCode.KEY_Shift_R: self.commit_string_no_add(self._convert_to_full_width(unichr(key.code))) return True return False if not self._candidate_words: return True if key.code in (KeyCode.KEY_KP_Space, KeyCode.KEY_space): key.code = KeyCode.KEY_1 #support shift key to select word if key.code == KeyCode.KEY_Shift_L : key.code = KeyCode.KEY_1+1 elif key.code == KeyCode.KEY_Shift_R : key.code = KeyCode.KEY_1+2 i = key.code - KeyCode.KEY_1 + \ self._lookup_table.get_current_page_start() self.select(i) return True elif key.code == KeyCode.KEY_Return: if self._input_string: if self._full_letter:self._display_string=u''.join(map(self._convert_to_full_width,self._input_string)) elif not self._v_mode:self._display_string = self._input_string #self.commit_string() self.commit_string_no_add(self._display_string) return True return False elif key.code == KeyCode.KEY_BackSpace: if not self._input_string: if self._last_add_words and (time.time()-self._last_add_words_time)<2: for x in self._last_add_words: if x._hit<2:self._fengine.remove(x) self._last_add_words=None return False #if self._selected_words: self.backward() #elif self._preedit_caret_pos>0: l = len(self._input_string) self._input_string = self._input_string[0:(self._preedit_caret_pos-1)]+self._input_string[self._preedit_caret_pos:] self._preedit_caret_pos-=1 self.update() return True elif key.code == KeyCode.KEY_Delete: if self._input_string: if self._preedit_caret_pos": return u"\u300b" return scim.unichar_half_to_full (c) def commit_string_no_add(self,string): IMEngine.commit_string(self, string) self.reset() def commit_string(self, string=None): """Add words into the word dictionary Args: string: a string of words. """ if string == None: string = self._display_string IMEngine.commit_string(self, string) if self._i_mode or self._v_mode: self.reset() return if len(self._selected_words) > 1: code = "" for w in self._selected_words: code += "'" + w._code nw = Word(string, code[1:]) self._selected_words.append(nw) self._fengine.add(self._selected_words) self._last_add_words=self._selected_words self._last_add_words_time=time.time() self.reset() def remove_user_word(self,index): if index < len(self._candidate_words): w=self._candidate_words[index] self._fengine.remove(w) self.update() def move_preedit_caret(self, pos): """Remove the preedit caret char Args: pos: the position where the caret should be removed. """ IMEngine.move_preedit_caret(self, pos) def select_candidate(self, index): """Selete the candidate words. Args: index: the index of the word to select. """ IMEngine.select_candidate(self, index) def update_lookup_table_page_size(self, page_size): """Update the lookup table page size Args: page_size: the page_size """ IMEngine.update_lookup_table_page_size (self, page_size) def lookup_table_page_up(self): """Filp the lookup table up for selection""" if self._lookup_table.page_up(): self.update_lookup_table(self._lookup_table) return True IMEngine.lookup_table_page_up(self) return False def lookup_table_page_down(self): """Flip the lookup table down for selection""" if self._lookup_table.page_down(): self.update_lookup_table(self._lookup_table) return True IMEngine.lookup_table_page_down (self) return False def focus_in(self): """ """ self._init_properties() IMEngine.focus_in(self) self.update() def focus_out(self): """ """ self.reset() IMEngine.focus_out(self) def trigger_property(self, aproperty): """ """ if aproperty == "full_punct": self._punct_mode = not self._punct_mode if aproperty == "full_letter": self._full_letter = not self._full_letter # if aproperty == "input_mode": # self._fengine.toggle_encoding() self._refresh_properties () IMEngine.trigger_property(self, aproperty) def process_helper_event(self, helper_uuid, trans): """ """ IMEngine.process_helper_event (self, helper_uuid, trans) def update_client_capabilities(self, cap): """ """ IMEngine.update_client_capabilities (self, cap) def reload_config(self, config): """ """ # The factory object has this method, why we have another one here? pass class Factory(IMEngineFactory): """A IM engine factory class""" def __init__(self, config): """Constructor to init the factory object. Note: Args: config: """ IMEngineFactory.__init__(self, config) self._config = config self.name = u"Fun Input Toy" self.uuid = "b88556dc-9023-4daa-9474-b0de394a636f" self.authors = u"Huajun Feng " self.icon_file = "/usr/share/fit/fitx.png" self.credits = u"GPL" self.help = _(u'http://fit.coollittlethings.com') self.set_languages("zh") self._fitxpath=self._start_fitx() self._fitx_client=FunInputClient(self._fitxpath) config_path=os.path.expanduser("~/.fit/fitd.conf") self._fitx_config=FitxConfig.FitxConfig(config_path) self._fitx_config.load() self._special_table=SymbolTable('/usr/share/fit/special_table') print "created a new fitx factory." #log.info("--fitx factory init----------------") #log.info(self._fitxpath) def create_instance(self, encoding, tid): """ """ # never used? engine = FunInputToy(self, self._config, encoding, tid) engine.set_fitx_client(self._fitx_client) engine.set_fitx_config(self._fitx_config) engine.set_specail_table(self._special_table) #log.info("create_instance") return engine def _start_fitx(self): """Start the FIT IM engine""" #while True: #filename = "/tmp/fit_" + str(int(random.uniform( # 32767, 65536))) + ".sock" #if not os.path.exists(filename): break filename='/tmp/fit_%d.sock' % os.geteuid() if not os.path.exists(filename): os.system("fitx -f " + filename + " &") atexit.register(goodbye, self) #log.info("start fitx "+filename) return filename def destroy (self): self.release_fitx() print "invoked destroy()" #log.info("invoked destroy()") def release_fitx(self): """Exit the FIT IM engine""" self._fitx_client.release() print "invoked release_fitx()" try: os.unlink(self._fitxpath) except: pass def reload_config(self, config): """Reload Fitx configuration Args: config: the Fitx configuration. """ print "reload fitx client" self._fitx_config.load() self._fitx_client.reload()