1 # vim:set et sts=4 sw=4:
5 # Copyright(c) 2007-2010 Peng Huang <shawn.p.huang@gmail.com>
6 # Copyright(c) 2007-2010 Red Hat, Inc.
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.1 of the License, or (at your option) any later version.
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 GNU
16 # Lesser General Public License for more details.
18 # You should have received a copy of the GNU Lesser General Public
19 # License along with this library; if not, write to the Free Software
20 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
29 from ibus._gtk import PangoAttrList
30 from handle import Handle
31 from i18n import _, N_
33 class EventBox(gtk.EventBox):
34 __gtype_name__ = "IBusEventBox"
36 super(EventBox, self).__init__()
37 self.connect("realize", self.__realize_cb)
39 def __realize_cb(self, widget):
40 widget.window.set_cursor(gdk.Cursor(gdk.HAND2))
42 class Label(gtk.Label):
43 __gtype_name__ = "IBusCandidateLabel"
45 class HSeparator(gtk.HBox):
46 __gtype_name__ = "IBusHSeparator"
48 super(HSeparator, self).__init__()
49 self.pack_start(gtk.HSeparator(), True, True, 4)
51 class VSeparator(gtk.VBox):
52 __gtype_name__ = "IBusVSeparator"
54 super(VSeparator, self).__init__()
55 self.pack_start(gtk.VSeparator(), True, True, 4)
57 class CandidateArea(gtk.HBox):
58 __gtype_name__ = "IBusCandidateArea"
60 "candidate-clicked" : (
61 gobject.SIGNAL_RUN_FIRST,
63 (gobject.TYPE_UINT, gobject.TYPE_UINT, gobject.TYPE_UINT )),
66 def __init__(self, orientation):
67 super(CandidateArea, self).__init__()
68 self.set_name("IBusCandidateArea")
69 self.__orientation = orientation
71 self.__candidates = []
74 def __create_ui(self):
75 if self.__orientation == ibus.ORIENTATION_VERTICAL:
76 self.__vbox1 = gtk.VBox()
77 self.__vbox1.set_homogeneous(True)
78 self.__vbox2 = gtk.VBox()
79 self.__vbox2.set_homogeneous(True)
80 self.pack_start(self.__vbox1, False, False, 4)
81 self.pack_start(VSeparator(), False, False, 0)
82 self.pack_start(self.__vbox2, True, True, 4)
84 for i in range(0, 16):
85 label1 = Label("%c." % ("1234567890abcdef"[i]))
86 label1.set_alignment(0.0, 0.5)
90 label2.set_alignment(0.0, 0.5)
93 if self.__orientation == ibus.ORIENTATION_VERTICAL:
94 label1.set_property("xpad", 8)
95 label2.set_property("xpad", 8)
97 ebox1.set_no_show_all(True)
100 ebox2.set_no_show_all(True)
102 self.__vbox1.pack_start(ebox1, False, False, 2)
103 self.__vbox2.pack_start(ebox2, False, False, 2)
104 self.__candidates.append((ebox1, ebox2))
108 hbox.pack_start(label1, False, False, 1)
109 hbox.pack_start(label2, False, False, 1)
111 ebox.set_no_show_all(True)
113 self.pack_start(ebox, False, False, 4)
114 self.__candidates.append((ebox,))
116 self.__labels.append((label1, label2))
118 for i, ws in enumerate(self.__candidates):
120 w.connect("button-press-event", lambda w, e, i:self.emit("candidate-clicked", i, e.button, e.state), i)
122 def set_labels(self, labels):
124 for i in xrange(0, 16):
125 self.__labels[i][0].set_text("%c." % ("1234567890abcdef"[i]))
126 self.__labels[i][0].set_property("attributes", None)
130 for text, attrs in labels:
131 self.__labels[i][0].set_text(text)
132 self.__labels[i][0].set_property("attributes", attrs)
137 def set_candidates(self, candidates, focus_candidate = 0, show_cursor = True):
138 assert len(candidates) <= len(self.__labels)
139 for i, (text, attrs) in enumerate(candidates):
140 if i == focus_candidate and show_cursor:
142 attrs = pango.AttrList()
143 color = self.__labels[i][1].style.base[gtk.STATE_SELECTED]
144 end_index = len(text.encode("utf8"))
145 attr = pango.AttrBackground(color.red, color.green, color.blue, 0, end_index)
147 color = self.__labels[i][1].style.text[gtk.STATE_SELECTED]
148 attr = pango.AttrForeground(color.red, color.green, color.blue, 0, end_index)
151 self.__labels[i][1].set_text(text)
152 self.__labels[i][1].show()
153 self.__labels[i][1].set_property("attributes", attrs)
154 for w in self.__candidates[i]:
157 for w in reduce(operator.add, self.__candidates[len(candidates):]):
160 class CandidatePanel(gtk.VBox):
161 __gtype_name__ = "IBusCandidate"
164 gobject.SIGNAL_RUN_FIRST,
168 gobject.SIGNAL_RUN_FIRST,
172 gobject.SIGNAL_RUN_FIRST,
176 gobject.SIGNAL_RUN_FIRST,
179 "candidate-clicked" : (
180 gobject.SIGNAL_RUN_FIRST,
182 (gobject.TYPE_UINT, gobject.TYPE_UINT, gobject.TYPE_UINT)),
186 super(CandidatePanel, self).__init__()
187 self.set_name("IBusCandidate")
189 self.__toplevel = gtk.Window(gtk.WINDOW_POPUP)
190 self.__viewport = gtk.Viewport()
191 self.__viewport.set_shadow_type(gtk.SHADOW_IN)
192 self.__toplevel.add(self.__viewport)
196 handle.connect("move-end", self.__handle_move_end_cb)
197 hbox.pack_start(handle)
198 hbox.pack_start(self)
200 self.__viewport.add(hbox)
201 self.__toplevel.add_events(
202 gdk.BUTTON_PRESS_MASK | \
203 gdk.BUTTON_RELEASE_MASK | \
204 gdk.BUTTON1_MOTION_MASK)
205 self.__toplevel.connect("size-allocate", lambda w, a: self.__check_position())
207 self.__orientation = ibus.ORIENTATION_VERTICAL
208 self.__current_orientation = self.__orientation
209 self.__preedit_visible = False
210 self.__aux_string_visible = False
211 self.__lookup_table_visible = False
212 self.__preedit_string = ""
213 self.__preedit_attrs = pango.AttrList()
214 self.__aux_string = ""
215 self.__aux_attrs = pango.AttrList()
216 self.__lookup_table = None
218 self.__cursor_location = (0, 0, 0, 0)
219 self.__moved_cursor_location = None
223 def __handle_move_end_cb(self, handle):
224 # store moved location
225 self.__moved_cursor_location = self.__toplevel.get_position() + (self.__cursor_location[2], self.__cursor_location[3])
227 def __recreate_ui(self):
231 # create preedit label
232 self.__preedit_label = Label(self.__preedit_string)
233 self.__preedit_label.set_attributes(self.__preedit_attrs)
234 self.__preedit_label.set_alignment(0.0, 0.5)
235 self.__preedit_label.set_padding(8, 0)
236 self.__preedit_label.set_no_show_all(True)
237 if self.__preedit_visible:
238 self.__preedit_label.show()
241 self.__aux_label = Label(self.__aux_string)
242 self.__aux_label.set_attributes(self.__aux_attrs)
243 self.__aux_label.set_alignment(0.0, 0.5)
244 self.__aux_label.set_padding(8, 0)
245 self.__aux_label.set_no_show_all(True)
246 if self.__aux_string_visible:
247 self.__aux_label.show()
249 # create candidates area
250 self.__candidate_area = CandidateArea(self.__current_orientation)
251 self.__candidate_area.set_no_show_all(True)
252 self.__candidate_area.connect("candidate-clicked", lambda x, i, b, s: self.emit("candidate-clicked", i, b, s))
253 # self.update_lookup_table(self.__lookup_table, self.__lookup_table_visible)
256 self.__state_label = Label()
257 self.__state_label.set_size_request(20, -1)
260 self.__prev_button = gtk.Button()
261 self.__prev_button.connect("clicked", lambda x: self.emit("page-up"))
262 self.__prev_button.set_relief(gtk.RELIEF_NONE)
263 self.__prev_button.set_tooltip_text(_("Previous page"))
265 self.__next_button = gtk.Button()
266 self.__next_button.connect("clicked", lambda x: self.emit("page-down"))
267 self.__next_button.set_relief(gtk.RELIEF_NONE)
268 self.__next_button.set_tooltip_text(_("Next page"))
270 self.__pack_all_widgets()
272 def __pack_all_widgets(self):
273 if self.__current_orientation == ibus.ORIENTATION_VERTICAL:
274 # package all widgets in vertical mode
276 image.set_from_stock(gtk.STOCK_GO_UP, gtk.ICON_SIZE_MENU)
277 self.__prev_button.set_image(image)
280 image.set_from_stock(gtk.STOCK_GO_DOWN, gtk.ICON_SIZE_MENU)
281 self.__next_button.set_image(image)
284 vbox.pack_start(self.__preedit_label, False, False, 0)
285 vbox.pack_start(self.__aux_label, False, False, 0)
286 self.pack_start(vbox, False, False, 5)
287 self.pack_start(HSeparator(), False, False)
288 self.pack_start(self.__candidate_area, False, False, 2)
289 self.pack_start(HSeparator(), False, False)
291 hbox.pack_start(self.__state_label, True, True)
292 hbox.pack_start(VSeparator(), False, False)
293 hbox.pack_start(self.__prev_button, False, False, 2)
294 hbox.pack_start(self.__next_button, False, False, 2)
295 self.pack_start(hbox, False, False)
297 # package all widgets in HORIZONTAL mode
299 image.set_from_stock(gtk.STOCK_GO_UP, gtk.ICON_SIZE_MENU)
300 self.__prev_button.set_image(image)
303 image.set_from_stock(gtk.STOCK_GO_DOWN, gtk.ICON_SIZE_MENU)
304 self.__next_button.set_image(image)
307 vbox.pack_start(self.__preedit_label, False, False, 0)
308 vbox.pack_start(self.__aux_label, False, False, 0)
309 self.pack_start(vbox, False, False, 5)
310 self.pack_start(HSeparator(), False, False)
312 hbox.pack_start(self.__candidate_area, True, True, 2)
313 hbox.pack_start(VSeparator(), False, False)
314 hbox.pack_start(self.__prev_button, False, False, 2)
315 hbox.pack_start(self.__next_button, False, False, 2)
316 self.pack_start(hbox, False, False)
321 def show_preedit_text(self):
322 self.__preedit_visible = True
323 self.__preedit_label.show()
324 self.__check_show_states()
326 def hide_preedit_text(self):
327 self.__preedit_visible = False
328 self.__preedit_label.hide()
329 self.__check_show_states()
331 def update_preedit_text(self, text, cursor_pos, visible):
332 attrs = PangoAttrList(text.attributes, text.text)
334 self.show_preedit_text()
336 self.hide_preedit_text()
337 self.__preedit_stribg = text.text
338 self.__preedit_label.set_text(text.text)
339 self.__preedit_attrs = attrs
340 self.__preedit_label.set_attributes(attrs)
342 def show_auxiliary_text(self):
343 self.__aux_string_visible = True
344 self.__aux_label.show()
345 self.__check_show_states()
347 def hide_auxiliary_text(self):
348 self.__aux_string_visible = False
349 self.__aux_label.hide()
350 self.__check_show_states()
352 def update_auxiliary_text(self, text, show):
353 attrs = PangoAttrList(text.attributes, text.text)
356 self.show_auxiliary_text()
358 self.hide_auxiliary_text()
360 self.__aux_string = text.text
361 self.__aux_label.set_text(text.text)
362 self.__aux_attrs = attrs
363 self.__aux_label.set_attributes(attrs)
365 def __refresh_labels(self):
366 labels = self.__lookup_table.get_labels()
368 labels = map(lambda x: (x.text, PangoAttrList(x.attributes, x.text)), labels)
371 self.__candidate_area.set_labels(labels)
374 def __refresh_candidates(self):
375 candidates = self.__lookup_table.get_candidates_in_current_page()
376 candidates = map(lambda x: (x.text, PangoAttrList(x.attributes, x.text)), candidates)
377 self.__candidate_area.set_candidates(candidates,
378 self.__lookup_table.get_cursor_pos_in_current_page(),
379 self.__lookup_table.is_cursor_visible()
382 def update_lookup_table(self, lookup_table, visible):
385 self.hide_lookup_table()
387 self.__lookup_table = lookup_table or ibus.LookupTable()
388 orientation = self.__lookup_table.get_orientation()
389 if orientation not in (ibus.ORIENTATION_HORIZONTAL, ibus.ORIENTATION_VERTICAL):
390 orientation = self.__orientation
391 self.set_current_orientation(orientation)
392 self.__refresh_candidates()
393 self.__refresh_labels()
397 self.show_lookup_table()
399 def show_lookup_table(self):
400 self.__lookup_table_visible = True
401 self.__candidate_area.set_no_show_all(False)
402 self.__candidate_area.show_all()
403 self.__check_show_states()
405 def hide_lookup_table(self):
406 self.__lookup_table_visible = False
407 self.__candidate_area.hide_all()
408 self.__candidate_area.set_no_show_all(True)
409 self.__check_show_states()
411 def page_up_lookup_table(self):
412 self.__lookup_table.page_up()
413 self.__refresh_candidates()
415 def page_down_lookup_table(self):
416 self.__lookup_table.page_down()
417 self.__refresh_candidates()
419 def cursor_up_lookup_table(self):
420 self.__lookup_table.cursor_up()
421 self.__refresh_candidates()
423 def cursor_down_lookup_table(self):
424 self.__lookup_table.cursor_down()
425 self.__refresh_candidates()
427 def set_cursor_location(self, x, y, w, h):
428 # if cursor location is changed, we reset the moved cursor location
429 if self.__cursor_location != (x, y, w, h):
430 self.__cursor_location = (x, y, w, h)
431 self.__moved_cursor_location = None
432 self.__check_position()
434 def __check_show_states(self):
435 if self.__preedit_visible or \
436 self.__aux_string_visible or \
437 self.__lookup_table_visible:
445 self.update_preedit_text(ibus.Text(""), 0, False)
446 self.update_auxiliary_text(ibus.Text(""), False)
447 self.update_lookup_table(None, False)
450 def set_current_orientation(self, orientation):
451 if self.__current_orientation == orientation:
453 self.__current_orientation = orientation
455 if self.__toplevel.flags() & gtk.VISIBLE:
458 def set_orientation(self, orientation):
459 self.__orientation = orientation
460 self.update_lookup_table(self.__lookup_table, self.__lookup_table_visible)
462 def get_current_orientation(self):
463 return self.__current_orientation
465 # def do_expose_event(self, event):
466 # self.style.paint_box(self.window,
472 # self.allocation.x, self.allocation.y,
473 # self.allocation.width, self.allocation.height)
475 # gtk.VBox.do_expose_event(self, event)
477 def do_size_request(self, requisition):
478 gtk.VBox.do_size_request(self, requisition)
479 self.__toplevel.resize(1, 1)
481 def __check_position(self):
482 cursor_location = self.__moved_cursor_location or self.__cursor_location
484 cursor_right = cursor_location[0] + cursor_location[2]
485 cursor_bottom = cursor_location[1] + cursor_location[3]
487 window_right = cursor_right + self.__toplevel.allocation.width
488 window_bottom = cursor_bottom + self.__toplevel.allocation.height
490 root_window = gdk.get_default_root_window()
491 sx, sy = root_window.get_size()
493 if window_right > sx:
494 x = sx - self.__toplevel.allocation.width
498 if window_bottom > sy:
499 # move the window just above the cursor so the window and a preedit string do not overlap.
500 y = cursor_location[1] - self.__toplevel.allocation.height
507 gtk.VBox.show_all(self)
508 self.__toplevel.show_all()
511 gtk.VBox.hide_all(self)
512 self.__toplevel.hide_all()
514 def move(self, x, y):
515 self.__toplevel.move(x, y)
517 if __name__ == "__main__":
518 table = ibus.LookupTable()
519 table.append_candidate(ibus.Text("AAA"))
520 table.append_candidate(ibus.Text("BBB"))
521 cp = CandidatePanel()
523 cp.update_lookup_table(table, True)