b16b90a095d46b075f8c5df8aab2fcd8e60644cc
[platform/upstream/ibus.git] / engine / anthy / engine.py
1 # vim:set noet ts=4:
2 # -*- coding: utf-8 -*-
3 #
4 # ibus - The Input Bus
5 #
6 # Copyright (c) 2007-2008 Huang Peng <shawn.p.huang@gmail.com>
7 #
8 # This library is free software; you can redistribute it and/or
9 # modify it under the terms of the GNU Lesser General Public
10 # License as published by the Free Software Foundation; either
11 # version 2 of the License, or (at your option) any later version.
12 #
13 # This library 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 Lesser General Public License for more details.
17 #
18 # You should have received a copy of the GNU Lesser General Public
19 # License along with this program; if not, write to the
20 # Free Software Foundation, Inc., 59 Temple Place, Suite 330,
21 # Boston, MA  02111-1307  USA
22
23 import gobject
24 import gtk
25 import pango
26 import dbus
27 import ibus
28 import anthy
29 from tables import *
30 from ibus import keysyms
31 from ibus import interface
32 MODE_HIRAGANA, \
33 MODE_KATAKANA, \
34 MODE_HALF_WIDTH_KATAKANA, \
35 MODE_LATIN, \
36 MODE_WIDE_LATIN = range (0, 5)
37
38 _ = lambda a: a
39
40 class Engine (interface.IEngine):
41         def __init__ (self, dbusconn, object_path):
42                 interface.IEngine.__init__ (self, dbusconn, object_path)
43                 self._dbusconn = dbusconn
44
45                 # create anthy context
46                 self._context = anthy.anthy_context ()
47                 self._context._set_encoding (anthy.ANTHY_UTF8_ENCODING)
48
49                 # init state
50                 self._input_mode = MODE_HIRAGANA
51                 self._prop_dict = {}
52
53                 self._lookup_table = ibus.LookupTable ()
54                 self._prop_list = self._init_props ()
55
56                 # use reset to init values
57                 self._reset ()
58
59         def _init_props (self):
60                 props = ibus.PropList ()
61
62                 # init input mode properties
63                 mode_prop = ibus.Property (name = "InputMode",
64                                                         type = ibus.PROP_TYPE_MENU,
65                                                         label = "あ",
66                                                         tooltip = "Switch input mode")
67                 self._prop_dict["InputMode"] = mode_prop
68
69                 mode_props = ibus.PropList ()
70                 mode_props.append (ibus.Property (name = "InputMode.Hiragana",
71                                                                                 type = ibus.PROP_TYPE_RADIO,
72                                                                                 label = "Hiragana"))
73                 mode_props.append (ibus.Property (name = "InputMode.Katakana",
74                                                                                 type = ibus.PROP_TYPE_RADIO,
75                                                                                 label = "Katakana"))
76                 mode_props.append (ibus.Property (name = "InputMode.HalfWidthKatakana",
77                                                                                 type = ibus.PROP_TYPE_RADIO,
78                                                                                 label = "Half width katakana"))
79                 mode_props.append (ibus.Property (name = "InputMode.Latin",
80                                                                                 type = ibus.PROP_TYPE_RADIO,
81                                                                                 label = "Latin"))
82                 mode_props.append (ibus.Property (name = "InputMode.WideLatin",
83                                                                                 type = ibus.PROP_TYPE_RADIO,
84                                                                                 label = "Wide Latin"))
85
86                 mode_props[self._input_mode].set_state (ibus.PROP_STATE_CHECKED)
87
88                 for prop in mode_props:
89                         self._prop_dict[prop.get_name ()] = prop
90
91                 mode_prop.set_sub_props (mode_props)
92                 props.append (mode_prop)
93
94
95                 # init test property
96                 test_prop = ibus.Property (name = "TestProp",
97                                                         type = ibus.PROP_TYPE_TOGGLE,
98                                                         label = "あ",
99                                                         tooltip = "test property")
100                 self._prop_dict["TestProp"] = test_prop
101                 props.append (test_prop)
102
103
104                 return props
105
106         # reset values of engine
107         def _reset (self):
108                 self._input_chars = u""
109                 self._convert_chars = u""
110                 self._cursor_pos = 0
111                 self._need_update = False
112                 self._convert_begined = False
113                 self._segments = []
114                 self._lookup_table.clean ()
115                 self._lookup_table_visible = False
116
117         # begine convert
118         def _begin_convert (self):
119                 if self._convert_begined:
120                         return
121                 self._convert_begined = True
122
123                 self._context.set_string (self._input_chars.encode ("utf-8"))
124                 conv_stat = anthy.anthy_conv_stat ()
125                 self._context.get_stat (conv_stat)
126
127                 for i in xrange (0, conv_stat.nr_segment):
128                         buf = self._context.get_segment (i, 0)
129                         text = unicode (buf, "utf-8")
130                         self._segments.append ((0, text))
131
132                 self._cursor_pos = 0
133                 self._fill_lookup_table ()
134                 self._lookup_table_visible = False
135
136         def _fill_lookup_table (self):
137                 # get segment stat
138                 seg_stat = anthy.anthy_segment_stat ()
139                 self._context.get_segment_stat (self._cursor_pos, seg_stat)
140
141                 # fill lookup_table
142                 self._lookup_table.clean ()
143                 for i in xrange (0, seg_stat.nr_candidate):
144                         buf = self._context.get_segment (self._cursor_pos, i)
145                         candidate = unicode (buf, "utf-8")
146                         self._lookup_table.append_candidate (candidate)
147
148
149         def _invalidate (self):
150                 if self._need_update:
151                         return
152                 self._need_update = True
153                 gobject.idle_add (self._update, priority = gobject.PRIORITY_LOW)
154
155         def _page_up (self):
156                 # only process cursor down in convert mode
157                 if not self._convert_begined:
158                         return False
159
160                 if not self._lookup_table.page_up ():
161                         return False
162
163                 candidate = self._lookup_table.get_current_candidate ()[0]
164                 index = self._lookup_table.get_cursor_pos ()
165                 self._segments[self._cursor_pos] = index, candidate
166                 self._invalidate ()
167                 return True
168
169         def _page_down (self):
170                 # only process cursor down in convert mode
171                 if not self._convert_begined:
172                         return False
173
174                 if not self._lookup_table.page_down ():
175                         return False
176
177                 candidate = self._lookup_table.get_current_candidate ()[0]
178                 index = self._lookup_table.get_cursor_pos ()
179                 self._segments[self._cursor_pos] = index, candidate
180                 self._invalidate ()
181                 return True
182
183         def _cursor_up (self):
184                 # only process cursor down in convert mode
185                 if not self._convert_begined:
186                         return False
187
188                 if not self._lookup_table.cursor_up ():
189                         return False
190
191                 candidate = self._lookup_table.get_current_candidate ()[0]
192                 index = self._lookup_table.get_cursor_pos ()
193                 self._segments[self._cursor_pos] = index, candidate
194                 self._invalidate ()
195                 return True
196
197         def _cursor_down (self):
198                 # only process cursor down in convert mode
199                 if not self._convert_begined:
200                         return False
201
202                 if not self._lookup_table.cursor_down ():
203                         return False
204
205                 candidate = self._lookup_table.get_current_candidate ()[0]
206                 index = self._lookup_table.get_cursor_pos ()
207                 self._segments[self._cursor_pos] = index, candidate
208                 self._invalidate ()
209                 return True
210
211         def _commit_string (self, text):
212                 self._reset ()
213                 self.CommitString (text)
214                 self._invalidate ()
215
216         def _update_input_chars (self):
217                 begin, end  = max (self._cursor_pos - 4, 0), self._cursor_pos
218
219                 for i in range (begin, end):
220                         text = self._input_chars[i:end]
221                         romja = romaji_typing_rule.get (text, None)
222                         if romja != None:
223                                 self._input_chars = u"".join ((self._input_chars[:i], romja, self._input_chars[end:]))
224                                 self._cursor_pos -= len(text)
225                                 self._cursor_pos += len(romja)
226
227                 attrs = ibus.AttrList ()
228                 attrs.append (ibus.AttributeUnderline (pango.UNDERLINE_SINGLE, 0, len (self._input_chars.encode ("utf-8"))))
229
230                 self.UpdatePreedit (dbus.String (self._input_chars),
231                                 attrs.to_dbus_value (),
232                                 dbus.Int32 (self._cursor_pos),
233                                 len (self._input_chars) > 0)
234                 self.UpdateAuxString (u"", ibus.AttrList ().to_dbus_value (), False)
235                 self.UpdateLookupTable (self._lookup_table.to_dbus_value (), self._lookup_table_visible)
236
237         def _update_convert_chars (self):
238                 self._convert_chars = u""
239                 pos = 0
240                 i = 0
241                 for seg_index, text in self._segments:
242                         self._convert_chars += text
243                         if i <= self._cursor_pos:
244                                 pos += len (text)
245                         i += 1
246
247                 attrs = ibus.AttrList ()
248                 attrs.append (ibus.AttributeUnderline (pango.UNDERLINE_SINGLE, 0, len (self._convert_chars)))
249                 attrs.append (ibus.AttributeBackground (ibus.RGB (200, 200, 240),
250                                 pos - len (self._segments[self._cursor_pos][1]),
251                                 pos))
252                 self.UpdatePreedit (dbus.String (self._convert_chars),
253                                 attrs.to_dbus_value (),
254                                 dbus.Int32 (pos),
255                                 True)
256                 aux_string = u"( %d / %d )" % (self._lookup_table.get_cursor_pos () + 1, self._lookup_table.get_number_of_candidates())
257                 self.UpdateAuxString (aux_string, ibus.AttrList ().to_dbus_value (), self._lookup_table_visible)
258                 self.UpdateLookupTable (self._lookup_table.to_dbus_value (), self._lookup_table_visible)
259
260         def _update (self):
261                 self._need_update = False
262                 if self._convert_begined == False:
263                         self._update_input_chars ()
264                 else:
265                         self._update_convert_chars ()
266
267         def _on_key_return (self):
268                 if not self._input_chars:
269                         return False
270                 if self._convert_begined == False:
271                         self._commit_string (self._input_chars)
272                 else:
273                         i = 0
274                         for seg_index, text in self._segments:
275                                 self._context.commit_segment (i, seg_index)
276                         self._commit_string (self._convert_chars)
277                 return True
278
279         def _on_key_escape (self):
280                 if not self._input_chars:
281                         return False
282                 self._reset ()
283                 self._invalidate ()
284                 return True
285
286         def _on_key_back_space (self):
287                 if not self._input_chars:
288                         return False
289
290                 if self._convert_begined:
291                         self._convert_begined = False
292                         self._cursor_pos = len (self._input_chars)
293                         self._lookup_table.clean ()
294                         self._lookup_table_visible = False
295                 elif self._cursor_pos > 0:
296                         self._input_chars = self._input_chars[:self._cursor_pos - 1] + self._input_chars [self._cursor_pos:]
297                         self._cursor_pos -= 1
298
299                 self._invalidate ()
300                 return True
301
302         def _on_key_delete (self):
303                 if not self._input_chars:
304                         return False
305
306                 if self._convert_begined:
307                         self._convert_begined = False
308                         self._cursor_pos = len (self._input_chars)
309                         self._lookup_table.clean ()
310                         self._lookup_table_visible = False
311                 elif self._cursor_pos < len (self._input_chars):
312                         self._input_chars = self._input_chars[:self._cursor_pos] + self._input_chars [self._cursor_pos + 1:]
313
314                 self._invalidate ()
315                 return True
316
317         def _on_key_space (self):
318                 if not self._input_chars:
319                         return False
320                 if self._convert_begined == False:
321                         self._begin_convert ()
322                         self._invalidate ()
323                 else:
324                         self._lookup_table_visible = True
325                         self._cursor_down ()
326                 return True
327
328         def _on_key_up (self):
329                 if not self._input_chars:
330                         return False
331                 self._lookup_table_visible = True
332                 self._cursor_up ()
333                 return True
334
335         def _on_key_down (self):
336                 if not self._input_chars:
337                         return False
338                 self._lookup_table_visible = True
339                 self._cursor_down ()
340                 return True
341
342         def _on_key_page_up (self):
343                 if not self._input_chars:
344                         return False
345                 if self._lookup_table_visible == True:
346                         self._page_up ()
347                 return True
348
349         def _on_key_page_down (self):
350                 if not self._input_chars:
351                         return False
352                 if self._lookup_table_visible == True:
353                         self._page_down ()
354                 return True
355
356         def _on_key_left (self):
357                 if not self._input_chars:
358                         return False
359                 if self._cursor_pos == 0:
360                         return True
361                 self._cursor_pos -= 1
362                 self._lookup_table_visible = False
363                 self._fill_lookup_table ()
364                 self._invalidate ()
365                 return True
366
367         def _on_key_right (self):
368                 if not self._input_chars:
369                         return False
370
371                 if self._convert_begined:
372                         max_pos = len (self._segments) - 1
373                 else:
374                         max_pos = len (self._input_chars)
375                 if self._cursor_pos == max_pos:
376                         return True
377                 self._cursor_pos += 1
378                 self._lookup_table_visible = False
379                 self._fill_lookup_table ()
380                 self._invalidate ()
381
382                 return True
383
384         def _on_key_number (self, index):
385                 if not self._input_chars:
386                         return False
387
388                 if self._convert_begined and self._lookup_table_visible:
389                         candidates = self._lookup_table.get_canidates_in_current_page ()
390                         if self._lookup_table.set_cursor_pos_in_current_page (index):
391                                 index = self._lookup_table.get_cursor_pos ()
392                                 candidate = self._lookup_table.get_current_candidate ()[0]
393                                 self._segments[self._cursor_pos] = index, candidate
394                                 self._lookup_table_visible = False
395                                 self._on_key_right ()
396                                 self._invalidate ()
397                 return True
398
399
400         def _on_key_common (self, keyval):
401                 if self._convert_begined:
402                         i = 0
403                         for seg_index, text in self._segments:
404                                 self._context.commit_segment (i, seg_index)
405                         self._commit_string (self._convert_chars)
406                 self._input_chars += unichr (keyval)
407                 self._cursor_pos += 1
408                 self._invalidate ()
409                 return True
410
411         def _process_key_event (self, keyval, is_press, state):
412                 # ignore key release events
413                 if not is_press:
414                         return False
415
416                 if keyval == keysyms.Return:
417                         return self._on_key_return ()
418                 elif keyval == keysyms.Escape:
419                         return self._on_key_escape ()
420                 elif keyval == keysyms.BackSpace:
421                         return self._on_key_back_space ()
422                 elif keyval == keysyms.Delete or keyval == keysyms.KP_Delete:
423                         return self._on_key_delete ()
424                 elif keyval == keysyms.space:
425                         return self._on_key_space ()
426                 elif keyval >= keysyms._1 and keyval <= keysyms._9:
427                         index = keyval - keysyms._1
428                         return self._on_key_number (index)
429                 elif keyval == keysyms.Page_Up or keyval == keysyms.KP_Page_Up:
430                         return self._on_key_page_up ()
431                 elif keyval == keysyms.Page_Down or keyval == keysyms.KP_Page_Down:
432                         return self._on_key_page_down ()
433                 elif keyval == keysyms.Up:
434                         return self._on_key_up ()
435                 elif keyval == keysyms.Down:
436                         return self._on_key_down ()
437                 elif keyval == keysyms.Left:
438                         return self._on_key_left ()
439                 elif keyval == keysyms.Right:
440                         return self._on_key_right ()
441                 elif keyval in xrange (keysyms.a, keysyms.z + 1) or \
442                         keyval in xrange (keysyms.A, keysyms.Z + 1):
443                         return self._on_key_common (keyval)
444                 else:
445                         return True
446
447                 return False
448
449         def _property_activate (self, prop_name, state):
450                 prop = self._prop_dict[prop_name]
451                 prop.set_state (state)
452
453                 if state == ibus.PROP_STATE_CHECKED:
454                         if prop_name == "InputMode.Hiragana":
455                                 prop = self._prop_dict["InputMode"]
456                                 prop.set_label (_("あ"))
457                                 self._input_mode = MODE_HIRAGANA
458                                 self._update_property (prop)
459                         elif prop_name == "InputMode.Katakana":
460                                 prop = self._prop_dict["InputMode"]
461                                 prop.set_label (_("ア"))
462                                 self._input_mode = MODE_KATAKANA
463                                 self._update_property (prop)
464                         elif prop_name == "InputMode.HalfWidthKatakana":
465                                 prop = self._prop_dict["InputMode"]
466                                 prop.set_label (_("ア"))
467                                 self._input_mode = MODE_HALF_WIDTH_KATAKANA
468                                 self._update_property (prop)
469                         elif prop_name == "InputMode.Latin":
470                                 prop = self._prop_dict["InputMode"]
471                                 self._input_mode = MODE_LATIN
472                                 prop.set_label (_("A"))
473                                 self._update_property (prop)
474                         elif prop_name == "InputMode.WideLatin":
475                                 prop = self._prop_dict["InputMode"]
476                                 prop.set_label (_("A"))
477                                 self._input_mode = MODE_WIDE_LATIN
478                                 self._update_property (prop)
479
480         def _update_property (self, prop):
481                 self.UpdateProperty (prop.to_dbus_value ())
482
483         # methods for dbus rpc
484         def ProcessKeyEvent (self, keyval, is_press, state):
485                 try:
486                         return self._process_key_event (keyval, is_press, state)
487                 except Exception, e:
488                         print e
489                 return False
490
491         def FocusIn (self):
492                 self.RegisterProperties (self._prop_list.to_dbus_value ())
493                 print "FocusIn"
494
495         def FocusOut (self):
496                 print "FocusOut"
497
498         def SetCursorLocation (self, x, y, w, h):
499                 pass
500
501         def Reset (self):
502                 print "Reset"
503
504         def PageUp (self):
505                 self._page_up ()
506
507         def PageDown (self):
508                 self._page_down ()
509
510         def CursorUp (self):
511                 self._cursor_up ()
512
513         def CursorDown (self):
514                 self._cursor_down ()
515
516         def SetEnable (self, enable):
517                 self._enable = enable
518                 if self._enable:
519                         self.RegisterProperties (self._prop_list.to_dbus_value ())
520
521         def PropertyActivate (self, prop_name, prop_state):
522                 self._property_activate (prop_name, prop_state)
523
524         def Destroy (self):
525                 print "Destroy"
526