File: //usr/share/ibus-table/engine/table.py
# -*- coding: utf-8 -*-
# vim:et sts=4 sw=4
#
# ibus-table - The Tables engine for IBus
#
# Copyright (c) 2008-2009 Yu Yuwei <[email protected]>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# $Id: $
#
__all__ = (
"tabengine",
)
import os
import string
from gi.repository import IBus
from gi.repository import GLib
from curses import ascii
#import tabsqlitedb
import tabdict
import re
patt_edit = re.compile (r'(.*)###(.*)###(.*)')
patt_uncommit = re.compile (r'(.*)@@@(.*)')
from gettext import dgettext
_ = lambda a : dgettext ("ibus-table", a)
N_ = lambda a : a
def variant_to_value(variant):
if type(variant) != GLib.Variant:
return variant
type_string = variant.get_type_string()
if type_string == 's':
return variant.get_string()
elif type_string == 'i':
return variant.get_int32()
elif type_string == 'b':
return variant.get_boolean()
elif type_string == 'as':
# In the latest pygobject3 3.3.4 or later, g_variant_dup_strv
# returns the allocated strv but in the previous release,
# it returned the tuple of (strv, length)
if type(GLib.Variant.new_strv([]).dup_strv()) == tuple:
return variant.dup_strv()[0]
else:
return variant.dup_strv()
else:
print 'error: unknown variant type:', type_string
return variant
def argb(a, r, g, b):
return ((a & 0xff)<<24) + ((r & 0xff) << 16) + ((g & 0xff) << 8) + (b & 0xff)
def rgb(r, g, b):
return argb(255, r, g, b)
__half_full_table = [
(0x0020, 0x3000, 1),
(0x0021, 0xFF01, 0x5E),
(0x00A2, 0xFFE0, 2),
(0x00A5, 0xFFE5, 1),
(0x00A6, 0xFFE4, 1),
(0x00AC, 0xFFE2, 1),
(0x00AF, 0xFFE3, 1),
(0x20A9, 0xFFE6, 1),
(0xFF61, 0x3002, 1),
(0xFF62, 0x300C, 2),
(0xFF64, 0x3001, 1),
(0xFF65, 0x30FB, 1),
(0xFF66, 0x30F2, 1),
(0xFF67, 0x30A1, 1),
(0xFF68, 0x30A3, 1),
(0xFF69, 0x30A5, 1),
(0xFF6A, 0x30A7, 1),
(0xFF6B, 0x30A9, 1),
(0xFF6C, 0x30E3, 1),
(0xFF6D, 0x30E5, 1),
(0xFF6E, 0x30E7, 1),
(0xFF6F, 0x30C3, 1),
(0xFF70, 0x30FC, 1),
(0xFF71, 0x30A2, 1),
(0xFF72, 0x30A4, 1),
(0xFF73, 0x30A6, 1),
(0xFF74, 0x30A8, 1),
(0xFF75, 0x30AA, 2),
(0xFF77, 0x30AD, 1),
(0xFF78, 0x30AF, 1),
(0xFF79, 0x30B1, 1),
(0xFF7A, 0x30B3, 1),
(0xFF7B, 0x30B5, 1),
(0xFF7C, 0x30B7, 1),
(0xFF7D, 0x30B9, 1),
(0xFF7E, 0x30BB, 1),
(0xFF7F, 0x30BD, 1),
(0xFF80, 0x30BF, 1),
(0xFF81, 0x30C1, 1),
(0xFF82, 0x30C4, 1),
(0xFF83, 0x30C6, 1),
(0xFF84, 0x30C8, 1),
(0xFF85, 0x30CA, 6),
(0xFF8B, 0x30D2, 1),
(0xFF8C, 0x30D5, 1),
(0xFF8D, 0x30D8, 1),
(0xFF8E, 0x30DB, 1),
(0xFF8F, 0x30DE, 5),
(0xFF94, 0x30E4, 1),
(0xFF95, 0x30E6, 1),
(0xFF96, 0x30E8, 6),
(0xFF9C, 0x30EF, 1),
(0xFF9D, 0x30F3, 1),
(0xFFA0, 0x3164, 1),
(0xFFA1, 0x3131, 30),
(0xFFC2, 0x314F, 6),
(0xFFCA, 0x3155, 6),
(0xFFD2, 0x315B, 9),
(0xFFE9, 0x2190, 4),
(0xFFED, 0x25A0, 1),
(0xFFEE, 0x25CB, 1)]
def unichar_half_to_full (c):
code = ord (c)
for half, full, size in __half_full_table:
if code >= half and code < half + size:
return unichr (full + code - half)
return c
def unichar_full_to_half (c):
code = ord (c)
for half, full, size in __half_full_table:
if code >= full and code < full + size:
return unichr (half + code - full)
return c
class KeyEvent:
def __init__(self, keyval, is_press, state):
self.code = keyval
self.mask = state
if not is_press:
self.mask |= IBus.ModifierType.RELEASE_MASK
def __str__(self):
return "%s 0x%08x" % (IBus.keyval_name(self.code), self.mask)
class editor(object):
'''Hold user inputs chars and preedit string'''
def __init__ (self, config, phrase_table_index,valid_input_chars, max_key_length, database, parser = tabdict.parse, deparser = tabdict.deparse, max_length = 64):
self.db = database
self._config = config
self._name = self.db.get_ime_property('name')
self._config_section = "engine/Table/%s" % self._name.replace(' ','_')
self._pt = phrase_table_index
self._parser = parser
self._deparser = deparser
self._max_key_len = int(max_key_length)
self._max_length = max_length
self._valid_input_chars = valid_input_chars
#
# below vals will be reset in self.clear()
#
# we hold this: [str,str,...]
# self._chars: hold user input in table mode (valid,invalid,prevalid)
self._chars = [[],[],[]]
#self._t_chars: hold total input for table mode for input check
self._t_chars = []
# self._u_chars: hold user input but not manual comitted chars
self._u_chars = []
# self._tabkey_list: hold tab_key objects transform from user input chars
self._tabkey_list = []
# self._strings: hold preedit strings
self._strings = []
# self._cursor: the caret position in preedit phrases
self._cursor = [0,0]
# self._candidates: hold candidates selected from database [[now],[pre]]
self._candidates = [[],[]]
# __orientation: lookup table orientation
__orientation = variant_to_value(self._config.get_value(
self._config_section,
"LookupTableOrientation"))
if __orientation == None:
__orientation = self.db.get_orientation()
# __page_size: lookup table page size
# this is computed from the select_keys, so should be done after it
__page_size = self.db.get_page_size()
# self._lookup_table: lookup table
self._lookup_table = IBus.LookupTable.new(
page_size=__page_size,
cursor_pos=0,
cursor_visible=True,
round=True)
self._lookup_table.set_orientation (__orientation)
# self._select_keys: a list of chars for select keys
self.init_select_keys()
# self._py_mode: whether in pinyin mode
self._py_mode = False
# self._zi: the last Zi commit to preedit
self._zi = u''
# self._caret: caret position in lookup_table
self._caret = 0
# self._onechar: whether we only select single character
self._onechar = variant_to_value(self._config.get_value(
self._config_section,
"OneChar"))
if self._onechar == None:
self_onechar = False
# self._chinese_mode: the candidate filter mode,
# 0 means to show simplified Chinese only
# 1 means to show traditional Chinese only
# 2 means to show all characters but show simplified Chinese first
# 3 means to show all characters but show traditional Chinese first
# 4 means to show all characters
# we use LC_CTYPE or LANG to determine which one to use
self._chinese_mode = variant_to_value(self._config.get_value(
self._config_section,
"ChineseMode"))
if self._chinese_mode == None:
self._chinese_mode = self.get_chinese_mode()
self._auto_select = variant_to_value(self._config.get_value(
self._config_section,
"AutoSelect"))
if self._auto_select == None:
if self.db.get_ime_property('auto_select') != None:
self._auto_select = self.db.get_ime_property('auto_select').lower() == u'true'
else:
self._auto_select = False
def init_select_keys(self):
# __select_keys: lookup table select keys/labels
__select_keys = variant_to_value(self._config.get_value(
self._config_section,
"LookupTableSelectKeys"))
if __select_keys == None:
__select_keys = self.db.get_select_keys()
if __select_keys:
self.set_select_keys(__select_keys)
def set_select_keys(self, astring):
"""astring: select keys setting. e.g. 1,2,3,4,5,6,7,8,9"""
self._select_keys = [x.strip() for x in astring.split(",")]
for x in self._select_keys:
self._lookup_table.append_label(IBus.Text.new_from_string("{}.".format(x)))
def get_select_keys(self):
"""@return: a list of chars as select keys: ["1", "2", ...]"""
return self._select_keys
def get_chinese_mode (self):
'''Use db value or LC_CTYPE in your box to determine the _chinese_mode'''
# use db value, if applicable
__db_chinese_mode = self.db.get_chinese_mode()
if __db_chinese_mode >= 0:
return __db_chinese_mode
# otherwise
try:
if os.environ.has_key('LC_ALL'):
__lc = os.environ['LC_ALL'].split('.')[0].lower()
elif os.environ.has_key('LC_CTYPE'):
__lc = os.environ['LC_CTYPE'].split('.')[0].lower()
else:
__lc = os.environ['LANG'].split('.')[0].lower()
if __lc.find('_cn') != -1:
return 0
# hk and tw is should use tc as default
elif __lc.find('_hk') != -1 or __lc.find('_tw') != -1\
or __lc.find('_mo') != -1:
return 1
else:
if self.db._is_chinese:
# if IME declare as Chinese IME
return 0
else:
return -1
except:
return -1
def change_chinese_mode (self):
if self._chinese_mode != -1:
self._chinese_mode = (self._chinese_mode +1 ) % 5
self._config.set_value (
self._config_section,
"ChineseMode",
GLib.Variant.new_int32(self._chinese_mode))
def clear (self):
'''Remove data holded'''
self.over_input ()
self._t_chars = []
self._strings = []
self._cursor = [0,0]
self._py_mode = False
self._zi = u''
self.update_candidates
def is_empty (self):
return len(self._t_chars) == 0
def clear_input (self):
'''
Remove input characters held for Table mode,
'''
self._chars = [[],[],[]]
self._tabkey_list = []
self._lookup_table.clear()
self._lookup_table.set_cursor_visible(True)
self._candidates = [[],[]]
def over_input (self):
'''
Remove input characters held for Table mode,
'''
self.clear_input ()
self._u_chars = []
def set_parser (self, parser):
'''change input parser'''
self.clear ()
self._parser = parser
def add_input (self,c):
'''add input character'''
if len (self._t_chars) == self._max_length:
return True
self._zi = u''
if self._cursor[1]:
self.split_phrase()
if (len (self._chars[0]) == self._max_key_len and (not self._py_mode)) or ( len (self._chars[0]) == 7 and self._py_mode ) :
self.auto_commit_to_preedit()
res = self.add_input (c)
return res
elif self._chars[1]:
self._chars[1].append (c)
else:
if (not self._py_mode and ( c in self._valid_input_chars)) or\
(self._py_mode and (c in u'abcdefghijklmnopqrstuvwxyz!@#$%')):
try:
self._tabkey_list += self._parser (c)
self._chars[0].append (c)
except:
self._chars[1].append (c)
else:
self._chars[1].append (c)
self._t_chars.append(c)
res = self.update_candidates ()
return res
def pop_input (self):
'''remove and display last input char held'''
_c =''
if self._chars[1]:
_c = self._chars[1].pop ()
elif self._chars[0]:
_c = self._chars[0].pop ()
self._tabkey_list.pop()
if (not self._chars[0]) and self._u_chars:
self._chars[0] = self._u_chars.pop()
self._chars[1] = self._chars[1][:-1]
self._tabkey_list = self._parser (self._chars[0])
self._strings.pop (self._cursor[0] - 1 )
self._cursor[0] -= 1
self._t_chars.pop()
self.update_candidates ()
return _c
def get_input_chars (self):
'''get characters held, valid and invalid'''
return self._chars[0] + self._chars[1]
def get_input_chars_string (self):
'''Get valid input char string'''
return u''.join(map(str,self._t_chars))
def get_all_input_strings (self):
'''Get all uncommit input characters, used in English mode or direct commit'''
return u''.join( map(u''.join, self._u_chars + [self._chars[0]] \
+ [self._chars[1]]) )
def get_index(self,key):
'''Get the index of key in database table'''
return self._pt.index(key)
def split_phrase (self):
'''Splite current phrase into two phrase'''
_head = u''
_end = u''
try:
_head = self._strings[self._cursor[0]][:self._cursor[1]]
_end = self._strings[self._cursor[0]][self._cursor[1]:]
self._strings.pop(self._cursor[0])
self._strings.insert(self._cursor[0],_head)
self._strings.insert(self._cursor[0]+1,_end)
self._cursor[0] +=1
self._cursor[1] = 0
except:
pass
def remove_before_string (self):
'''Remove string before cursor'''
if self._cursor[1] != 0:
self.split_phrase()
if self._cursor[0] > 0:
self._strings.pop(self._cursor[0]-1)
self._cursor[0] -= 1
else:
pass
# if we remove all characters in preedit string, we need to clear the self._t_chars
if self._cursor == [0,0]:
self._t_chars =[]
def remove_after_string (self):
'''Remove string after cursor'''
if self._cursor[1] != 0:
self.split_phrase()
if self._cursor[0] >= len (self._strings):
pass
else:
self._strings.pop(self._cursor[0])
def remove_before_char (self):
'''Remove character before cursor'''
if self._cursor[1] > 0:
_str = self._strings[ self._cursor[0] ]
self._strings[ self._cursor[0] ] = _str[ : self._cursor[1]-1] + _str[ self._cursor[1] :]
self._cursor[1] -= 1
else:
if self._cursor[0] == 0:
pass
else:
if len ( self._strings[self._cursor[0] - 1] ) == 1:
self.remove_before_string()
else:
self._strings[self._cursor[0] - 1] = self._strings[self._cursor[0] - 1][:-1]
# if we remove all characters in preedit string, we need to clear the self._t_chars
if self._cursor == [0,0] and not self._strings:
self._t_chars =[]
def remove_after_char (self):
'''Remove character after cursor'''
if self._cursor[1] == 0:
if self._cursor[0] == len ( self._strings):
pass
else:
if len( self._strings[ self._cursor[0] ]) == 1:
self.remove_after_string ()
else:
self._strings[ self._cursor[0] ] = self._strings[ self._cursor[0] ][1:]
else:
if ( self._cursor[1] + 1 ) == len( self._strings[ self._cursor[0] ] ) :
self.split_phrase ()
self.remove_after_string ()
else:
string = self._strings[ self._cursor[0] ]
self._strings[ self._cursor[0] ] = string[:self._cursor[1]] + string[ self._cursor[1] + 1 : ]
def get_invalid_input_chars (self):
'''get invalid characters held'''
return self._chars[1]
def get_invalid_input_string (self):
'''get invalid characters in string form'''
return u''.join (self._chars[1])
def get_preedit_strings (self):
'''Get preedit strings'''
if self._candidates[0]:
if self._py_mode:
_p_index = 8
else:
_p_index = self.get_index ('phrase')
_candi = u'###' + self._candidates[0][ int (self._lookup_table.get_cursor_pos() ) ][ _p_index ] + u'###'
else:
input_chars = self.get_input_chars ()
if input_chars:
_candi = u''.join( ['###'] + map( str, input_chars) + ['###'] )
else:
_candi = u''
if self._strings:
res = u''
_cursor = self._cursor[0]
_luc = len (self._u_chars)
if _luc:
_candi = _candi == u'' and u'######' or _candi
res =u''.join( self._strings[ : _cursor - _luc] +[u'@@@'] + self._strings[_cursor - _luc : _cursor ] + [ _candi ] + self._strings[ _cursor : ])
else:
res = u''.join( self._strings[ : _cursor ] + [ _candi ] + self._strings[ _cursor : ])
return res
else:
return _candi
def add_caret (self, addstr):
'''add length to caret position'''
self._caret += len(addstr)
def get_caret (self):
'''Get caret position in preedit strings'''
self._caret = 0
if self._cursor[0] and self._strings:
map (self.add_caret,self._strings[:self._cursor[0]])
self._caret += self._cursor[1]
if self._candidates[0]:
if self._py_mode:
_p_index = 8
else:
_p_index = self.get_index ('phrase')
_candi =self._candidates[0][ int (self._lookup_table.get_cursor_pos() ) ][ _p_index ]
else:
_candi = u''.join( map( str,self.get_input_chars()) )
self._caret += len( _candi )
return self._caret
def arrow_left (self):
'''Process Arrow Left Key Event.
Update cursor data when move caret left'''
if self.get_preedit_strings ():
if not( self.get_input_chars () or self._u_chars ):
if self._cursor[1] > 0:
self._cursor[1] -= 1
else:
if self._cursor[0] > 0:
self._cursor[1] = len (self._strings[self._cursor[0]-1]) - 1
self._cursor[0] -= 1
else:
self._cursor[0] = len(self._strings)
self._cursor[1] = 0
self.update_candidates ()
return True
else:
return False
def arrow_right (self):
'''Process Arrow Right Key Event.
Update cursor data when move caret right'''
if self.get_preedit_strings ():
if not( self.get_input_chars () or self._u_chars ):
if self._cursor[1] == 0:
if self._cursor[0] == len (self._strings):
self._cursor[0] = 0
else:
self._cursor[1] += 1
else:
self._cursor[1] += 1
if self._cursor[1] == len(self._strings[ self._cursor[0] ]):
self._cursor[0] += 1
self._cursor[1] = 0
self.update_candidates ()
return True
else:
return False
def control_arrow_left (self):
'''Process Control + Arrow Left Key Event.
Update cursor data when move caret to string left'''
if self.get_preedit_strings ():
if not( self.get_input_chars () or self._u_chars ):
if self._cursor[1] == 0:
if self._cursor[0] == 0:
self._cursor[0] = len (self._strings) - 1
else:
self._cursor[0] -= 1
else:
self._cursor[1] = 0
self.update_candidates ()
return True
else:
return False
def control_arrow_right (self):
'''Process Control + Arrow Right Key Event.
Update cursor data when move caret to string right'''
if self.get_preedit_strings ():
if not( self.get_input_chars () or self._u_chars ):
if self._cursor[1] == 0:
if self._cursor[0] == len (self._strings):
self._cursor[0] = 1
else:
self._cursor[0] += 1
else:
self._cursor[0] += 1
self._cursor[1] = 0
self.update_candidates ()
return True
else:
return False
def ap_candidate (self, candi):
'''append candidate to lookup_table'''
if not self._py_mode:
_p_index = self.get_index('phrase')
_fkey = self.get_index('m0')
else:
_p_index = 8
_fkey = 1
if self.db._is_chinese:
_tbks = u''.join( map(self._deparser , candi[_fkey + len(self._tabkey_list) : _p_index-1 ] ) )
if self._py_mode:
# restore tune symbol
_tbks = _tbks.replace('!','↑1').replace('@','↑2').replace('#','↑3').replace('$','↑4').replace('%','↑5')
else:
_tbks = u''.join( map(self._deparser , candi[_fkey + len(self._tabkey_list) : _p_index ] ) )
_phrase = candi[_p_index]
# further color implementation needed :)
# here -2 is the pos of num, -1 is the pos of . 0 is the pos of string
#attrs = IBus.AttrList ([IBus.AttributeForeground (0x8e2626, -2, 1)])
attrs = IBus.AttrList ()
# this is the part of tabkey
attrs.append(IBus.attr_foreground_new(rgb(0x19,0x73,0xa2), 0, \
len(_phrase) + len(_tbks)))
if candi[-2] < 0:
# this is a user defined phrase:
attrs.append(IBus.attr_foreground_new(rgb(0x77,0x00,0xc3), 0, len(_phrase)))
elif candi[-1] > 0:
# this is a sys phrase used by user:
attrs.append(IBus.attr_foreground_new(rgb(0x00,0x00,0x00), 0, len(_phrase)))
else:
# this is a system phrase haven't been used:
attrs.append(IBus.attr_foreground_new(rgb(0x00,0x00,0x00), 0, len(_phrase)))
text = IBus.Text.new_from_string(_phrase + _tbks)
i = 0
while attrs.get(i) != None:
attr = attrs.get(i)
text.append_attribute(attr.get_attr_type(),
attr.get_value(),
attr.get_start_index(),
attr.get_end_index())
i += 1
self._lookup_table.append_candidate (text)
self._lookup_table.set_cursor_visible(True)
def filter_candidates (self, candidates):
'''Filter candidates if IME is Chinese'''
#if self.db._is_chinese and (not self._py_mode):
if not self._chinese_mode in(2,3):
return candidates[:]
bm_index = self._pt.index('category')
if self._chinese_mode == 2:
# All Chinese characters with simplified Chinese first
return filter (lambda x: x[bm_index] & 1, candidates)\
+filter (lambda x: x[bm_index] & (1 << 1) and \
(not x[bm_index] & 1), candidates)\
+ filter (lambda x: x[bm_index] & (1 << 2), candidates)
elif self._chinese_mode == 3:
# All Chinese characters with traditional Chinese first
return filter (lambda x: x[bm_index] & (1 << 1), candidates)\
+filter (lambda x: x[bm_index] & 1 and\
(not x[bm_index] & (1<<1)) , candidates)\
+ filter (lambda x: x[bm_index] & (1 << 2), candidates)
def update_candidates (self):
'''Update lookuptable'''
# first check whether the IME have defined start_chars
if self.db.startchars and ( len(self._chars[0]) == 1 )\
and ( len(self._chars[1]) == 0 ) \
and ( self._chars[0][0] not in self.db.startchars):
self._u_chars.append ( self._chars[0][0] )
self._strings.insert ( self._cursor[0], self._chars[0][0] )
self._cursor [0] += 1
self.clear_input()
else:
if (self._chars[0] == self._chars[2] and self._candidates[0]) \
or self._chars[1]:
# if no change in valid input char or we have invalid input,
# we do not do sql enquery
pass
else:
# check whether last time we have only one candidate
only_one_last = self.one_candidate()
# do enquiry
self._lookup_table.clear()
self._lookup_table.set_cursor_visible(True)
if self._tabkey_list:
# here we need to consider two parts, table and pinyin
# first table
if not self._py_mode:
if self.db._is_chinese :
bm_index = self._pt.index('category')
if self._chinese_mode == 0:
# simplify Chinese mode
self._candidates[0] = self.db.select_words(\
self._tabkey_list, self._onechar, 1 )
elif self._chinese_mode == 1:
# traditional Chinese mode
self._candidates[0] = self.db.select_words(\
self._tabkey_list, self._onechar, 2 )
else:
self._candidates[0] = self.db.select_words(\
self._tabkey_list, self._onechar )
else:
self._candidates[0] = self.db.select_words( self._tabkey_list, self._onechar )
else:
self._candidates[0] = self.db.select_zi( self._tabkey_list )
self._chars[2] = self._chars[0][:]
else:
self._candidates[0] =[]
if self._candidates[0]:
self._candidates[0] = self.filter_candidates (self._candidates[0])
if self._candidates[0]:
self.fill_lookup_table()
else:
if self._chars[0]:
## old manner:
#if self._candidates[1]:
# #print self._candidates[1]
# self._candidates[0] = self._candidates[1]
# self._candidates[1] = []
# last_input = self.pop_input ()
# self.auto_commit_to_preedit ()
# res = self.add_input( last_input )
# print res
# return res
#else:
# self.pop_input ()
# self._lookup_table.clear()
# self._lookup_table.set_cursor_visible(True)
# return False
###################
## new manner, we add new char to invalid input
## chars
if not self._chars[1]:
# we don't have invalid input chars
# here we need to check the last input
# is a punctuation or not, if is a punct,
# then we use old maner to summit the former valid
# candidate
if ascii.ispunct (self._chars[0][-1].encode('ascii')) \
or len (self._chars[0][:-1]) \
in self.db.pkeylens \
or only_one_last \
or self._auto_select:
# because we use [!@#$%] to denote [12345]
# in py_mode, so we need to distinguish them
## old manner:
if self._py_mode:
if self._chars[0][-1] in "!@#$%":
self._chars[0].pop()
self._tabkey_list.pop()
return True
if self._candidates[1]:
self._candidates[0] = self._candidates[1]
self._candidates[1] = []
last_input = self.pop_input ()
self.auto_commit_to_preedit ()
res = self.add_input( last_input )
return res
else:
self.pop_input ()
self._lookup_table.clear()
self._lookup_table.set_cursor_visible(True)
return False
else:
# this is not a punct or not a valid phrase
# last time
self._chars[1].append( self._chars[0].pop() )
self._tabkey_list.pop()
else:
pass
self._candidates[0] =[]
else:
self._lookup_table.clear()
self._lookup_table.set_cursor_visible(True)
self._candidates[1] = self._candidates[0]
return True
def commit_to_preedit (self):
'''Add select phrase in lookup table to preedit string'''
if not self._py_mode:
_p_index = self.get_index('phrase')
else:
_p_index = 8
try:
if self._candidates[0]:
self._strings.insert(self._cursor[0], self._candidates[0][ self.get_cursor_pos() ][_p_index])
self._cursor [0] += 1
if self._py_mode:
self._zi = self._candidates[0][ self.get_cursor_pos() ][_p_index]
self.over_input ()
self.update_candidates ()
except:
pass
def auto_commit_to_preedit (self):
'''Add select phrase in lookup table to preedit string'''
if not self._py_mode:
_p_index = self.get_index('phrase')
else:
_p_index = 8
try:
self._u_chars.append( self._chars[0][:] )
self._strings.insert(self._cursor[0], self._candidates[0][ self.get_cursor_pos() ][_p_index])
self._cursor [0] += 1
self.clear_input()
self.update_candidates ()
except:
pass
def get_aux_strings (self):
'''Get aux strings'''
input_chars = self.get_input_chars ()
if input_chars:
#aux_string = u' '.join( map( u''.join, self._u_chars + [self._chars[0]] ) )
aux_string = u''.join (self._chars[0])
if self._py_mode:
aux_string = aux_string.replace('!','1').replace('@','2').replace('#','3').replace('$','4').replace('%','5')
return aux_string
aux_string = u''
if self._zi:
# we have pinyin result
tabcodes = self.db.find_zi_code(self._zi)
aux_string = u' '.join(tabcodes)
# self._zi = u''
cstr = u''.join(self._strings)
if self.db.user_can_define_phrase:
if len (cstr ) > 1:
aux_string += (u'\t#: ' + self.db.parse_phrase_to_tabkeys (cstr))
return aux_string
def fill_lookup_table(self):
'''Fill more entries to self._lookup_table if needed.
If the cursor in _lookup_table moved beyond current length,
add more entries from _candidiate[0] to _lookup_table.'''
lookup = self._lookup_table
looklen = lookup.get_number_of_candidates()
psize = lookup.get_page_size()
if (lookup.get_cursor_pos() + psize >= looklen and
looklen < len(self._candidates[0])):
endpos = looklen + psize
batch = self._candidates[0][looklen:endpos]
map(self.ap_candidate, batch)
def arrow_down(self):
'''Process Arrow Down Key Event
Move Lookup Table cursor down'''
self.fill_lookup_table()
res = self._lookup_table.cursor_down()
self.update_candidates ()
if not res and self._candidates[0]:
return True
return res
def arrow_up(self):
'''Process Arrow Up Key Event
Move Lookup Table cursor up'''
res = self._lookup_table.cursor_up()
self.update_candidates ()
if not res and self._candidates[0]:
return True
return res
def page_down(self):
'''Process Page Down Key Event
Move Lookup Table page down'''
self.fill_lookup_table()
res = self._lookup_table.page_down()
self.update_candidates ()
if not res and self._candidates[0]:
return True
return res
def page_up(self):
'''Process Page Up Key Event
move Lookup Table page up'''
res = self._lookup_table.page_up()
self.update_candidates ()
if not res and self._candidates[0]:
return True
return res
def select_key(self, char):
'''
Commit a candidate in the lookup table which was selected
by typing a selection key
'''
try:
index = self._select_keys.index(char)
except ValueError:
return False
cursor_pos = self._lookup_table.get_cursor_pos()
cursor_in_page = self._lookup_table.get_cursor_in_page()
current_page_start = cursor_pos - cursor_in_page
real_index = current_page_start + index
if real_index >= len (self._candidates[0]):
# the index given is out of range we do not commit anything
return False
self._lookup_table.set_cursor_pos(real_index)
self.commit_to_preedit ()
return True
def alt_select_key(self, char):
'''Remove the candidates in Lookup Table from user_db index.'''
try:
index = self._select_keys.index(char)
except ValueError:
return False
cursor_pos = self._lookup_table.get_cursor_pos()
cursor_in_page = self._lookup_table.get_cursor_in_page()
current_page_start = cursor_pos - cursor_in_page
real_index = current_page_start + index
if len (self._candidates[0]) > real_index:
# this index is valid
can = self._candidates[0][real_index]
if can[-2] < 0:
# freq of this candidate is -1, means this a user phrase
self.db.remove_phrase (can)
# make update_candidates do sql enquiry
self._chars[2].pop()
self.update_candidates ()
return True
else:
return False
def get_cursor_pos (self):
'''get lookup table cursor position'''
return self._lookup_table.get_cursor_pos()
def get_lookup_table (self):
'''Get lookup table'''
return self._lookup_table
def is_lt_visible (self):
'''Check whether lookup table is visible'''
return self._lookup_table.is_cursor_visible ()
def backspace (self):
'''Process backspace Key Event'''
self._zi = u''
if self.get_input_chars():
self.pop_input ()
return True
elif self.get_preedit_strings ():
self.remove_before_char ()
return True
else:
return False
def control_backspace (self):
'''Process control+backspace Key Event'''
self._zi = u''
if self.get_input_chars():
self.over_input ()
return True
elif self.get_preedit_strings ():
self.remove_before_string ()
return True
else:
return False
def delete (self):
'''Process delete Key Event'''
self._zi = u''
if self.get_input_chars():
return True
elif self.get_preedit_strings ():
self.remove_after_char ()
return True
else:
return False
def control_delete (self):
'''Process control+delete Key Event'''
self._zi = u''
if self.get_input_chars ():
return True
elif self.get_preedit_strings ():
self.remove_after_string ()
return True
else:
return False
def l_shift (self):
'''Process Left Shift Key Event as immediately commit to preedit strings'''
if self._chars[0]:
self.commit_to_preedit ()
return True
else:
return False
def r_shift (self):
'''Proess Right Shift Key Event as changed between PinYin Mode and Table Mode'''
self._zi = u''
if self._chars[0]:
self.commit_to_preedit ()
self._py_mode = not (self._py_mode)
return True
def l_alt(self):
"""Left Alt key, cycle cursor to next candidate in the page."""
total = len(self._candidates[0])
if total > 0:
lookup = self._lookup_table
page_size = lookup.get_page_size()
pos = lookup.get_cursor_pos()
page = int(pos/page_size)
pos += 1
if pos >= (page+1)*page_size or pos >= total:
pos = page*page_size
res = lookup.set_cursor_pos(pos)
return True
else:
return False
def space (self):
'''Process space Key Event
return (KeyProcessResult,whethercommit,commitstring)'''
if self._chars[1]:
# we have invalid input, so do not commit
return (False,u'')
if self._t_chars :
# user has input sth
istr = self.get_all_input_strings ()
self.commit_to_preedit ()
pstr = self.get_preedit_strings ()
#print "istr: ",istr
self.clear()
return (True,pstr,istr)
else:
return (False,u'',u'')
def one_candidate (self):
'''Return true if there is only one candidate'''
return len(self._candidates[0]) == 1
########################
### Engine Class #####
####################
class tabengine (IBus.Engine):
'''The IM Engine for Tables'''
# colors
# _phrase_color = 0xffffff
# _user_phrase_color = 0xffffff
# _new_phrase_color = 0xffffff
def __init__ (self, bus, obj_path, db ):
super(tabengine,self).__init__ (connection=bus.get_connection(),
object_path=obj_path)
self._bus = bus
# this is the backend sql db we need for our IME
# we receive this db from IMEngineFactory
#self.db = tabsqlitedb.tabsqlitedb( name = dbname )
self.db = db
# this is the parer which parse the input string to key object
self._parser = tabdict.parse
self._icon_dir = '%s%s%s%s' % (os.getenv('IBUS_TABLE_LOCATION'),
os.path.sep, 'icons', os.path.sep)
# 0 = english input mode
# 1 = table input mode
self._mode = 1
# self._ime_py: True / False this IME support pinyin mode
self._ime_py = self.db.get_ime_property ('pinyin_mode')
if self._ime_py:
if self._ime_py.lower() == u'true':
self._ime_py = True
else:
self._ime_py = False
else:
print 'We could not find "pinyin_mode" entry in database, is it an outdated database?'
self._ime_py = False
self._status = self.db.get_ime_property('status_prompt').encode('utf8')
# now we check and update the valid input characters
self._chars = self.db.get_ime_property('valid_input_chars')
self._valid_input_chars = []
for _c in self._chars:
if _c in tabdict.tab_key_list:
self._valid_input_chars.append(_c)
del self._chars
# check whether we can use '=' and '-' for page_down/up
self._page_down_keys = [IBus.KEY_Page_Down, IBus.KEY_KP_Page_Down]
self._page_up_keys = [IBus.KEY_Page_Up, IBus.KEY_KP_Page_Up]
if '=' not in self._valid_input_chars \
and '-' not in self._valid_input_chars:
self._page_down_keys.append (IBus.KEY_equal)
self._page_up_keys.append (IBus.KEY_minus)
pageup_prop = self.db.get_ime_property('page_up_keys')
pagedown_prop = self.db.get_ime_property('page_down_keys')
if pageup_prop is not None:
self._page_up_keys = [IBus.keyval_from_name(x) for x in
pageup_prop.split(",")]
if pagedown_prop is not None:
self._page_down_keys = [IBus.keyval_from_name(x) for x in
pagedown_prop.split(",")]
self._pt = self.db.get_phrase_table_index ()
self._ml = int(self.db.get_ime_property ('max_key_length'))
# name for config section
self._name = self.db.get_ime_property('name')
self._config_section = "engine/Table/%s" % self._name.replace(' ', '_')
# config module
self._config = self._bus.get_config ()
self._config.connect ("value-changed", self.config_value_changed_cb)
# Containers we used:
self._editor = editor(self._config, self._pt, self._valid_input_chars, self._ml, self.db)
# some other vals we used:
# self._prev_key: hold the key event last time.
self._prev_key = None
self._prev_char = None
self._double_quotation_state = False
self._single_quotation_state = False
# [EnMode,TabMode] we get TabMode properties from db
self._full_width_letter = [
variant_to_value(self._config.get_value(
self._config_section,
"EnDefFullWidthLetter")),
variant_to_value(self._config.get_value(
self._config_section,
"TabDefFullWidthLetter"))
]
if self._full_width_letter[0] == None:
self._full_width_letter[0] = False
if self._full_width_letter[1] == None:
self._full_width_letter[1] = self.db.get_ime_property('def_full_width_letter').lower() == u'true'
self._full_width_punct = [
variant_to_value(self._config.get_value(
self._config_section,
"EnDefFullWidthPunct")),
variant_to_value(self._config.get_value(
self._config_section,
"TabDefFullWidthPunct"))
]
if self._full_width_punct[0] == None:
self._full_width_punct[0] = False
if self._full_width_punct[1] == None:
self._full_width_punct[1] = self.db.get_ime_property('def_full_width_punct').lower() == u'true'
# some properties we will involved, Property is taken from scim.
#self._setup_property = Property ("setup", _("Setup"))
self._auto_commit = variant_to_value(self._config.get_value(
self._config_section,
"AutoCommit"))
if self._auto_commit == None:
self._auto_commit = self.db.get_ime_property('auto_commit').lower() == u'true'
self._auto_select = variant_to_value(self._config.get_value(
self._config_section,
"AutoSelect"))
if self._auto_select == None:
if self.db.get_ime_property('auto_select') != None:
self._auto_select = self.db.get_ime_property('auto_select').lower() == u'true'
else:
self._auto_select = False
# the commit phrases length
self._len_list = [0]
# connect to SpeedMeter
#try:
# bus = dbus.SessionBus()
# user = os.path.basename( os.path.expanduser('~') )
# self._sm_bus = bus.get_object ("org.ibus.table.SpeedMeter.%s"\
# % user, "/org/ibus/table/SpeedMeter")
# self._sm = dbus.Interface(self._sm_bus,\
# "org.ibus.table.SpeedMeter")
#except:
# self._sm = None
#self._sm_on = False
self._on = False
self.reset ()
def reset (self):
self._editor.clear ()
self._double_quotation_state = False
self._single_quotation_state = False
self._prev_key = None
#self._editor._onechar = False
self._init_properties ()
self._update_ui ()
def do_destroy(self):
self.reset ()
self.do_focus_out ()
#self.db.sync_usrdb ()
super(tabengine,self).destroy()
def _init_properties (self):
self.properties= IBus.PropList ()
self._status_property = IBus.Property(key=u'status',
label=None,
icon=None,
tooltip=None,
sensitive=True,
visible=True)
self.properties.append(self._status_property)
if self.db._is_chinese:
self._cmode_property = IBus.Property(key=u'cmode',
label=None,
icon=None,
tooltip=None,
sensitive=True,
visible=True)
self.properties.append(self._cmode_property)
self._letter_property = IBus.Property(key=u'letter',
label=None,
icon=None,
tooltip=None,
sensitive=True,
visible=True)
self.properties.append(self._letter_property)
self._punct_property = IBus.Property(key=u'punct',
label=None,
icon=None,
tooltip=None,
sensitive=True,
visible=True)
self.properties.append(self._punct_property)
self._py_property = IBus.Property(key=u'py_mode',
label=None,
icon=None,
tooltip=None,
sensitive=True,
visible=True)
self.properties.append(self._py_property)
self._onechar_property = IBus.Property(key=u'onechar',
label=None,
icon=None,
tooltip=None,
sensitive=True,
visible=True)
self.properties.append(self._onechar_property)
self._auto_commit_property = IBus.Property(key=u'acommit',
label=None,
icon=None,
tooltip=None,
sensitive=True,
visible=True)
self.properties.append(self._auto_commit_property)
self.register_properties (self.properties)
self._refresh_properties ()
def _refresh_properties (self):
'''Method used to update properties'''
# taken and modified from PinYin.py :)
if self._mode == 1: # refresh mode
if self._status == u'CN':
self._set_property(self._status_property, 'chinese.svg', _('Chinese Mode'), _('Switch to English mode'))
else:
self._set_property(self._status_property, 'ibus-table.svg', self._status, _('Switch to English mode'))
else:
self._set_property(self._status_property, 'english.svg', _('English Mode'), _('Switch to Table mode'))
self.update_property(self._status_property)
if self._full_width_letter[self._mode]:
self._set_property(self._letter_property, 'full-letter.svg', _('Full Letter'), _('Switch to half-width letter'))
else:
self._set_property(self._letter_property, 'half-letter.svg', _('Half Letter'), _('Switch to full-width letter'))
self.update_property(self._letter_property)
if self._full_width_punct[self._mode]:
self._set_property(self._punct_property, 'full-punct.svg', _('Full-width Punctuation'), _('Switch to half-width punctuation'))
else:
self._set_property(self._punct_property, 'half-punct.svg', _('Half-width Punctuation'), _('Switch to full-width punctuation'))
self.update_property(self._punct_property)
if self._editor._py_mode:
self._set_property(self._py_property, 'py-mode.svg', _('PinYin Mode'), _('Switch to Table mode'))
else:
self._set_property(self._py_property, 'tab-mode.svg', _('Table Mode'), _('Switch to PinYin mode'))
self.update_property(self._py_property)
if self._editor._onechar:
self._set_property(self._onechar_property, 'onechar.svg', _('Single Char Mode'), _('Switch to phrase mode'))
else:
self._set_property(self._onechar_property, 'phrase.svg', _('Phrase Mode'), _('Switch to single char mode'))
self.update_property(self._onechar_property)
if self._auto_commit:
self._set_property(self._auto_commit_property, 'acommit.svg', _('Direct Commit Mode'), _('Switch to normal commit mode, which use space to commit'))
else:
self._set_property(self._auto_commit_property, 'ncommit.svg', _('Normal Commit Mode'), _('Switch to direct commit mode'))
self.update_property(self._auto_commit_property)
# the chinese_mode:
if self.db._is_chinese:
if self._editor._chinese_mode == 0:
self._set_property(self._cmode_property, 'sc-mode.svg', _('Simplified Chinese Mode'), _('Switch to Traditional Chinese mode'))
elif self._editor._chinese_mode == 1:
self._set_property(self._cmode_property, 'tc-mode.svg', _('Traditional Chinese Mode'), _('Switch to Simplify Chinese first Big Charset Mode'))
elif self._editor._chinese_mode == 2:
self._set_property(self._cmode_property, 'scb-mode.svg', _('Simplified Chinese First Big Charset Mode'), _('Switch to Traditional Chinese first Big Charset Mode'))
elif self._editor._chinese_mode == 3:
self._set_property(self._cmode_property, 'tcb-mode.svg', _('Traditional Chinese First Big Charset Mode'), _('Switch to Big Charset Mode'))
elif self._editor._chinese_mode == 4:
self._set_property(self._cmode_property, 'cb-mode.svg', _('Big Chinese Mode'), _('Switch to Simplified Chinese Mode'))
self.update_property(self._cmode_property)
def _set_property (self, property, icon, label, tooltip):
property.set_icon ( u'%s%s' % (self._icon_dir, icon ) )
property.set_label (IBus.Text.new_from_string(unicode(label)))
property.set_tooltip (IBus.Text.new_from_string(unicode(tooltip)))
def _change_mode (self):
'''Shift input mode, TAB -> EN -> TAB
'''
self._mode = int (not self._mode)
self.reset ()
self._update_ui ()
def do_property_activate (self, property, prop_state = IBus.PropState.UNCHECKED):
'''Shift property'''
if property == u"status":
self._change_mode ()
elif property == u'py_mode' and self._ime_py:
self._editor.r_shift ()
elif property == u'onechar':
self._editor._onechar = not self._editor._onechar
self._config.set_value(self._config_section,
"OneChar",
GLib.Variant.new_boolean(self._editor._onechar))
elif property == u'acommit':
self._auto_commit = not self._auto_commit
self._config.set_value( self._config_section,
"AutoCommit",
GLib.Variant.new_boolean(self._auto_commit))
elif property == u'letter':
self._full_width_letter [self._mode] = not self._full_width_letter [self._mode]
if self._mode:
self._config.set_value(self._config_section,
"TabDefFullWidthLetter",
GLib.Variant.new_boolean(self._full_width_letter [self._mode]))
else:
self._config.set_value(self._config_section,
"EnDefFullWidthLetter",
GLib.Variant.new_boolean(self._full_width_letter [self._mode]))
elif property == u'punct':
self._full_width_punct [self._mode] = not self._full_width_punct [self._mode]
if self._mode:
self._config.set_value(self._config_section,
"TabDefFullWidthPunct",
GLib.Variant.new_boolean(self._full_width_punct [self._mode]))
else:
self._config.set_value(self._config_section,
"EnDefFullWidthPunct",
GLib.Variant.new_boolean(self._full_width_punct [self._mode]))
elif property == u'cmode':
self._editor.change_chinese_mode()
self.reset()
self._refresh_properties ()
# elif property == "setup":
# Need implementation
# self.start_helper ("96c07b6f-0c3d-4403-ab57-908dd9b8d513")
# at last invoke default method
def _update_preedit (self):
'''Update Preedit String in UI'''
_str = self._editor.get_preedit_strings ()
if _str == u'':
super(tabengine, self).update_preedit_text(IBus.Text.new_from_string(u''), 0, False)
else:
attrs = IBus.AttrList()
res = patt_edit.match (_str)
if res:
_str = u''
ures = patt_uncommit.match (res.group(1))
if ures:
_str=u''.join (ures.groups())
lc = len (ures.group(1) )
lu = len (ures.group(2) )
attrs.append(IBus.attr_foreground_new(rgb(0x1b,0x3f,0x03),0,lc))
attrs.append(IBus.attr_foreground_new(rgb(0x08,0x95,0xa2),lc,lu))
lg1 = len (_str)
else:
_str += res.group (1)
lg1 = len ( res.group(1) )
attrs.append(IBus.attr_foreground_new(rgb(0x1b,0x3f,0x03),0,lg1))
_str += res.group(2)
_str += res.group(3)
lg2 = len ( res.group(2) )
lg3 = len ( res.group(3) )
attrs.append(IBus.attr_foreground_new(rgb(0x0e,0x0e,0xa0),lg1,lg2))
attrs.append(IBus.attr_foreground_new(rgb(0x1b,0x3f,0x03),lg1+lg2,lg3))
else:
attrs.append(IBus.attr_foreground_new(rgb(0x1b,0x3f,0x03),0,len(_str)))
# because ibus now can only insert preedit into txt, so...
attrs = IBus.AttrList()
attrs.append(IBus.attr_underline_new(IBus.AttrUnderline.SINGLE, 0, len(_str)))
text = IBus.Text.new_from_string(_str)
i = 0
while attrs.get(i) != None:
attr = attrs.get(i)
text.append_attribute(attr.get_attr_type(),
attr.get_value(),
attr.get_start_index(),
attr.get_end_index())
i += 1
super(tabengine, self).update_preedit_text(text, self._editor.get_caret(), True)
def _update_aux (self):
'''Update Aux String in UI'''
_ic = self._editor.get_aux_strings ()
if _ic:
attrs = IBus.AttrList()
attrs.append(IBus.attr_foreground_new(rgb(0x95,0x15,0xb5),0, len(_ic)))
text = IBus.Text.new_from_string(_ic)
i = 0
while attrs.get(i) != None:
attr = attrs.get(i)
text.append_attribute(attr.get_attr_type(),
attr.get_value(),
attr.get_start_index(),
attr.get_end_index())
i += 1
super(tabengine, self).update_auxiliary_text(text, True)
else:
self.hide_auxiliary_text()
#self.update_aux_string (u'', None, False)
def _update_lookup_table (self):
'''Update Lookup Table in UI'''
if self._editor.is_empty ():
self.hide_lookup_table()
return
self.update_lookup_table(self._editor.get_lookup_table(), True)
def _update_ui (self):
'''Update User Interface'''
self._update_lookup_table ()
self._update_preedit ()
self._update_aux ()
#def add_string_len(self, astring):
# if self._sm_on:
# try:
# self._sm.Accumulate(len(astring))
# except:
# pass
def commit_string (self,string):
self._editor.clear ()
self._update_ui ()
super(tabengine,self).commit_text(IBus.Text.new_from_string(string))
self._prev_char = string[-1]
def _convert_to_full_width (self, c):
'''convert half width character to full width'''
if c in [u".", u"\\", u"^", u"_", u"$", u"\"", u"'", u">", u"<", u"[", u"]", u"{", u"}" ]:
if c == u".":
if self._prev_char and self._prev_char.isdigit () \
and self._prev_key and chr (self._prev_key.code) == self._prev_char:
return u"."
else:
return u"\u3002"
elif c == u"\\":
return u"\u3001"
elif c == u"^":
return u"\u2026\u2026"
elif c == u"_":
return u"\u2014\u2014"
elif c == u"$":
return u"\uffe5"
elif c == u"\"":
self._double_quotation_state = not self._double_quotation_state
if self._double_quotation_state:
return u"\u201c"
else:
return u"\u201d"
elif c == u"'":
self._single_quotation_state = not self._single_quotation_state
if self._single_quotation_state:
return u"\u2018"
else:
return u"\u2019"
elif c == u"<":
if self._mode:
return u"\u300a"
elif c == u">":
if self._mode:
return u"\u300b"
elif c == u"[":
if self._mode:
return u"\u300c"
elif c == u"]":
if self._mode:
return u"\u300d"
elif c == u"{":
if self._mode:
return u"\u300e"
elif c == u"}":
if self._mode:
return u"\u300f"
return unichar_half_to_full (c)
def _match_hotkey (self, key, code, mask):
if key.code == code and key.mask == mask:
if self._prev_key and key.code == self._prev_key.code and key.mask & IBus.ModifierType.RELEASE_MASK:
return True
if not key.mask & IBus.ModifierType.RELEASE_MASK:
return True
return False
def do_process_key_event(self, keyval, keycode, state):
'''Process Key Events
Key Events include Key Press and Key Release,
modifier means Key Pressed
'''
key = KeyEvent(keyval, state & IBus.ModifierType.RELEASE_MASK == 0, state)
# ignore NumLock mask
key.mask &= ~IBus.ModifierType.MOD2_MASK
result = self._process_key_event (key)
self._prev_key = key
return result
def _process_key_event (self, key):
'''Internal method to process key event'''
# Match mode switch hotkey
if not self._editor._t_chars and ( self._match_hotkey (key, IBus.KEY_Shift_L, IBus.ModifierType.SHIFT_MASK | IBus.ModifierType.RELEASE_MASK)):
self._change_mode ()
return True
# Match full half letter mode switch hotkey
if self._match_hotkey (key, IBus.KEY_space, IBus.ModifierType.SHIFT_MASK):
self.do_property_activate ("letter")
return True
# Match full half punct mode switch hotkey
if self._match_hotkey (key, IBus.KEY_period, IBus.ModifierType.CONTROL_MASK):
self.do_property_activate ("punct")
return True
# we ignore all hotkeys
# if key.mask & IBus.ModifierType.MOD1_MASK:
# return False
# Ignore key release event
# if key.mask & IBus.ModifierType.RELEASE_MASK:
# return True
if self._mode:
return self._table_mode_process_key_event (key)
else:
return self._english_mode_process_key_event (key)
def _english_mode_process_key_event (self, key):
'''English Mode Process Key Event'''
# Ignore key release event
if key.mask & IBus.ModifierType.RELEASE_MASK:
return True
if key.code >= 128:
return False
# we ignore all hotkeys here
if key.mask & (IBus.ModifierType.CONTROL_MASK|IBus.ModifierType.MOD1_MASK):
return False
keychar = unichr (key.code)
if ascii.ispunct (key.code): # if key code is a punctation
if self._full_width_punct[self._mode]:
self.commit_string (self._convert_to_full_width (keychar))
return True
else:
self.commit_string (keychar)
return True
# then, the key code is a letter or digit
if self._full_width_letter[self._mode]:
# in full width letter mode
self.commit_string (self._convert_to_full_width (keychar))
return True
else:
return False
# should not reach there
return False
def _table_mode_process_key_event (self, key):
'''Xingma Mode Process Key Event'''
cond_letter_translate = lambda (c): \
self._convert_to_full_width (c) if self._full_width_letter [self._mode] else c
cond_punct_translate = lambda (c): \
self._convert_to_full_width (c) if self._full_width_punct [self._mode] else c
# We have to process the pinyin mode change key event here,
# because we ignore all Release event below.
if self._match_hotkey (key, IBus.KEY_Shift_R, IBus.ModifierType.SHIFT_MASK | IBus.ModifierType.RELEASE_MASK) and self._ime_py:
res = self._editor.r_shift ()
self._refresh_properties ()
self._update_ui ()
return res
# process commit to preedit
if self._match_hotkey (key, IBus.KEY_Shift_R, IBus.ModifierType.SHIFT_MASK | IBus.ModifierType.RELEASE_MASK) or self._match_hotkey (key, IBus.KEY_Shift_L, IBus.ModifierType.SHIFT_MASK | IBus.ModifierType.RELEASE_MASK):
res = self._editor.l_shift ()
self._update_ui ()
return res
# Left ALT key to cycle candidates in the current page.
if self._match_hotkey (key, IBus.KEY_Alt_L, IBus.ModifierType.MOD1_MASK | IBus.ModifierType.RELEASE_MASK):
res = self._editor.l_alt ()
self._update_ui ()
return res
# Match single char mode switch hotkey
if self._match_hotkey (key, IBus.KEY_comma, IBus.ModifierType.CONTROL_MASK):
self.do_property_activate ( u"onechar" )
return True
# Match direct commit mode switch hotkey
if self._match_hotkey (key, IBus.KEY_slash, IBus.ModifierType.CONTROL_MASK):
self.do_property_activate ( u"acommit" )
return True
# Match Chinese mode shift
if self._match_hotkey (key, IBus.KEY_semicolon, IBus.ModifierType.CONTROL_MASK):
self.do_property_activate ( u"cmode" )
return True
# Match speedmeter shift
#if self._match_hotkey (key, IBus.KEY_apostrophe, IBus.ModifierType.CONTROL_MASK):
# self._sm_on = not self._sm_on
# if self._sm_on:
# self._sm.Show ()
# else:
# self._sm.Hide ()
# return True
# Ignore key release event now :)
if key.mask & IBus.ModifierType.RELEASE_MASK:
return True
#
keychar = unichr (key.code)
if self._editor.is_empty ():
# we have not input anything
if key.code >= 32 and key.code <= 127 and ( keychar not in self._valid_input_chars ) \
and (not (key.mask & (IBus.ModifierType.MOD1_MASK | IBus.ModifierType.CONTROL_MASK))):
if key.code == IBus.KEY_space:
#self.commit_string (cond_letter_translate (keychar))
# little hack to make ibus to input space in gvim :)
if self._full_width_letter [self._mode]:
self.commit_string (cond_letter_translate (keychar))
return True
else:
return False
if ascii.ispunct (key.code):
self.commit_string (cond_punct_translate (keychar))
return True
if ascii.isdigit (key.code):
self.commit_string (cond_letter_translate (keychar))
return True
elif (key.code < 32 or key.code > 127) and ( keychar not in self._valid_input_chars ) \
and(not self._editor._py_mode):
return False
if key.code == IBus.KEY_Escape:
self.reset ()
self._update_ui ()
return True
elif key.code in (IBus.KEY_Return, IBus.KEY_KP_Enter):
commit_string = self._editor.get_all_input_strings ()
self.commit_string (commit_string)
return True
elif key.code in (IBus.KEY_Down, IBus.KEY_KP_Down) :
res = self._editor.arrow_down ()
self._update_ui ()
return res
elif key.code in (IBus.KEY_Up, IBus.KEY_KP_Up):
res = self._editor.arrow_up ()
self._update_ui ()
return res
elif key.code in (IBus.KEY_Left, IBus.KEY_KP_Left) and key.mask & IBus.ModifierType.CONTROL_MASK:
res = self._editor.control_arrow_left ()
self._update_ui ()
return res
elif key.code in (IBus.KEY_Right, IBus.KEY_KP_Right) and key.mask & IBus.ModifierType.CONTROL_MASK:
res = self._editor.control_arrow_right ()
self._update_ui ()
return res
elif key.code in (IBus.KEY_Left, IBus.KEY_KP_Left):
res = self._editor.arrow_left ()
self._update_ui ()
return res
elif key.code in (IBus.KEY_Right, IBus.KEY_KP_Right):
res = self._editor.arrow_right ()
self._update_ui ()
return res
elif key.code == IBus.KEY_BackSpace and key.mask & IBus.ModifierType.CONTROL_MASK:
res = self._editor.control_backspace ()
self._update_ui ()
return res
elif key.code == IBus.KEY_BackSpace:
res = self._editor.backspace ()
self._update_ui ()
return res
elif key.code == IBus.KEY_Delete and key.mask & IBus.ModifierType.CONTROL_MASK:
res = self._editor.control_delete ()
self._update_ui ()
return res
elif key.code == IBus.KEY_Delete:
res = self._editor.delete ()
self._update_ui ()
return res
elif ( keychar in self._editor.get_select_keys() and
self._editor._candidates[0] and
key.mask & IBus.ModifierType.CONTROL_MASK ):
res = self._editor.select_key (keychar)
self._update_ui ()
return res
elif ( keychar in self._editor.get_select_keys() and
self._editor._candidates[0] and
key.mask & IBus.ModifierType.MOD1_MASK ):
res = self._editor.alt_select_key (keychar)
self._update_ui ()
return res
elif key.code == IBus.KEY_space:
# if space is one of "page_down_keys" change to next page
# on lookup page
if IBus.KEY_space in self._page_down_keys:
res = self._editor.page_down()
self._update_ui ()
return res
else:
o_py = self._editor._py_mode
sp_res = self._editor.space ()
#return (KeyProcessResult,whethercommit,commitstring)
if sp_res[0]:
if self._auto_select:
self.commit_string ("%s " %sp_res[1])
else:
self.commit_string (sp_res[1])
#self.add_string_len(sp_res[1])
self.db.check_phrase (sp_res[1], sp_res[2])
else:
if sp_res[1] == u' ':
self.commit_string (cond_letter_translate (u" "))
if o_py != self._editor._py_mode:
self._refresh_properties ()
self._update_ui ()
return True
# now we ignore all else hotkeys
elif key.mask & (IBus.ModifierType.CONTROL_MASK|IBus.ModifierType.MOD1_MASK):
return False
elif key.mask & IBus.ModifierType.MOD1_MASK:
return False
elif keychar in self._valid_input_chars or \
( self._editor._py_mode and \
keychar in u'abcdefghijklmnopqrstuvwxyz!@#$%' ):
if self._auto_commit and ( len(self._editor._chars[0]) == self._ml \
or len (self._editor._chars[0]) in self.db.pkeylens )\
and not self._editor._py_mode:
# it is time to direct commit
sp_res = self._editor.space ()
#return (whethercommit,commitstring)
if sp_res[0]:
self.commit_string (sp_res[1])
#self.add_string_len(sp_res[1])
self.db.check_phrase (sp_res[1],sp_res[2])
res = self._editor.add_input ( keychar )
if not res:
if ascii.ispunct (key.code):
key_char = cond_punct_translate (keychar)
else:
key_char = cond_letter_translate (keychar)
sp_res = self._editor.space ()
#return (KeyProcessResult,whethercommit,commitstring)
if sp_res[0]:
self.commit_string (sp_res[1] + key_char)
#self.add_string_len(sp_res[1])
self.db.check_phrase (sp_res[1],sp_res[2])
return True
else:
self.commit_string ( key_char )
return True
else:
if self._auto_commit and self._editor.one_candidate () and \
(len(self._editor._chars[0]) == self._ml \
or not self.db._is_chinese):
# it is time to direct commit
sp_res = self._editor.space ()
#return (whethercommit,commitstring)
if sp_res[0]:
self.commit_string (sp_res[1])
#self.add_string_len(sp_res[1])
self.db.check_phrase (sp_res[1], sp_res[2])
return True
self._update_ui ()
return True
elif key.code in self._page_down_keys \
and self._editor._candidates[0]:
res = self._editor.page_down()
self._update_ui ()
return res
elif key.code in self._page_up_keys \
and self._editor._candidates[0]:
res = self._editor.page_up ()
self._update_ui ()
return res
elif keychar in self._editor.get_select_keys() and self._editor._candidates[0]:
input_keys = self._editor.get_all_input_strings ()
res = self._editor.select_key (keychar)
if res:
o_py = self._editor._py_mode
commit_string = self._editor.get_preedit_strings ()
self.commit_string (commit_string)
#self.add_string_len(commit_string)
if o_py != self._editor._py_mode:
self._refresh_properties ()
self._update_ui ()
# modify freq info
self.db.check_phrase (commit_string, input_keys)
return True
elif key.code <= 127:
if not self._editor._candidates[0]:
commit_string = self._editor.get_all_input_strings ()
else:
self._editor.commit_to_preedit ()
commit_string = self._editor.get_preedit_strings ()
# we need to take care of the py_mode here :)
py_mode = self._editor._py_mode
self._editor.clear ()
if py_mode:
self._refresh_properties ()
if ascii.ispunct (key.code):
self.commit_string ( commit_string + cond_punct_translate(keychar))
else:
self.commit_string ( commit_string + cond_letter_translate(keychar))
return True
return False
# below for initial test
def do_focus_in (self):
if self._on:
self.register_properties (self.properties)
self._refresh_properties ()
self._update_ui ()
#try:
# if self._sm_on:
# self._sm.Show ()
# else:
# self._sm.Hide ()
#except:
# pass
def do_focus_out (self):
#try:
# self._sm.Hide()
#except:
# pass
pass
def do_enable (self):
#try:
# self._sm.Reset()
#except:
# pass
self._on = True
self.do_focus_in()
def do_disable (self):
self.reset()
#try:
# self._sm.Hide()
#except:
# pass
self._on = False
def do_page_up (self):
if self._editor.page_up ():
self._update_ui ()
return True
return False
def do_page_down (self):
if self._editor.page_down ():
self._update_ui ()
return True
return False
def config_section_normalize(self, section):
# This function replaces _: with - in the dconf
# section and converts to lower case to make
# the comparison of the dconf sections work correctly.
# I avoid using .lower() here because it is locale dependent,
# when using .lower() this would not achieve the desired
# effect of comparing the dconf sections case insentively
# in some locales, it would fail for example if Turkish
# locale (tr_TR.UTF-8) is set.
if type(section) == type(u''):
# translate() does not work in Python’s internal Unicode type
section = section.encode('utf-8')
return re.sub(r'[_:]', r'-', section).translate(
string.maketrans(string.ascii_uppercase, string.ascii_lowercase ))
def config_value_changed_cb (self, config, section, name, value):
if self.config_section_normalize(self._config_section) != self.config_section_normalize(section):
return
print "config value %(n)s for engine %(en)s changed" %{'n': name, 'en': self._name}
value = variant_to_value(value)
if name == u'autocommit':
self._auto_commit = value
self._refresh_properties()
return
elif name == u'chinesemode':
self._editor._chinese_mode = value
self._refresh_properties()
return
elif name == u'endeffullwidthletter':
self._full_width_letter[0] = value
self._refresh_properties()
return
elif name == u'endeffullwidthpunct':
self._full_width_punct[0] = value
self._refresh_properties()
return
elif name == u'lookuptableorientation':
self._editor._lookup_table.set_orientation (value)
return
elif name == u'lookuptableselectkeys':
self._editor.set_select_keys (value)
return
elif name == u'onechar':
self._editor._onechar = value
self._refresh_properties()
return
elif name == u'tabdeffullwidthletter':
self._full_width_letter[1] = value
self._refresh_properties()
return
elif name == u'tabdeffullwidthpunct':
self._full_width_punct[1] = value
self._refresh_properties()
return
# for further implementation :)
@classmethod
def CONFIG_VALUE_CHANGED(cls, bus, section, name, value):
config = bus.get_config()
if section != self._config_section:
return
@classmethod
def CONFIG_RELOADED(cls, bus):
config = bus.get_config()
if section != self._config_section:
return