Position the popup menu.
[ibus.git] / panel / candidatepanel.py
bloba11eab01e9d9cacd96910a9311b14bf7dc85f3ff
1 # vim:set noet ts=4:
3 # ibus - The Input Bus
5 # Copyright (c) 2007-2008 Huang Peng <shawn.p.huang@gmail.com>
7 # This library is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU Lesser General Public
9 # License as published by the Free Software Foundation; either
10 # version 2 of the License, or (at your option) any later version.
12 # This library is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Lesser General Public License for more details.
17 # You should have received a copy of the GNU Lesser General Public
18 # License along with this program; if not, write to the
19 # Free Software Foundation, Inc., 59 Temple Place, Suite 330,
20 # Boston, MA 02111-1307 USA
22 import gtk
23 import gtk.gdk as gdk
24 import gobject
25 import pango
26 import ibus
27 from ibus.gtk import PangoAttrList
29 class HSeparator (gtk.HBox):
30 def __init__ (self):
31 gtk.HBox.__init__ (self)
32 self.pack_start (gtk.HSeparator (), True, True, 4)
34 class VSeparator (gtk.VBox):
35 def __init__ (self):
36 gtk.VBox.__init__ (self)
37 self.pack_start (gtk.VSeparator (), True, True, 4)
39 class CandidateArea (gtk.HBox):
40 def __init__ (self, orientation):
41 gtk.HBox.__init__ (self)
42 self._orientation = orientation
43 self._labels = []
44 self._create_ui ()
46 def _create_ui (self):
47 if self._orientation == gtk.ORIENTATION_VERTICAL:
48 self._vbox1 = gtk.VBox ()
49 self._vbox2 = gtk.VBox ()
50 self.pack_start (self._vbox1, False, False, 4)
51 self.pack_start (VSeparator(), False, False, 0)
52 self.pack_start (self._vbox2, True, True, 4)
54 for i in xrange (1, 10):
55 label1 = gtk.Label ("%d." % i)
56 label1.set_alignment (0.0, 0.5)
57 label1.set_no_show_all (True)
59 label2 = gtk.Label ()
60 label2.set_alignment (0.0, 0.5)
61 label2.set_no_show_all (True)
63 if self._orientation == gtk.ORIENTATION_VERTICAL:
64 label1.set_property ("xpad", 8)
65 label2.set_property ("xpad", 8)
66 self._vbox1.pack_start (label1, False, False, 2)
67 self._vbox2.pack_start (label2, False, False, 2)
68 else:
69 hbox = gtk.HBox ()
70 hbox.pack_start (label1, False, False, 1)
71 hbox.pack_start (label2, False, False, 1)
72 self.pack_start (hbox, False, False, 4)
74 self._labels.append ((label1, label2))
76 self._labels[0][0].show ()
77 self._labels[0][1].show ()
79 def set_candidates (self, candidates, focus_candidate = 0):
80 assert len (candidates) <= len (self._labels)
81 i = 0
82 for text, attrs in candidates:
83 self._labels[i][1].set_text (text)
84 self._labels[i][1].set_attributes (attrs)
85 self._labels[i][0].show ()
86 self._labels[i][1].show ()
87 if i == focus_candidate:
88 self._labels[i][0].set_state (gtk.STATE_SELECTED)
89 self._labels[i][1].set_state (gtk.STATE_SELECTED)
90 else:
91 self._labels[i][0].set_state (gtk.STATE_NORMAL)
92 self._labels[i][1].set_state (gtk.STATE_NORMAL)
94 i += 1
96 for label1, label2 in self._labels[max (1, len(candidates)):]:
97 label1.hide ()
98 label2.hide ()
100 if len (candidates) == 0:
101 self._labels[0][0].set_text ("")
102 self._labels[0][1].set_text ("")
103 else:
104 self._labels[0][0].set_text ("1.")
106 class CandidatePanel (gtk.VBox):
107 __gproperties__ = {
108 'orientation' : (gtk.Orientation, # type
109 'orientation of candidates', # nick name
110 'the orientation of candidates list', # description
112 gobject.PARAM_READWRITE) # flags
115 __gsignals__ = {
116 "cursor-up" : (
117 gobject.SIGNAL_RUN_FIRST,
118 gobject.TYPE_NONE,
119 ()),
120 "cursor-down" : (
121 gobject.SIGNAL_RUN_FIRST,
122 gobject.TYPE_NONE,
123 ()),
126 def __init__ (self):
127 gtk.VBox.__init__ (self)
128 self._tooltips = gtk.Tooltips ()
130 self._toplevel = gtk.Window (gtk.WINDOW_POPUP)
131 self._toplevel.add (self)
132 self._toplevel.add_events (
133 gdk.BUTTON_PRESS_MASK | \
134 gdk.BUTTON_RELEASE_MASK | \
135 gdk.BUTTON1_MOTION_MASK)
136 self._begin_move = False
137 self._toplevel.connect ("button-press-event", self._button_press_event_cb)
138 self._toplevel.connect ("button-release-event", self._button_release_event_cb)
139 self._toplevel.connect ("motion-notify-event", self._motion_notify_event_cb)
141 self._orientation = gtk.ORIENTATION_HORIZONTAL
142 self._orientation = gtk.ORIENTATION_VERTICAL
143 self._show_preedit_string = False
144 self._show_aux_string = False
145 self._show_lookup_table = False
146 self._preedit_string = ""
147 self._preedit_attrs = pango.AttrList ()
148 self._aux_string = ""
149 self._aux_attrs = pango.AttrList ()
150 self._lookup_table = None
152 self._recreate_ui ()
154 def _recreate_ui (self):
155 for w in self:
156 self.remove (w)
157 w.destroy ()
158 # create preedit label
159 self._preedit_label = gtk.Label (self._preedit_string)
160 self._preedit_label.set_attributes (self._preedit_attrs)
161 self._preedit_label.set_alignment (0.0, 0.5)
162 self._preedit_label.set_padding (8, 0)
163 self._preedit_label.set_no_show_all (True)
164 if self._show_preedit_string:
165 self._preedit_label.show ()
167 # create aux label
168 self._aux_label = gtk.Label (self._aux_string)
169 self._aux_label.set_attributes (self._aux_attrs)
170 self._aux_label.set_alignment (0.0, 0.5)
171 self._aux_label.set_padding (8, 0)
172 self._tooltips.set_tip (self._aux_label, "Aux string")
173 self._aux_label.set_no_show_all (True)
174 if self._show_aux_string:
175 self._aux_label.show ()
177 # create candidates area
178 self._candidate_area = CandidateArea (self._orientation)
179 self._candidate_area.set_no_show_all (True)
180 self.update_lookup_table (self._lookup_table, self._show_lookup_table)
182 # create state label
183 self._state_label = gtk.Label ()
184 self._state_label.set_size_request (20, -1)
186 # create buttons
187 self._prev_button = gtk.Button ()
188 self._prev_button.connect ("clicked", lambda x: self.emit ("cursor-up"))
189 self._prev_button.set_relief (gtk.RELIEF_NONE)
190 self._tooltips.set_tip (self._prev_button, "Previous candidate")
192 self._next_button = gtk.Button ()
193 self._next_button.connect ("clicked", lambda x: self.emit ("cursor-down"))
194 self._next_button.set_relief (gtk.RELIEF_NONE)
195 self._tooltips.set_tip (self._next_button, "Next candidate")
197 self._pack_all_widgets ()
199 def _pack_all_widgets (self):
200 if self._orientation == gtk.ORIENTATION_VERTICAL:
201 # package all widgets in vertical mode
202 image = gtk.Image ()
203 image.set_from_stock (gtk.STOCK_GO_UP, gtk.ICON_SIZE_MENU)
204 self._prev_button.set_image (image)
206 image = gtk.Image ()
207 image.set_from_stock (gtk.STOCK_GO_DOWN, gtk.ICON_SIZE_MENU)
208 self._next_button.set_image (image)
209 vbox = gtk.VBox ()
210 vbox.pack_start (self._preedit_label, False, False, 0)
211 vbox.pack_start (self._aux_label, False, False, 0)
212 self.pack_start (vbox, False, False, 5)
213 self.pack_start (HSeparator (), False, False)
214 self.pack_start (self._candidate_area, False, False, 2)
215 self.pack_start (HSeparator (), False, False)
216 hbox= gtk.HBox ()
217 hbox.pack_start (self._state_label, True, True)
218 hbox.pack_start (VSeparator (), False, False)
219 hbox.pack_start (self._prev_button, False, False, 2)
220 hbox.pack_start (self._next_button, False, False, 2)
221 self.pack_start (hbox, False, False)
222 else:
223 # package all widgets in HORIZONTAL mode
224 image = gtk.Image ()
225 image.set_from_stock (gtk.STOCK_GO_BACK, gtk.ICON_SIZE_MENU)
226 self._prev_button.set_image (image)
228 image = gtk.Image ()
229 image.set_from_stock (gtk.STOCK_GO_FORWARD, gtk.ICON_SIZE_MENU)
230 self._next_button.set_image (image)
232 vbox = gtk.VBox ()
233 vbox.pack_start (self._preedit_label, False, False, 0)
234 vbox.pack_start (self._aux_label, False, False, 0)
235 self.pack_start (vbox, False, False, 5)
236 self.pack_start (HSeparator (), False, False)
237 hbox= gtk.HBox ()
238 hbox.pack_start (self._candidate_area, True, True, 2)
239 hbox.pack_start (VSeparator (), False, False)
240 hbox.pack_start (self._prev_button, False, False, 2)
241 hbox.pack_start (self._next_button, False, False, 2)
242 self.pack_start (hbox, False, False)
244 # self.hide_all ()
245 # self.show_all ()
247 def show_preedit_string (self):
248 self._show_preedit_string = True
249 self._preedit_label.show ()
250 self._check_show_states ()
252 def hide_preedit_string (self):
253 self._show_preedit_string = False
254 self._preedit_label.hide ()
255 self._check_show_states ()
257 def update_preedit (self, text, attrs, cursor_pos, show):
258 attrs = PangoAttrList (attrs, text)
259 if show:
260 self.show_preedit_string ()
261 else:
262 self.hide_preedit_string ()
263 self._preedit_string = text
264 self._preedit_label.set_text (text)
265 if attrs == None:
266 attrs = pango.AttrList ()
267 self._preedit_attrs = attrs
268 self._preedit_label.set_attributes (attrs)
270 def show_aux_string (self):
271 self._show_aux_string = True
272 self._aux_label.show ()
273 self._check_show_states ()
275 def hide_aux_string (self):
276 self._show_aux_string = False
277 self._aux_label.hide ()
278 self._check_show_states ()
280 def update_aux_string (self, text, attrs, show):
281 attrs = PangoAttrList (attrs, text)
283 if show:
284 self.show_aux_string ()
285 else:
286 self.hide_aux_string ()
288 self._aux_string = text
289 self._aux_label.set_text (text)
290 if attrs == None:
291 attrs = pango.AttrList ()
292 self._aux_attrs = attrs
293 self._aux_label.set_attributes (attrs)
295 def show_lookup_table (self):
296 self._show_lookup_table = True
297 self._candidate_area.set_no_show_all (False)
298 self._candidate_area.show_all ()
299 self._check_show_states ()
301 def hide_lookup_table (self):
302 self._show_lookup_table = False
303 self._candidate_area.hide_all ()
304 self._candidate_area.set_no_show_all (True)
305 self._check_show_states ()
307 def update_lookup_table (self, lookup_table, show):
308 if lookup_table == None:
309 lookup_table = ibus.LookupTable ()
311 if show:
312 self.show_lookup_table ()
313 else:
314 self.hide_lookup_table ()
316 self._lookup_table = lookup_table
317 candidates = self._lookup_table.get_canidates_in_current_page ()
318 candidates = map (lambda x: (x[0], PangoAttrList (x[1], x[0])), candidates)
319 self._candidate_area.set_candidates (candidates, self._lookup_table.get_cursor_pos_in_current_page ())
321 def _check_show_states (self):
322 if self._show_preedit_string or \
323 self._show_aux_string or \
324 self._show_lookup_table:
325 self.show_all ()
326 self.emit ("show")
327 else:
328 self.hide_all ()
329 self.emit ("hide")
331 def reset (self):
332 self.hide ()
333 self.hide_preedit_string ()
334 self.hide_aux_string ()
335 self.hide_lookup_table ()
336 self.update_preedit ("", None, 0, False)
337 self.update_aux_string ("", None, False)
338 self.update_lookup_table (None, False)
340 def set_orientation (self, orientation):
341 if self._orientation == orientation:
342 return
343 self._orientation = orientation
344 self._recreate_ui ()
345 if self._toplevel.flags () & gtk.VISIBLE:
346 self.show_all ()
348 def get_orientation (self):
349 return self._orientation
351 def do_set_property (self, property, value):
352 if property == 'orientation':
353 self.set_orientation (value)
354 else:
355 return gtk.DrawingArea.do_set_property (property, value)
357 def do_get_property (self, property):
358 if property == 'orientation':
359 return self._orientation
360 else:
361 return gtk.DrawingArea.do_get_property (property)
363 def do_expose_event (self, event):
364 self.style.paint_box (self.window,
365 gtk.STATE_NORMAL,
366 gtk.SHADOW_IN,
367 event.area,
368 self,
369 "menu",
370 self.allocation.x, self.allocation.y,
371 self.allocation.width, self.allocation.height)
373 gtk.VBox.do_expose_event (self, event)
375 def do_size_request (self, requisition):
376 gtk.VBox.do_size_request (self, requisition)
377 self._toplevel.resize (1, 1)
379 def _button_press_event_cb (self, widget, event):
380 if event.button == 1:
381 self._begin_move = True
382 self._press_pos = event.x_root, event.y_root
383 self._toplevel.window.set_cursor (gdk.Cursor (gdk.FLEUR))
384 return True
386 if event.button == 3:
387 if self.get_orientation () == gtk.ORIENTATION_HORIZONTAL:
388 self.set_orientation (gtk.ORIENTATION_VERTICAL)
389 else:
391 self.set_orientation (gtk.ORIENTATION_HORIZONTAL)
392 return True
393 return False
395 def _button_release_event_cb (self, widget, event):
396 if event.button == 1:
397 del self._press_pos
398 self._begin_move = False
399 self._toplevel.window.set_cursor (gdk.Cursor (gdk.LEFT_PTR))
400 return True
401 return False
403 def _motion_notify_event_cb (self, widget, event):
404 if self._begin_move != True:
405 return False
406 x, y = self._toplevel.get_position ()
407 x = int (x + event.x_root - self._press_pos[0])
408 y = int (y + event.y_root - self._press_pos[1])
409 self._toplevel.move (x, y)
410 self._press_pos = event.x_root, event.y_root
411 return True
413 def show_all (self):
414 gtk.VBox.show_all (self)
415 self._toplevel.show_all ()
417 def hide_all (self):
418 gtk.VBox.hide_all (self)
419 self._toplevel.hide_all ()
421 def move (self, x, y):
422 self._toplevel.move (x, y)
424 gobject.type_register (CandidatePanel, "IBusCandidate")