Use gtk_status_icon_set_name() only if it is available.
[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         # gtk_status_icon_set_name() is not available in pygtk2 2.17
114         if hasattr(self.__status_icon, 'set_name'):
115             self.__status_icon.set_name('ibus-ui-gtk')
116         self.__status_icon.set_title(_("IBus Panel"))
117         self.__status_icon.set_visible(True)
118         self.__status_icon.connect("popup-menu", self.__status_icon_popup_menu_cb)
119         self.__status_icon.connect("activate", self.__status_icon_activate_cb)
120         self.__status_icon.set_from_icon_name(ICON_KEYBOARD)
121         self.__status_icon.set_tooltip(_("IBus input method framework"))
122         self.__status_icon.set_visible(True)
123
124         self.__config_load_lookup_table_orientation()
125         self.__config_load_show()
126         self.__config_load_position()
127         self.__config_load_custom_font()
128         self.__config_load_show_icon_on_systray()
129         self.__config_load_show_im_name()
130         # self.__bus.request_name(ibus.panel.IBUS_SERVICE_PANEL, 0)
131
132     def set_cursor_location(self, x, y, w, h):
133         self.__candidate_panel.set_cursor_location(x, y, w, h)
134
135     def update_preedit_text(self, text, cursor_pos, visible):
136         self.__candidate_panel.update_preedit_text(text, cursor_pos, visible)
137
138     def show_preedit_text(self):
139         self.__candidate_panel.show_preedit_text()
140
141     def hide_preedit_text(self):
142         self.__candidate_panel.hide_preedit_text()
143
144     def update_auxiliary_text(self, text, visible):
145         self.__candidate_panel.update_auxiliary_text(text, visible)
146
147     def show_auxiliary_text(self):
148         self.__candidate_panel.show_auxiliary_text()
149
150     def hide_auxiliary_text(self):
151         self.__candidate_panel.hide_auxiliary_text()
152
153     def update_lookup_table(self, lookup_table, visible):
154         self.__candidate_panel.update_lookup_table(lookup_table, visible)
155
156     def show_lookup_table(self):
157         self.__candidate_panel.show_lookup_table()
158
159     def hide_lookup_table(self):
160         self.__candidate_panel.hide_lookup_table()
161
162     def page_up_lookup_table(self):
163         self.__candidate_panel.page_up_lookup_table()
164
165     def page_down_lookup_table(self):
166         self.__candidate_panel.page_down_lookup_table()
167
168     def cursor_up_lookup_table(self):
169         self.__candidate_panel.cursor_up_lookup_table()
170
171     def cursor_down_lookup_table(self):
172         self.__candidate_panel.cursor_down_lookup_table()
173
174     def show_candidate_window(self):
175         self.__candidate_panel.show_all()
176
177     def hide_candidate_window(self):
178         self.__candidate_panel.hide_all()
179
180     def show_language_bar(self):
181         self.__language_bar.show_all()
182
183     def hide_language_bar(self):
184         self.__language_bar.hide_all()
185
186     def register_properties(self, props):
187         self.__language_bar.register_properties(props)
188
189     def update_property(self, prop):
190         self.__language_bar.update_property(prop)
191
192     def get_status_icon(self):
193         return self.__status_icon
194
195     def __set_im_icon(self, icon_name):
196         if not icon_name:
197             icon_name = ICON_ENGINE
198         self.__language_bar.set_im_icon(icon_name)
199         if icon_name.startswith("/"):
200             self.__status_icon.set_from_file(icon_name)
201         else:
202             self.__status_icon.set_from_icon_name(icon_name)
203
204     def __set_im_name(self, name):
205         self.__language_bar.set_im_name(name)
206
207     def focus_in(self, ic):
208         self.reset()
209         self.__focus_ic = ibus.InputContext(self.__bus, ic)
210         enabled = self.__focus_ic.is_enabled()
211         self.__language_bar.set_enabled(enabled)
212
213         if not enabled:
214             self.__set_im_icon(ICON_KEYBOARD)
215             self.__set_im_name(None)
216         else:
217             engine = self.__focus_ic.get_engine()
218             if engine:
219                 self.__set_im_icon(engine.icon)
220                 self.__set_im_name(engine.longname)
221             else:
222                 self.__set_im_icon(ICON_KEYBOARD)
223                 self.__set_im_name(None)
224         self.__language_bar.focus_in()
225
226     def focus_out(self, ic):
227         self.reset()
228         self.__focus_ic = None
229         self.__language_bar.set_enabled(False)
230         self.__language_bar.focus_out()
231         self.__set_im_icon(ICON_KEYBOARD)
232         self.__set_im_name(None)
233
234     def state_changed(self):
235         if not self.__focus_ic:
236             return
237
238         enabled = self.__focus_ic.is_enabled()
239         self.__language_bar.set_enabled(enabled)
240
241         if enabled == False:
242             self.reset()
243             self.__set_im_icon(ICON_KEYBOARD)
244             self.__set_im_name(None)
245         else:
246             engine = self.__focus_ic.get_engine()
247             if engine:
248                 self.__set_im_icon(engine.icon)
249                 self.__set_im_name(engine.longname)
250             else:
251                 self.__set_im_icon(ICON_KEYBOARD)
252                 self.__set_im_name(None)
253
254
255     def reset(self):
256         self.__candidate_panel.reset()
257         self.__language_bar.reset()
258
259     def start_setup(self):
260         self.__start_setup()
261
262     def do_destroy(self):
263         gtk.main_quit()
264
265     def __config_load_lookup_table_orientation(self):
266         value = self.__config.get_value("panel", "lookup_table_orientation", 0)
267         if value in (ibus.ORIENTATION_HORIZONTAL, ibus.ORIENTATION_VERTICAL):
268             orientation = value
269         else:
270             orientation = ibus.ORIENTATION_HORIZONTAL
271         self.__candidate_panel.set_orientation(orientation)
272
273     def __config_load_show(self):
274         show = self.__config.get_value("panel", "show", 0)
275         self.__language_bar.set_show(show)
276
277     def __config_load_position(self):
278         x = self.__config.get_value("panel", "x", -1)
279         y = self.__config.get_value("panel", "y", -1)
280         self.__language_bar.set_position(x, y)
281
282     def __config_load_custom_font(self):
283         use_custom_font = self.__config.get_value("panel", "use_custom_font", False)
284         font_name = gtk.settings_get_default().get_property("gtk-font-name")
285         font_name = unicode(font_name, "utf-8")
286         custom_font =  self.__config.get_value("panel", "custom_font", font_name)
287         style_string = 'style "custom-font" { font_name="%s" }\n' \
288             'class "IBusCandidateLabel" style "custom-font"\n'
289         if use_custom_font:
290             style_string = style_string % custom_font
291             gtk.rc_parse_string(style_string)
292         else:
293             style_string = style_string % ""
294             gtk.rc_parse_string(style_string)
295
296         settings = gtk.settings_get_default()
297         gtk.rc_reset_styles(settings)
298
299     def __config_load_show_icon_on_systray(self):
300         value = self.__config.get_value("panel", "show_icon_on_systray", True)
301         self.__status_icon.set_visible(True if value else False)
302
303     def __config_load_show_im_name(self):
304         value = self.__config.get_value("panel", "show_im_name", False)
305         self.__language_bar.set_show_im_name(value)
306
307     def __config_value_changed_cb(self, bus, section, name, value):
308         if section != "panel":
309             return
310         if name == "lookup_table_orientation":
311             self.__config_load_lookup_table_orientation()
312         elif name == "show":
313             self.__config_load_show()
314         elif name == "use_custom_font" or name == "custom_font":
315             self.__config_load_custom_font()
316         elif name == "show_icon_on_systray":
317             self.__config_load_show_icon_on_systray()
318         elif name == "show_im_name":
319             self.__config_load_show_im_name()
320         elif name == "x" or name == "y":
321             pass
322         else:
323             print >> sys.stderr, "Unknown config item [%s]" % name
324
325     def __config_reloaded_cb(self, bus):
326         pass
327
328     def __create_sys_menu(self):
329         menu = gtk.Menu()
330         item = gtk.ImageMenuItem(gtk.STOCK_PREFERENCES)
331         item.connect("activate",
332             self.__sys_menu_item_activate_cb, gtk.STOCK_PREFERENCES)
333         menu.add(item)
334         item = gtk.ImageMenuItem(gtk.STOCK_ABOUT)
335         item.connect("activate",
336             self.__sys_menu_item_activate_cb, gtk.STOCK_ABOUT)
337         menu.add(item)
338         menu.add(gtk.SeparatorMenuItem())
339         item = gtk.MenuItem(_("Restart"))
340         item.connect("activate",
341             self.__sys_menu_item_activate_cb, "Restart")
342         menu.add(item)
343         item = gtk.ImageMenuItem(gtk.STOCK_QUIT)
344         item.connect("activate",
345             self.__sys_menu_item_activate_cb, gtk.STOCK_QUIT)
346         menu.add(item)
347
348         menu.show_all()
349         menu.set_take_focus(False)
350         return menu
351
352     # def __create_im_menu(self):
353     #     menu = gtk.Menu()
354     #     engines = self.__bus.list_active_engines()
355
356     #     tmp = {}
357     #     for engine in engines:
358     #         lang = ibus.get_language_name(engine.language)
359     #         if lang not in tmp:
360     #             tmp[lang] = []
361     #         tmp[lang].append(engine)
362
363     #     langs = tmp.keys()
364     #     other = tmp.get(_("Other"), [])
365     #     if _("Other") in tmp:
366     #         langs.remove(_("Other"))
367     #         langs.append(_("Other"))
368
369     #     size = gtk.icon_size_lookup(gtk.ICON_SIZE_MENU)
370     #     for lang in langs:
371     #         if len(tmp[lang]) == 1:
372     #             engine = tmp[lang][0]
373     #             item = gtk.ImageMenuItem("%s - %s" % (lang, engine.longname))
374     #             if engine.icon:
375     #                 item.set_image(_icon.IconWidget(engine.icon, size[0]))
376     #             else:
377     #                 item.set_image(_icon.IconWidget(ICON_ENGINE, size[0]))
378     #             item.connect("activate", self.__im_menu_item_activate_cb, engine)
379     #             menu.add(item)
380     #         else:
381     #             item = gtk.MenuItem(lang)
382     #             menu.add(item)
383     #             submenu = gtk.Menu()
384     #             item.set_submenu(submenu)
385     #             for engine in tmp[lang]:
386     #                 item = gtk.ImageMenuItem(engine.longname)
387     #                 if engine.icon:
388     #                     item.set_image(_icon.IconWidget(engine.icon, size[0]))
389     #                 else:
390     #                     item.set_image(_icon.IconWidget(ICON_ENGINE, size[0]))
391     #                 item.connect("activate", self.__im_menu_item_activate_cb, engine)
392     #                 submenu.add(item)
393
394     #     item = gtk.ImageMenuItem(_("Turn off input method"))
395     #     item.set_image(_icon.IconWidget("gtk-close", size[0]))
396     #     item.connect("activate", self.__im_menu_item_activate_cb, None)
397     #     menu.add(item)
398
399     #     menu.show_all()
400     #     menu.set_take_focus(False)
401     #     return menu
402
403     def __create_im_menu(self):
404         engines = self.__bus.list_active_engines()
405         current_engine = \
406             (self.__focus_ic != None and self.__focus_ic.get_engine()) or \
407             (engines and engines[0]) or \
408             None
409
410         size = gtk.icon_size_lookup(gtk.ICON_SIZE_MENU)
411         menu = gtk.Menu()
412         for i, engine in enumerate(engines):
413             lang = ibus.get_language_name(engine.language)
414             item = gtk.ImageMenuItem("%s - %s" % (lang, engine.longname))
415             if current_engine and current_engine.name == engine.name:
416                 for widget in item.get_children():
417                     if isinstance(widget, gtk.Label):
418                         widget.set_markup("<b>%s</b>" % widget.get_text())
419             if engine.icon:
420                 item.set_image(_icon.IconWidget(engine.icon, size[0]))
421             else:
422                 item.set_image(_icon.IconWidget(ICON_ENGINE, size[0]))
423             item.connect("activate", self.__im_menu_item_activate_cb, engine)
424             menu.add(item)
425
426         item = gtk.ImageMenuItem(_("Turn off input method"))
427         item.set_image(_icon.IconWidget("gtk-close", size[0]))
428         item.connect("activate", self.__im_menu_item_activate_cb, None)
429         if self.__focus_ic == None or not self.__focus_ic.is_enabled():
430             item.set_sensitive(False)
431         menu.add(item)
432
433         menu.show_all()
434         menu.set_take_focus(False)
435         return menu
436
437     def __get_im_menu_cb(self, languagebar):
438         menu = self.__create_im_menu()
439         return menu
440
441     def __show_engine_about_cb(self, langagebar):
442         try:
443             engine = self.__focus_ic.get_engine()
444             dlg = EngineAbout(engine)
445             dlg.run()
446             dlg.destroy()
447         except:
448             pass
449
450     def __position_changed_cb(self, langagebar, x, y):
451         self.__config.set_value("panel", "x", x)
452         self.__config.set_value("panel", "y", y)
453
454     def __status_icon_popup_menu_cb(self, status_icon, button, active_time):
455         menu = self.__create_sys_menu()
456         menu.popup(None, None,
457                 gtk.status_icon_position_menu,
458                 button,
459                 active_time,
460                 self.__status_icon)
461
462     def __status_icon_activate_cb(self, status_icon):
463         if not self.__focus_ic:
464             menu = gtk.Menu()
465             item = gtk.ImageMenuItem(_("No input window"))
466             size = gtk.icon_size_lookup(gtk.ICON_SIZE_MENU)
467             item.set_image(_icon.IconWidget("gtk-info", size[0]))
468             menu.add(item)
469             menu.show_all()
470         else:
471             menu = self.__create_im_menu()
472             self.__language_bar.create_im_menu(menu)
473         menu.popup(None, None,
474                 gtk.status_icon_position_menu,
475                 0,
476                 gtk.get_current_event_time(),
477                 self.__status_icon)
478
479     def __im_menu_item_activate_cb(self, item, engine):
480         if not self.__focus_ic:
481             return
482         if engine:
483             self.__focus_ic.set_engine(engine)
484         else:
485             self.__focus_ic.disable()
486
487     def __sys_menu_item_activate_cb(self, item, command):
488         if command == gtk.STOCK_PREFERENCES:
489             self.__start_setup()
490         elif command == gtk.STOCK_ABOUT:
491             about_dialog = gtk.AboutDialog()
492             about_dialog.set_program_name("IBus")
493             about_dialog.set_version(ibus.get_version())
494             about_dialog.set_copyright(ibus.get_copyright())
495             about_dialog.set_license(ibus.get_license())
496             about_dialog.set_comments(_("IBus is an intelligent input bus for Linux/Unix."))
497             about_dialog.set_website("http://code.google.com/p/ibus")
498             about_dialog.set_authors(["Peng Huang <shawn.p.huang@gmail.com>"])
499             about_dialog.set_documenters(["Peng Huang <shawn.p.huang@gmail.com>"])
500             about_dialog.set_translator_credits(_("translator-credits"))
501             about_dialog.set_logo_icon_name("ibus")
502             about_dialog.set_icon_name("ibus")
503             about_dialog.run()
504             about_dialog.destroy()
505         elif command == gtk.STOCK_QUIT:
506             self.__bus.exit(False)
507         elif command == "Restart":
508             self.__bus.exit(True)
509         else:
510             print >> sys.stderr, "Unknown command %s" % command
511
512     def __child_watch_cb(self, pid, status):
513         if self.__setup_pid == pid:
514             self.__setup_pid.close()
515             self.__setup_pid = None
516
517     def __start_setup(self):
518         if self.__setup_pid != None:
519             try:
520                 # if setup dialog is running, bring the dialog to front by SIGUSR1
521                 os.kill(self.__setup_pid, signal.SIGUSR1)
522                 return
523             except OSError:
524                 # seems the setup dialog is not running anymore
525                 self.__setup_pid.close()
526                 self.__setup_pid = None
527
528         pid = glib.spawn_async(argv=[self.__setup_cmd, "ibus-setup"],
529                                flags=glib.SPAWN_DO_NOT_REAP_CHILD)[0]
530         self.__setup_pid = pid
531         glib.child_watch_add(self.__setup_pid, self.__child_watch_cb)