cf8f78a31ee9dc09bd5bdd81df2bff02b381d27c
[platform/upstream/ibus-hangul.git] / engine / engine.py
1 # vim:set et sts=4 sw=4:
2 # -*- coding: utf-8 -*-
3 #
4 # ibus-anthy - The Anthy engine for IBus
5 #
6 # Copyright (c) 2007-2008 Huang Peng <shawn.p.huang@gmail.com>
7 #
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2, or (at your option)
11 # any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21
22 import gobject
23 import ibus
24 import anthy
25 from tables import *
26 from ibus import keysyms
27
28 MODE_HIRAGANA, \
29 MODE_KATAKANA, \
30 MODE_HALF_WIDTH_KATAKANA, \
31 MODE_LATIN, \
32 MODE_WIDE_LATIN = range(0, 5)
33
34 _ = lambda a: a
35
36 class Engine(ibus.EngineBase):
37     def __init__(self, bus, object_path):
38         super(Engine, self).__init__(bus, object_path)
39
40         # create anthy context
41         self.__context = anthy.anthy_context()
42         self.__context._set_encoding(anthy.ANTHY_UTF8_ENCODING)
43
44         # init state
45         self.__input_mode = MODE_HIRAGANA
46         self.__prop_dict = {}
47
48         self.__lookup_table = ibus.LookupTable()
49         self.__prop_list = self.__init_props()
50
51         # use reset to init values
52         self.__reset()
53
54     def __init_props(self):
55         props = ibus.PropList()
56
57         # init input mode properties
58         mode_prop = ibus.Property(name = "InputMode",
59                             type = ibus.PROP_TYPE_MENU,
60                             label = u"あ",
61                             tooltip = "Switch input mode")
62         self.__prop_dict["InputMode"] = mode_prop
63
64         mode_props = ibus.PropList()
65         mode_props.append(ibus.Property(name = "InputMode.Hiragana",
66                                         type = ibus.PROP_TYPE_RADIO,
67                                         label = "Hiragana"))
68         mode_props.append(ibus.Property(name = "InputMode.Katakana",
69                                         type = ibus.PROP_TYPE_RADIO,
70                                         label = "Katakana"))
71         mode_props.append(ibus.Property(name = "InputMode.HalfWidthKatakana",
72                                         type = ibus.PROP_TYPE_RADIO,
73                                         label = "Half width katakana"))
74         mode_props.append(ibus.Property(name = "InputMode.Latin",
75                                         type = ibus.PROP_TYPE_RADIO,
76                                         label = "Latin"))
77         mode_props.append(ibus.Property(name = "InputMode.WideLatin",
78                                         type = ibus.PROP_TYPE_RADIO,
79                                         label = "Wide Latin"))
80
81         mode_props[self.__input_mode].set_state(ibus.PROP_STATE_CHECKED)
82
83         for prop in mode_props:
84             self.__prop_dict[prop.get_name()] = prop
85
86         mode_prop.set_sub_props(mode_props)
87         props.append(mode_prop)
88
89
90         # init test property
91         test_prop = ibus.Property(name = "TestProp",
92                             type = ibus.PROP_TYPE_TOGGLE,
93                             label = u"あ",
94                             tooltip = "test property")
95         self.__prop_dict["TestProp"] = test_prop
96         props.append(test_prop)
97
98
99         return props
100
101     # reset values of engine
102     def __reset(self):
103         self.__input_chars = u""
104         self.__convert_chars = u""
105         self.__cursor_pos = 0
106         self.__need_update = False
107         self.__convert_begined = False
108         self.__segments = list()
109         self.__lookup_table.clean()
110         self.__lookup_table_visible = False
111
112     def page_up(self):
113         # only process cursor down in convert mode
114         if not self.__convert_begined:
115             return False
116
117         if not self.__lookup_table.page_up():
118             return False
119
120         candidate = self.__lookup_table.get_current_candidate()[0]
121         index = self.__lookup_table.get_cursor_pos()
122         self.__segments[self.__cursor_pos] = index, candidate
123         self.__invalidate()
124         return True
125
126     def page_down(self):
127         # only process cursor down in convert mode
128         if not self.__convert_begined:
129             return False
130
131         if not self.__lookup_table.page_down():
132             return False
133
134         candidate = self.__lookup_table.get_current_candidate()[0]
135         index = self.__lookup_table.get_cursor_pos()
136         self.__segments[self.__cursor_pos] = index, candidate
137         self.__invalidate()
138         return True
139
140     def cursor_up(self):
141         # only process cursor down in convert mode
142         if not self.__convert_begined:
143             return False
144
145         if not self.__lookup_table.cursor_up():
146             return False
147
148         candidate = self.__lookup_table.get_current_candidate()[0]
149         index = self.__lookup_table.get_cursor_pos()
150         self.__segments[self.__cursor_pos] = index, candidate
151         self.__invalidate()
152         return True
153
154     def cursor_down(self):
155         # only process cursor down in convert mode
156         if not self.__convert_begined:
157             return False
158
159         if not self.__lookup_table.cursor_down():
160             return False
161
162         candidate = self.__lookup_table.get_current_candidate()[0]
163         index = self.__lookup_table.get_cursor_pos()
164         self.__segments[self.__cursor_pos] = index, candidate
165         self.__invalidate()
166         return True
167
168     def __commit_string(self, text):
169         self.__reset()
170         self.commit_string(text)
171         self.__invalidate()
172
173     def process_key_event(self, keyval, is_press, state):
174         # ignore key release events
175         if not is_press:
176             return False
177
178         if keyval == keysyms.Return:
179             return self.__on_key_return()
180         elif keyval == keysyms.Escape:
181             return self.__on_key_escape()
182         elif keyval == keysyms.BackSpace:
183             return self.__on_key_back_space()
184         elif keyval == keysyms.Delete or keyval == keysyms.KP_Delete:
185             return self.__on_key_delete()
186         elif keyval == keysyms.space:
187             return self.__on_key_space()
188         elif keyval >= keysyms._1 and keyval <= keysyms._9:
189             index = keyval - keysyms._1
190             return self.__on_key_number(index)
191         elif keyval == keysyms.Page_Up or keyval == keysyms.KP_Page_Up:
192             return self.__on_key_page_up()
193         elif keyval == keysyms.Page_Down or keyval == keysyms.KP_Page_Down:
194             return self.__on_key_page_down()
195         elif keyval == keysyms.Up:
196             return self.__on_key_up()
197         elif keyval == keysyms.Down:
198             return self.__on_key_down()
199         elif keyval == keysyms.Left:
200             return self.__on_key_left()
201         elif keyval == keysyms.Right:
202             return self.__on_key_right()
203         elif keyval in xrange(keysyms.a, keysyms.z + 1) or \
204             keyval in xrange(keysyms.A, keysyms.Z + 1):
205             return self.__on_key_common(keyval)
206         else:
207             return True
208
209         return False
210
211     def property_activate(self, prop_name, state):
212         prop = self.__prop_dict[prop_name]
213         prop.set_state(state)
214
215         if state == ibus.PROP_STATE_CHECKED:
216             if prop_name == "InputMode.Hiragana":
217                 prop = self.__prop_dict["InputMode"]
218                 prop.set_label(_(u"あ"))
219                 self.__input_mode = MODE_HIRAGANA
220                 self.update_property(prop)
221             elif prop_name == "InputMode.Katakana":
222                 prop = self.__prop_dict["InputMode"]
223                 prop.set_label(_(u"ア"))
224                 self.__input_mode = MODE_KATAKANA
225                 self.update_property(prop)
226             elif prop_name == "InputMode.HalfWidthKatakana":
227                 prop = self.__prop_dict["InputMode"]
228                 prop.set_label(_(u"ア"))
229                 self.__input_mode = MODE_HALF_WIDTH_KATAKANA
230                 self.update_property(prop)
231             elif prop_name == "InputMode.Latin":
232                 prop = self.__prop_dict["InputMode"]
233                 self.__input_mode = MODE_LATIN
234                 prop.set_label(_(u"A"))
235                 self.update_property(prop)
236             elif prop_name == "InputMode.WideLatin":
237                 prop = self.__prop_dict["InputMode"]
238                 prop.set_label(_(u"A"))
239                 self.__input_mode = MODE_WIDE_LATIN
240                 self.update_property(prop)
241
242     def focus_in(self):
243         self.register_properties(self.__prop_list)
244
245     def focus_out(self):
246         pass
247
248     # begine convert
249     def __begin_convert(self):
250         if self.__convert_begined:
251             return
252         self.__convert_begined = True
253
254         self.__context.set_string(self.__input_chars.encode("utf-8"))
255         conv_stat = anthy.anthy_conv_stat()
256         self.__context.get_stat(conv_stat)
257
258         for i in xrange(0, conv_stat.nr_segment):
259             buf = self.__context.get_segment(i, 0)
260             text = unicode(buf, "utf-8")
261             self.__segments.append((0, text))
262
263         self.__cursor_pos = 0
264         self.__fill_lookup_table()
265         self.__lookup_table_visible = False
266
267     def __fill_lookup_table(self):
268         # get segment stat
269         seg_stat = anthy.anthy_segment_stat()
270         self.__context.get_segment_stat(self.__cursor_pos, seg_stat)
271
272         # fill lookup_table
273         self.__lookup_table.clean()
274         for i in xrange(0, seg_stat.nr_candidate):
275             buf = self.__context.get_segment(self.__cursor_pos, i)
276             candidate = unicode(buf, "utf-8")
277             self.__lookup_table.append_candidate(candidate)
278
279
280     def __invalidate(self):
281         if self.__need_update:
282             return
283         self.__need_update = True
284         gobject.idle_add(self.__update, priority = gobject.PRIORITY_LOW)
285
286
287     def __update_input_chars(self):
288         begin, end  = max(self.__cursor_pos - 4, 0), self.__cursor_pos
289
290         for i in range(begin, end):
291             text = self.__input_chars[i:end]
292             romja = romaji_typing_rule.get(text, None)
293             if romja != None:
294                 self.__input_chars = u"".join((self.__input_chars[:i], romja, self.__input_chars[end:]))
295                 self.__cursor_pos -= len(text)
296                 self.__cursor_pos += len(romja)
297
298         attrs = ibus.AttrList()
299         attrs.append(ibus.AttributeUnderline(ibus.ATTR_UNDERLINE_SINGLE, 0, len(self.__input_chars.encode("utf-8"))))
300
301         self.update_preedit(self.__input_chars, attrs, self.__cursor_pos, len(self.__input_chars) > 0)
302         self.update_aux_string(u"", ibus.AttrList(), False)
303         self.update_lookup_table(self.__lookup_table, self.__lookup_table_visible)
304
305     def __update_convert_chars(self):
306         self.__convert_chars = u""
307         pos = 0
308         i = 0
309         for seg_index, text in self.__segments:
310             self.__convert_chars += text
311             if i <= self.__cursor_pos:
312                 pos += len(text)
313             i += 1
314
315         attrs = ibus.AttrList()
316         attrs.append(ibus.AttributeUnderline(ibus.ATTR_UNDERLINE_SINGLE, 0, len(self.__convert_chars)))
317         attrs.append(ibus.AttributeBackground(ibus.RGB(200, 200, 240),
318                 pos - len(self.__segments[self.__cursor_pos][1]),
319                 pos))
320         self.update_preedit(self.__convert_chars, attrs, pos, True)
321         aux_string = u"( %d / %d )" % (self.__lookup_table.get_cursor_pos() + 1, self.__lookup_table.get_number_of_candidates())
322         self.update_aux_string(aux_string, ibus.AttrList(), self.__lookup_table_visible)
323         self.update_lookup_table(self.__lookup_table, self.__lookup_table_visible)
324
325     def __update(self):
326         self.__need_update = False
327         if self.__convert_begined == False:
328             self.__update_input_chars()
329         else:
330             self.__update_convert_chars()
331
332     def __on_key_return(self):
333         if not self.__input_chars:
334             return False
335         if self.__convert_begined == False:
336             self.__commit_string(self.__input_chars)
337         else:
338             i = 0
339             for seg_index, text in self.__segments:
340                 self.__context.commit_segment(i, seg_index)
341             self.__commit_string(self.__convert_chars)
342         return True
343
344     def __on_key_escape(self):
345         if not self.__input_chars:
346             return False
347         self.__reset()
348         self.__invalidate()
349         return True
350
351     def __on_key_back_space(self):
352         if not self.__input_chars:
353             return False
354
355         if self.__convert_begined:
356             self.__convert_begined = False
357             self.__cursor_pos = len(self.__input_chars)
358             self.__lookup_table.clean()
359             self.__lookup_table_visible = False
360         elif self.__cursor_pos > 0:
361             self.__input_chars = self.__input_chars[:self.__cursor_pos - 1] + self.__input_chars [self.__cursor_pos:]
362             self.__cursor_pos -= 1
363
364         self.__invalidate()
365         return True
366
367     def __on_key_delete(self):
368         if not self.__input_chars:
369             return False
370
371         if self.__convert_begined:
372             self.__convert_begined = False
373             self.__cursor_pos = len(self.__input_chars)
374             self.__lookup_table.clean()
375             self.__lookup_table_visible = False
376         elif self.__cursor_pos < len(self.__input_chars):
377             self.__input_chars = self.__input_chars[:self.__cursor_pos] + self.__input_chars [self.__cursor_pos + 1:]
378
379         self.__invalidate()
380         return True
381
382     def __on_key_space(self):
383         if not self.__input_chars:
384             return False
385         if self.__convert_begined == False:
386             self.__begin_convert()
387             self.__invalidate()
388         else:
389             self.__lookup_table_visible = True
390             self.cursor_down()
391         return True
392
393     def __on_key_up(self):
394         if not self.__input_chars:
395             return False
396         self.__lookup_table_visible = True
397         self.cursor_up()
398         return True
399
400     def __on_key_down(self):
401         if not self.__input_chars:
402             return False
403         self.__lookup_table_visible = True
404         self.cursor_down()
405         return True
406
407     def __on_key_page_up(self):
408         if not self.__input_chars:
409             return False
410         if self.__lookup_table_visible == True:
411             self.page_up()
412         return True
413
414     def __on_key_page_down(self):
415         if not self.__input_chars:
416             return False
417         if self.__lookup_table_visible == True:
418             self.page_down()
419         return True
420
421     def __on_key_left(self):
422         if not self.__input_chars:
423             return False
424         if self.__cursor_pos == 0:
425             return True
426         self.__cursor_pos -= 1
427         self.__lookup_table_visible = False
428         self.__fill_lookup_table()
429         self.__invalidate()
430         return True
431
432     def __on_key_right(self):
433         if not self.__input_chars:
434             return False
435
436         if self.__convert_begined:
437             max_pos = len(self.__segments) - 1
438         else:
439             max_pos = len(self.__input_chars)
440         if self.__cursor_pos == max_pos:
441             return True
442         self.__cursor_pos += 1
443         self.__lookup_table_visible = False
444         self.__fill_lookup_table()
445         self.__invalidate()
446
447         return True
448
449     def __on_key_number(self, index):
450         if not self.__input_chars:
451             return False
452
453         if self.__convert_begined and self.__lookup_table_visible:
454             candidates = self.__lookup_table.get_canidates_in_current_page()
455             if self.__lookup_table.set_cursor_pos_in_current_page(index):
456                 index = self.__lookup_table.get_cursor_pos()
457                 candidate = self.__lookup_table.get_current_candidate()[0]
458                 self.__segments[self.__cursor_pos] = index, candidate
459                 self.__lookup_table_visible = False
460                 self.__on_key_right()
461                 self.__invalidate()
462         return True
463
464
465     def __on_key_common(self, keyval):
466         if self.__convert_begined:
467             i = 0
468             for seg_index, text in self.__segments:
469                 self.__context.commit_segment(i, seg_index)
470             self.__commit_string(self.__convert_chars)
471         self.__input_chars += unichr(keyval)
472         self.__cursor_pos += 1
473         self.__invalidate()
474         return True
475
476