9d63a172582eed43a3acb22107b0610b6db80e49
[platform/upstream/ibus.git] / ui / gtk / panel.py
1 # vim:set et sts=4 sw=4:
2 #
3 # ibus - The Input Bus
4 #
5 # Copyright(c) 2007-2010 Peng Huang <shawn.p.huang@gmail.com>
6 # Copyright(c) 2007-2010 Red Hat, Inc.
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 gtk
24 import gtk.gdk as gdk
25 import glib
26 import gobject
27 import ibus
28 import icon as _icon
29 import os
30 import sys
31 import signal
32 from os import path
33 from ibus import interface
34 from languagebar import LanguageBar
35 from candidatepanel import CandidatePanel
36 from engineabout import EngineAbout
37
38 from i18n import _, N_
39
40 ICON_KEYBOARD = "ibus-keyboard"
41 ICON_ENGINE = "ibus-engine"
42
43 def show_uri(screen, link):
44     try:
45         gtk.show_uri(screen, link, 0)
46     except:
47         print >> sys.stderr, "pygtk do not support show_uri"
48
49 def url_hook(about, link, user_data):
50     show_uri(about.get_screen(), link)
51
52 def email_hook(about, email, user_data):
53     show_uri(about.get_screen(), "mailto:%s" % email)
54
55 gtk.about_dialog_set_url_hook(url_hook, None)
56 gtk.about_dialog_set_email_hook(email_hook, None)
57
58 class Panel(ibus.PanelBase):
59     __gtype_name__ = "IBusPanel"
60     def __init__(self, bus):
61         super(Panel, self).__init__(bus)
62         self.__bus = bus
63         self.__config = self.__bus.get_config()
64         self.__focus_ic = None
65         self.__setup_pid = None
66         self.__prefix = os.getenv("IBUS_PREFIX")
67         self.__data_dir = path.join(self.__prefix, "share", "ibus")
68         # self.__icons_dir = path.join(self.__data_dir, "icons")
69         self.__setup_cmd = path.join(self.__prefix, "bin", "ibus-setup")
70
71         # connect bus signal
72         self.__config.connect("value-changed", self.__config_value_changed_cb)
73         self.__config.connect("reloaded", self.__config_reloaded_cb)
74         # self.__bus.config_add_watch("panel")
75
76         # add icon search path
77         # icon_theme = gtk.icon_theme_get_default()
78         # icon_theme.prepend_search_path(self.__icons_dir)
79
80         self.__language_bar = LanguageBar()
81         self.__language_bar.connect("property-activate",
82                         lambda widget, prop_name, prop_state: self.property_activate(prop_name, prop_state))
83         self.__language_bar.connect("get-im-menu",
84                         self.__get_im_menu_cb)
85         self.__language_bar.connect("show-engine-about",
86                         self.__show_engine_about_cb)
87         self.__language_bar.connect("position-changed",
88                         self.__position_changed_cb)
89         self.__language_bar.focus_out()
90         self.__language_bar.show_all()
91
92         self.__candidate_panel = CandidatePanel()
93         self.__candidate_panel.connect("cursor-up",
94                         lambda widget: self.cursor_up())
95         self.__candidate_panel.connect("cursor-down",
96                         lambda widget: self.cursor_down())
97         self.__candidate_panel.connect("page-up",
98                         lambda widget: self.page_up())
99         self.__candidate_panel.connect("page-down",
100                         lambda widget: self.page_down())
101         self.__candidate_panel.connect("candidate-clicked",
102                         lambda widget, index, button, state: self.candidate_clicked(index, button, state))
103
104
105         self.__status_icon = gtk.StatusIcon()
106         # gnome-shell checks XClassHint.res_class with ShellTrayIcon.
107         # gtk_status_icon_set_name() can set XClassHint.res_class .
108         # However gtk_status_icon_new() also calls gtk_window_realize() so
109         # gtk_status_icon_set_visible() needs to be called to set WM_CLASS
110         # so that gtk_window_realize() is called later again.
111         # set_title is for gnome-shell notificationDaemon in bottom right.
112         self.__status_icon.set_visible(False)
113         self.__status_icon.set_name('ibus-ui-gtk')
114         self.__status_icon.set_title(_("IBus Panel"))
115         self.__status_icon.set_visible(True)
116         self.__status_icon.connect("popup-menu", self.__status_icon_popup_menu_cb)
117         self.__status_icon.connect("activate", self.__status_icon_activate_cb)
118         self.__status_icon.set_from_icon_name(ICON_KEYBOARD)
119         self.__status_icon.set_tooltip(_("IBus input method framework"))
120         self.__status_icon.set_visible(True)
121
122         self.__config_load_lookup_table_orientation()
123         self.__config_load_show()
124         self.__config_load_position()
125         self.__config_load_custom_font()
126         self.__config_load_show_icon_on_systray()
127         self.__config_load_show_im_name()
128         # self.__bus.request_name(ibus.panel.IBUS_SERVICE_PANEL, 0)
129
130     def set_cursor_location(self, x, y, w, h):
131         self.__candidate_panel.set_cursor_location(x, y, w, h)
132
133     def update_preedit_text(self, text, cursor_pos, visible):
134         self.__candidate_panel.update_preedit_text(text, cursor_pos, visible)
135
136     def show_preedit_text(self):
137         self.__candidate_panel.show_preedit_text()
138
139     def hide_preedit_text(self):
140         self.__candidate_panel.hide_preedit_text()
141
142     def update_auxiliary_text(self, text, visible):
143         self.__candidate_panel.update_auxiliary_text(text, visible)
144
145     def show_auxiliary_text(self):
146         self.__candidate_panel.show_auxiliary_text()
147
148     def hide_auxiliary_text(self):
149         self.__candidate_panel.hide_auxiliary_text()
150
151     def update_lookup_table(self, lookup_table, visible):
152         self.__candidate_panel.update_lookup_table(lookup_table, visible)
153
154     def show_lookup_table(self):
155         self.__candidate_panel.show_lookup_table()
156
157     def hide_lookup_table(self):
158         self.__candidate_panel.hide_lookup_table()
159
160     def page_up_lookup_table(self):
161         self.__candidate_panel.page_up_lookup_table()
162
163     def page_down_lookup_table(self):
164         self.__candidate_panel.page_down_lookup_table()
165
166     def cursor_up_lookup_table(self):
167         self.__candidate_panel.cursor_up_lookup_table()
168
169     def cursor_down_lookup_table(self):
170         self.__candidate_panel.cursor_down_lookup_table()
171
172     def show_candidate_window(self):
173         self.__candidate_panel.show_all()
174
175     def hide_candidate_window(self):
176         self.__candidate_panel.hide_all()
177
178     def show_language_bar(self):
179         self.__language_bar.show_all()
180
181     def hide_language_bar(self):
182         self.__language_bar.hide_all()
183
184     def register_properties(self, props):
185         self.__language_bar.register_properties(props)
186
187     def update_property(self, prop):
188         self.__language_bar.update_property(prop)
189
190     def get_status_icon(self):
191         return self.__status_icon
192
193     def __set_im_icon(self, icon_name):
194         if not icon_name:
195             icon_name = ICON_ENGINE
196         self.__language_bar.set_im_icon(icon_name)
197         if icon_name.startswith("/"):
198             self.__status_icon.set_from_file(icon_name)
199         else:
200             self.__status_icon.set_from_icon_name(icon_name)
201
202     def __set_im_name(self, name):
203         self.__language_bar.set_im_name(name)
204
205     def focus_in(self, ic):
206         self.reset()
207         self.__focus_ic = ibus.InputContext(self.__bus, ic)
208         enabled = self.__focus_ic.is_enabled()
209         self.__language_bar.set_enabled(enabled)
210
211         if not enabled:
212             self.__set_im_icon(ICON_KEYBOARD)
213             self.__set_im_name(None)
214         else:
215             engine = self.__focus_ic.get_engine()
216             if engine:
217                 self.__set_im_icon(engine.icon)
218                 self.__set_im_name(engine.longname)
219             else:
220                 self.__set_im_icon(ICON_KEYBOARD)
221                 self.__set_im_name(None)
222         self.__language_bar.focus_in()
223
224     def focus_out(self, ic):
225         self.reset()
226         self.__focus_ic = None
227         self.__language_bar.set_enabled(False)
228         self.__language_bar.focus_out()
229         self.__set_im_icon(ICON_KEYBOARD)
230         self.__set_im_name(None)
231
232     def state_changed(self):
233         if not self.__focus_ic:
234             return
235
236         enabled = self.__focus_ic.is_enabled()
237         self.__language_bar.set_enabled(enabled)
238
239         if enabled == False:
240             self.reset()
241             self.__set_im_icon(ICON_KEYBOARD)
242             self.__set_im_name(None)
243         else:
244             engine = self.__focus_ic.get_engine()
245             if engine:
246                 self.__set_im_icon(engine.icon)
247                 self.__set_im_name(engine.longname)
248             else:
249                 self.__set_im_icon(ICON_KEYBOARD)
250                 self.__set_im_name(None)
251
252
253     def reset(self):
254         self.__candidate_panel.reset()
255         self.__language_bar.reset()
256
257     def start_setup(self):
258         self.__start_setup()
259
260     def do_destroy(self):
261         gtk.main_quit()
262
263     def __config_load_lookup_table_orientation(self):
264         value = self.__config.get_value("panel", "lookup_table_orientation", 0)
265         if value in (ibus.ORIENTATION_HORIZONTAL, ibus.ORIENTATION_VERTICAL):
266             orientation = value
267         else:
268             orientation = ibus.ORIENTATION_HORIZONTAL
269         self.__candidate_panel.set_orientation(orientation)
270
271     def __config_load_show(self):
272         show = self.__config.get_value("panel", "show", 0)
273         self.__language_bar.set_show(show)
274
275     def __config_load_position(self):
276         x = self.__config.get_value("panel", "x", -1)
277         y = self.__config.get_value("panel", "y", -1)
278         self.__language_bar.set_position(x, y)
279
280     def __config_load_custom_font(self):
281         use_custom_font = self.__config.get_value("panel", "use_custom_font", False)
282         font_name = gtk.settings_get_default().get_property("gtk-font-name")
283         font_name = unicode(font_name, "utf-8")
284         custom_font =  self.__config.get_value("panel", "custom_font", font_name)
285         style_string = 'style "custom-font" { font_name="%s" }\n' \
286             'class "IBusCandidateLabel" style "custom-font"\n'
287         if use_custom_font:
288             style_string = style_string % custom_font
289             gtk.rc_parse_string(style_string)
290         else:
291             style_string = style_string % ""
292             gtk.rc_parse_string(style_string)
293
294         settings = gtk.settings_get_default()
295         gtk.rc_reset_styles(settings)
296
297     def __config_load_show_icon_on_systray(self):
298         value = self.__config.get_value("panel", "show_icon_on_systray", True)
299         self.__status_icon.set_visible(True if value else False)
300
301     def __config_load_show_im_name(self):
302         value = self.__config.get_value("panel", "show_im_name", False)
303         self.__language_bar.set_show_im_name(value)
304
305     def __config_value_changed_cb(self, bus, section, name, value):
306         if section != "panel":
307             return
308         if name == "lookup_table_orientation":
309             self.__config_load_lookup_table_orientation()
310         elif name == "show":
311             self.__config_load_show()
312         elif name == "use_custom_font" or name == "custom_font":
313             self.__config_load_custom_font()
314         elif name == "show_icon_on_systray":
315             self.__config_load_show_icon_on_systray()
316         elif name == "show_im_name":
317             self.__config_load_show_im_name()
318         elif name == "x" or name == "y":
319             pass
320         else:
321             print >> sys.stderr, "Unknown config item [%s]" % name
322
323     def __config_reloaded_cb(self, bus):
324         pass
325
326     def __create_sys_menu(self):
327         menu = gtk.Menu()
328         item = gtk.ImageMenuItem(gtk.STOCK_PREFERENCES)
329         item.connect("activate",
330             self.__sys_menu_item_activate_cb, gtk.STOCK_PREFERENCES)
331         menu.add(item)
332         item = gtk.ImageMenuItem(gtk.STOCK_ABOUT)
333         item.connect("activate",
334             self.__sys_menu_item_activate_cb, gtk.STOCK_ABOUT)
335         menu.add(item)
336         menu.add(gtk.SeparatorMenuItem())
337         item = gtk.MenuItem(_("Restart"))
338         item.connect("activate",
339             self.__sys_menu_item_activate_cb, "Restart")
340         menu.add(item)
341         item = gtk.ImageMenuItem(gtk.STOCK_QUIT)
342         item.connect("activate",
343             self.__sys_menu_item_activate_cb, gtk.STOCK_QUIT)
344         menu.add(item)
345
346         menu.show_all()
347         menu.set_take_focus(False)
348         return menu
349
350     # def __create_im_menu(self):
351     #     menu = gtk.Menu()
352     #     engines = self.__bus.list_active_engines()
353
354     #     tmp = {}
355     #     for engine in engines:
356     #         lang = ibus.get_language_name(engine.language)
357     #         if lang not in tmp:
358     #             tmp[lang] = []
359     #         tmp[lang].append(engine)
360
361     #     langs = tmp.keys()
362     #     other = tmp.get(_("Other"), [])
363     #     if _("Other") in tmp:
364     #         langs.remove(_("Other"))
365     #         langs.append(_("Other"))
366
367     #     size = gtk.icon_size_lookup(gtk.ICON_SIZE_MENU)
368     #     for lang in langs:
369     #         if len(tmp[lang]) == 1:
370     #             engine = tmp[lang][0]
371     #             item = gtk.ImageMenuItem("%s - %s" % (lang, engine.longname))
372     #             if engine.icon:
373     #                 item.set_image(_icon.IconWidget(engine.icon, size[0]))
374     #             else:
375     #                 item.set_image(_icon.IconWidget(ICON_ENGINE, size[0]))
376     #             item.connect("activate", self.__im_menu_item_activate_cb, engine)
377     #             menu.add(item)
378     #         else:
379     #             item = gtk.MenuItem(lang)
380     #             menu.add(item)
381     #             submenu = gtk.Menu()
382     #             item.set_submenu(submenu)
383     #             for engine in tmp[lang]:
384     #                 item = gtk.ImageMenuItem(engine.longname)
385     #                 if engine.icon:
386     #                     item.set_image(_icon.IconWidget(engine.icon, size[0]))
387     #                 else:
388     #                     item.set_image(_icon.IconWidget(ICON_ENGINE, size[0]))
389     #                 item.connect("activate", self.__im_menu_item_activate_cb, engine)
390     #                 submenu.add(item)
391
392     #     item = gtk.ImageMenuItem(_("Turn off input method"))
393     #     item.set_image(_icon.IconWidget("gtk-close", size[0]))
394     #     item.connect("activate", self.__im_menu_item_activate_cb, None)
395     #     menu.add(item)
396
397     #     menu.show_all()
398     #     menu.set_take_focus(False)
399     #     return menu
400
401     def __create_im_menu(self):
402         engines = self.__bus.list_active_engines()
403         current_engine = \
404             (self.__focus_ic != None and self.__focus_ic.get_engine()) or \
405             (engines and engines[0]) or \
406             None
407
408         size = gtk.icon_size_lookup(gtk.ICON_SIZE_MENU)
409         menu = gtk.Menu()
410         for i, engine in enumerate(engines):
411             lang = ibus.get_language_name(engine.language)
412             item = gtk.ImageMenuItem("%s - %s" % (lang, engine.longname))
413             if current_engine and current_engine.name == engine.name:
414                 for widget in item.get_children():
415                     if isinstance(widget, gtk.Label):
416                         widget.set_markup("<b>%s</b>" % widget.get_text())
417             if engine.icon:
418                 item.set_image(_icon.IconWidget(engine.icon, size[0]))
419             else:
420                 item.set_image(_icon.IconWidget(ICON_ENGINE, size[0]))
421             item.connect("activate", self.__im_menu_item_activate_cb, engine)
422             menu.add(item)
423
424         item = gtk.ImageMenuItem(_("Turn off input method"))
425         item.set_image(_icon.IconWidget("gtk-close", size[0]))
426         item.connect("activate", self.__im_menu_item_activate_cb, None)
427         if self.__focus_ic == None or not self.__focus_ic.is_enabled():
428             item.set_sensitive(False)
429         menu.add(item)
430
431         menu.show_all()
432         menu.set_take_focus(False)
433         return menu
434
435     def __get_im_menu_cb(self, languagebar):
436         menu = self.__create_im_menu()
437         return menu
438
439     def __show_engine_about_cb(self, langagebar):
440         try:
441             engine = self.__focus_ic.get_engine()
442             dlg = EngineAbout(engine)
443             dlg.run()
444             dlg.destroy()
445         except:
446             pass
447
448     def __position_changed_cb(self, langagebar, x, y):
449         self.__config.set_value("panel", "x", x)
450         self.__config.set_value("panel", "y", y)
451
452     def __status_icon_popup_menu_cb(self, status_icon, button, active_time):
453         menu = self.__create_sys_menu()
454         menu.popup(None, None,
455                 gtk.status_icon_position_menu,
456                 button,
457                 active_time,
458                 self.__status_icon)
459
460     def __status_icon_activate_cb(self, status_icon):
461         if not self.__focus_ic:
462             menu = gtk.Menu()
463             item = gtk.ImageMenuItem(_("No input window"))
464             size = gtk.icon_size_lookup(gtk.ICON_SIZE_MENU)
465             item.set_image(_icon.IconWidget("gtk-info", size[0]))
466             menu.add(item)
467             menu.show_all()
468         else:
469             menu = self.__create_im_menu()
470             self.__language_bar.create_im_menu(menu)
471         menu.popup(None, None,
472                 gtk.status_icon_position_menu,
473                 0,
474                 gtk.get_current_event_time(),
475                 self.__status_icon)
476
477     def __im_menu_item_activate_cb(self, item, engine):
478         if not self.__focus_ic:
479             return
480         if engine:
481             self.__focus_ic.set_engine(engine)
482         else:
483             self.__focus_ic.disable()
484
485     def __sys_menu_item_activate_cb(self, item, command):
486         if command == gtk.STOCK_PREFERENCES:
487             self.__start_setup()
488         elif command == gtk.STOCK_ABOUT:
489             about_dialog = gtk.AboutDialog()
490             about_dialog.set_program_name("IBus")
491             about_dialog.set_version(ibus.get_version())
492             about_dialog.set_copyright(ibus.get_copyright())
493             about_dialog.set_license(ibus.get_license())
494             about_dialog.set_comments(_("IBus is an intelligent input bus for Linux/Unix."))
495             about_dialog.set_website("http://code.google.com/p/ibus")
496             about_dialog.set_authors(["Peng Huang <shawn.p.huang@gmail.com>"])
497             about_dialog.set_documenters(["Peng Huang <shawn.p.huang@gmail.com>"])
498             about_dialog.set_translator_credits(_("translator-credits"))
499             about_dialog.set_logo_icon_name("ibus")
500             about_dialog.set_icon_name("ibus")
501             about_dialog.run()
502             about_dialog.destroy()
503         elif command == gtk.STOCK_QUIT:
504             self.__bus.exit(False)
505         elif command == "Restart":
506             self.__bus.exit(True)
507         else:
508             print >> sys.stderr, "Unknown command %s" % command
509
510     def __child_watch_cb(self, pid, status):
511         if self.__setup_pid == pid:
512             self.__setup_pid.close()
513             self.__setup_pid = None
514
515     def __start_setup(self):
516         if self.__setup_pid != None:
517             try:
518                 # if setup dialog is running, bring the dialog to front by SIGUSR1
519                 os.kill(self.__setup_pid, signal.SIGUSR1)
520                 return
521             except OSError:
522                 # seems the setup dialog is not running anymore
523                 self.__setup_pid.close()
524                 self.__setup_pid = None
525
526         pid = glib.spawn_async(argv=[self.__setup_cmd, "ibus-setup"],
527                                flags=glib.SPAWN_DO_NOT_REAP_CHILD)[0]
528         self.__setup_pid = pid
529         glib.child_watch_add(self.__setup_pid, self.__child_watch_cb)