Fix problem in ibus-setup
[platform/upstream/ibus.git] / setup / main.py
1 # vim:set et sts=4 sw=4:
2 #
3 # ibus - The Input Bus
4 #
5 # Copyright (c) 2007-2008 Huang Peng <shawn.p.huang@gmail.com>
6 #
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.
11 #
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.
16 #
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
21
22 import locale
23 import gettext
24 import os
25 import sys
26 import time
27 import gtk
28 import gobject
29 import ibus
30 import keyboardshortcut
31 from os import path
32 from xdg import BaseDirectory
33 from gtk import gdk
34 from gtk import glade
35
36 _  = lambda a : gettext.dgettext("ibus", a)
37 N_ = lambda a : a
38
39 (
40     COLUMN_NAME,
41     COLUMN_ENABLE,
42     COLUMN_PRELOAD,
43     COLUMN_VISIBLE,
44     COLUMN_ICON,
45     COLUMN_DATA,
46 ) = range(6)
47
48 (
49     DATA_NAME,
50     DATA_LOCAL_NAME,
51     DATA_LANG,
52     DATA_ICON,
53     DATA_AUTHOR,
54     DATA_CREDITS,
55     DATA_EXEC,
56     DATA_STARTED,
57     DATA_PRELOAD
58 ) = range(9)
59
60 class Setup(object):
61     def __flush_gtk_events(self):
62         while gtk.events_pending():
63             gtk.main_iteration()
64
65     def __init__(self):
66         super(Setup, self).__init__()
67         locale.bind_textdomain_codeset("ibus", "UTF-8")
68         glade.textdomain("ibus")
69         glade_file = path.join(path.dirname(__file__), "./setup.glade")
70         self.__xml = glade.XML(glade_file)
71         self.__bus = None
72         self.__init_bus()
73         self.__init_ui()
74
75     def __init_ui(self):
76         # add icon search path
77         icon_theme = gtk.icon_theme_get_default()
78         dir = path.dirname(__file__)
79         icondir = path.join(dir, "..", "icons")
80         icon_theme.prepend_search_path(icondir)
81
82
83         self.__dialog = self.__xml.get_widget("dialog_setup")
84
85         # auto start ibus
86         self.__checkbutton_auto_start = self.__xml.get_widget("checkbutton_auto_start")
87         self.__checkbutton_auto_start.set_active(self.__is_auto_start())
88         self.__checkbutton_auto_start.connect("toggled", self.__checkbutton_auto_start_toggled_cb)
89
90         # keyboard shortcut
91         # trigger
92         shortcuts = self.__bus.config_get_value(
93                         "general", "keyboard_shortcut_trigger",
94                         ibus.CONFIG_GENERAL_SHORTCUT_TRIGGER_DEFAULT)
95         button = self.__xml.get_widget("button_trigger")
96         entry = self.__xml.get_widget("entry_trigger")
97         entry.set_text("; ".join(shortcuts))
98         button.connect("clicked", self.__shortcut_button_clicked_cb,
99                     N_("trigger"), "general", "keyboard_shortcut_trigger", entry)
100
101         # next engine
102         shortcuts = self.__bus.config_get_value(
103                         "general", "keyboard_shortcut_next_engine",
104                         ibus.CONFIG_GENERAL_SHORTCUT_NEXT_ENGINE_DEFAULT)
105         button = self.__xml.get_widget("button_next_engine")
106         entry = self.__xml.get_widget("entry_next_engine")
107         entry.set_text("; ".join(shortcuts))
108         button.connect("clicked", self.__shortcut_button_clicked_cb,
109                     N_("next engine"), "general", "keyboard_shortcut_next_engine", entry)
110
111         # prev engine
112         shortcuts = self.__bus.config_get_value(
113                         "general", "keyboard_shortcut_prev_engine",
114                         ibus.CONFIG_GENERAL_SHORTCUT_PREV_ENGINE_DEFAULT)
115         button = self.__xml.get_widget("button_prev_engine")
116         entry = self.__xml.get_widget("entry_prev_engine")
117         entry.set_text("; ".join(shortcuts))
118         button.connect("clicked", self.__shortcut_button_clicked_cb,
119                     N_("prev engine"), "general", "keyboard_shortcut_prev_engine", entry)
120
121
122
123         # lookup table orientation
124         self.__combobox_lookup_table_orientation = self.__xml.get_widget("combobox_lookup_table_orientation")
125         self.__combobox_lookup_table_orientation.set_active(
126             self.__bus.config_get_value("panel", "lookup_table_orientation", 0))
127         self.__combobox_lookup_table_orientation.connect("changed",
128             self.__combobox_lookup_table_orientation_changed_cb)
129
130         # auto hide
131         self.__checkbutton_auto_hide = self.__xml.get_widget("checkbutton_auto_hide")
132         self.__checkbutton_auto_hide.set_active(
133             self.__bus.config_get_value("panel", "auto_hide", False))
134         self.__checkbutton_auto_hide.connect("toggled", self.__checkbutton_auto_hide_toggled_cb)
135
136         # custom font
137         self.__checkbutton_custom_font = self.__xml.get_widget("checkbutton_custom_font")
138         self.__checkbutton_custom_font.set_active(
139             self.__bus.config_get_value("panel", "use_custom_font", False))
140         self.__checkbutton_custom_font.connect("toggled", self.__checkbutton_custom_font_toggled_cb)
141
142         self.__fontbutton_custom_font = self.__xml.get_widget("fontbutton_custom_font")
143         if self.__bus.config_get_value("panel", "use_custom_font", False):
144             self.__fontbutton_custom_font.set_sensitive(True)
145         else:
146             self.__fontbutton_custom_font.set_sensitive(False)
147         font_name = gtk.settings_get_default().get_property("gtk-font-name")
148         font_name = unicode(font_name, "utf-8")
149         font_name = self.__bus.config_get_value("panel", "custom_font", font_name)
150         self.__fontbutton_custom_font.connect("notify::font-name", self.__fontbutton_custom_font_notify_cb)
151         self.__fontbutton_custom_font.set_font_name(font_name)
152
153         self.__init_engine_view()
154
155     def __init_bus(self):
156         try:
157             self.__bus = ibus.Bus()
158             # self.__bus.connect("config-value-changed", self.__config_value_changed_cb)
159             self.__bus.connect("config-reloaded", self.__config_reloaded_cb)
160             self.__bus.config_add_watch("/general")
161             self.__bus.config_add_watch("/panel")
162         except:
163             while self.__bus == None:
164                 message = _("IBus daemon is not started. Do you want to start it now?")
165                 dlg = gtk.MessageDialog(type = gtk.MESSAGE_QUESTION,
166                         buttons = gtk.BUTTONS_YES_NO,
167                         message_format = message)
168                 id = dlg.run()
169                 dlg.destroy()
170                 self.__flush_gtk_events()
171                 if id != gtk.RESPONSE_YES:
172                     sys.exit(0)
173                 pid = os.spawnlp(os.P_NOWAIT, "ibus", "ibus")
174                 time.sleep(1)
175                 try:
176                     self.__bus = ibus.Bus()
177                 except:
178                     continue
179                 message = _("IBus has been started! "
180                     "If you can not use IBus, please add below lines in $HOME/.bashrc, and relogin your desktop.\n"
181                     "  export GTK_IM_MODULE=ibus\n"
182                     "  export XMODIFIERS=@im=ibus\n"
183                     "  export QT_IM_MODULE=ibus"
184                     )
185                 dlg = gtk.MessageDialog(type = gtk.MESSAGE_INFO,
186                                         buttons = gtk.BUTTONS_OK,
187                                         message_format = message)
188                 id = dlg.run()
189                 dlg.destroy()
190                 self.__flush_gtk_events()
191
192     def __init_engine_view(self):
193         # engines tree
194         self.__tree = self.__xml.get_widget("treeview_engines")
195         self.__preload_engines = set(self.__bus.config_get_value("general", "preload_engines", []))
196         model = self.__create_model()
197         self.__tree.set_model(model)
198
199         # column for engine
200         column = gtk.TreeViewColumn()
201         column.set_title(_("Engine"))
202
203         renderer = gtk.CellRendererPixbuf()
204         renderer.set_property("xalign", 0.5)
205
206         column.pack_start(renderer)
207         column.set_attributes(renderer, pixbuf = COLUMN_ICON, visible = COLUMN_VISIBLE)
208
209         renderer = gtk.CellRendererText()
210         renderer.set_property("xalign", 0.0)
211
212         # column.set_clickable(True)
213         column.pack_start(renderer)
214         column.set_attributes(renderer, text = COLUMN_NAME)
215
216         self.__tree.append_column(column)
217
218         # column for started
219         renderer = gtk.CellRendererToggle()
220         renderer.set_data('column', COLUMN_ENABLE)
221         renderer.set_property("xalign", 0.5)
222         renderer.connect("toggled", self.__item_started_column_toggled_cb, model)
223
224         #col_offset = gtk.TreeViewColumn("Holiday", renderer, text=HOLIDAY_NAME)
225         column = gtk.TreeViewColumn(_("Started"), renderer, active = COLUMN_ENABLE, visible = COLUMN_VISIBLE)
226         self.__tree.append_column(column)
227
228         # column for preload
229         renderer = gtk.CellRendererToggle()
230         renderer.set_data('column', COLUMN_PRELOAD)
231         renderer.set_property("xalign", 0.5)
232         renderer.connect("toggled", self.__item_preload_column_toggled_cb, model)
233
234         column = gtk.TreeViewColumn(_("Preload"), renderer, active = COLUMN_PRELOAD, visible = COLUMN_VISIBLE)
235         self.__tree.append_column(column)
236
237         renderer = gtk.CellRendererText()
238         column = gtk.TreeViewColumn("", renderer)
239         self.__tree.append_column(column)
240
241     def __shortcut_button_clicked_cb(self, button, name, section, _name, entry):
242         buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK)
243         title = _("Select keyboard shortcut for %s") %  _(name)
244         dialog = keyboardshortcut.KeyboardShortcutSelectionDialog(buttons = buttons, title = title)
245         text = entry.get_text()
246         if text:
247             shortcuts = text.split("; ")
248         else:
249             shortcuts = None
250         dialog.set_shortcuts(shortcuts)
251         id = dialog.run()
252         shortcuts = list(set(dialog.get_shortcuts()))
253         dialog.destroy()
254         if id != gtk.RESPONSE_OK:
255             return
256         self.__bus.config_set_value(section, name, shortcuts)
257         entry.set_text("; ".join(shortcuts))
258
259
260     def __item_started_column_toggled_cb(self, cell, path_str, model):
261
262         # get toggled iter
263         iter = model.get_iter_from_string(path_str)
264         data = model.get_value(iter, COLUMN_DATA)
265
266         # do something with the value
267         if data[DATA_STARTED] == False:
268             try:
269                 self.__bus.register_start_engine(data[DATA_LANG], data[DATA_NAME])
270             except Exception, e:
271                 dlg = gtk.MessageDialog(type = gtk.MESSAGE_ERROR,
272                         buttons = gtk.BUTTONS_CLOSE,
273                         message_format = str(e))
274                 dlg.run()
275                 dlg.destroy()
276                 self.__flush_gtk_events()
277                 return
278         else:
279             try:
280                 self.__bus.register_stop_engine(data[DATA_LANG], data[DATA_NAME])
281             except Exception, e:
282                 dlg = gtk.MessageDialog(type = gtk.MESSAGE_ERROR,
283                         buttons = gtk.BUTTONS_CLOSE,
284                         message_format = str(e))
285                 dlg.run()
286                 dlg.destroy()
287                 self.__flush_gtk_events()
288                 return
289         data[DATA_STARTED] = not data[DATA_STARTED]
290
291         # set new value
292         model.set(iter, COLUMN_ENABLE, data[DATA_STARTED])
293
294     def __item_preload_column_toggled_cb(self, cell, path_str, model):
295
296         # get toggled iter
297         iter = model.get_iter_from_string(path_str)
298         data = model.get_value(iter, COLUMN_DATA)
299
300         data[DATA_PRELOAD] = not data[DATA_PRELOAD]
301         engine = "%s:%s" % (data[DATA_LANG], data[DATA_NAME])
302
303         if data[DATA_PRELOAD]:
304             if engine not in self.__preload_engines:
305                 self.__preload_engines.add(engine)
306                 self.__bus.config_set_list("general", "preload_engines", list(self.__preload_engines), "s")
307         else:
308             if engine in self.__preload_engines:
309                 self.__preload_engines.remove(engine)
310                 self.__bus.config_set_list("general", "preload_engines", list(self.__preload_engines), "s")
311
312         # set new value
313         model.set(iter, COLUMN_PRELOAD, data[DATA_PRELOAD])
314
315     def __load_icon(self, icon, icon_size):
316         pixbuf = None
317         try:
318             pixbuf = gdk.pixbuf_new_from_file(icon)
319             w, h = pixbuf.get_width(), pixbuf.get_height()
320             rate = max(w, h) / float(icon_size)
321             w = int(w / rate)
322             h = int(h / rate)
323             pixbuf = pixbuf.scale_simple(w, h, gdk.INTERP_BILINEAR)
324         except:
325             pass
326         if pixbuf == None:
327             try:
328                 theme = gtk.icon_theme_get_default()
329                 pixbuf = theme.load_icon(icon, icon_size, 0)
330             except:
331                 pass
332         return pixbuf
333
334     def __create_model(self):
335         # create tree store
336         model = gtk.TreeStore(
337             gobject.TYPE_STRING,
338             gobject.TYPE_BOOLEAN,
339             gobject.TYPE_BOOLEAN,
340             gobject.TYPE_BOOLEAN,
341             gdk.Pixbuf,
342             gobject.TYPE_PYOBJECT)
343
344         langs = dict()
345
346         self.__bus.register_reload_engines()
347         for name, local_name, lang, icon, author, credits, _exec, started in self.__bus.register_list_engines():
348             _lang = ibus.get_language_name(lang)
349             if _lang not in langs:
350                 langs[_lang] = list()
351             langs[_lang].append([name, local_name, lang, icon, author, credits, _exec, started])
352
353         keys = langs.keys()
354         keys.sort()
355         if _("Other") in keys:
356             keys.remove(_("Other"))
357             keys.append(_("Other"))
358
359         icon_size = gtk.icon_size_lookup(gtk.ICON_SIZE_LARGE_TOOLBAR)[0]
360         pixbuf_missing = self.__load_icon("engine-default", icon_size)
361         if pixbuf_missing == None:
362             pixbuf_missing = self.__load_icon("gtk-missing-image", icon_size)
363
364         for key in keys:
365             iter = model.append(None)
366             model.set(iter,
367                 COLUMN_NAME, key,
368                 COLUMN_ENABLE, False,
369                 COLUMN_PRELOAD, False,
370                 COLUMN_VISIBLE, False,
371                 COLUMN_ICON, None,
372                 COLUMN_DATA, None)
373             langs[key].sort()
374
375             for name, local_name, lang, icon, author, credits, _exec, started in langs[key]:
376                 child_iter = model.append(iter)
377                 is_preload = "%s:%s" % (lang, name) in self.__preload_engines
378
379                 pixbuf = self.__load_icon(icon, icon_size)
380                 if pixbuf == None:
381                     pixbuf = pixbuf_missing
382
383                 model.set(child_iter,
384                     COLUMN_NAME, local_name,
385                     COLUMN_ENABLE, started,
386                     COLUMN_PRELOAD, is_preload,
387                     COLUMN_VISIBLE, True,
388                     COLUMN_ICON, pixbuf,
389                     COLUMN_DATA,
390                     [name, local_name, lang, icon, author, credits, _exec, started, is_preload])
391
392         return model
393
394     def __is_auto_start(self):
395         link_file = path.join(BaseDirectory.xdg_config_home, "autostart/ibus.desktop")
396         ibus_desktop = path.join(os.getenv("IBUS_PREFIX"), "share/applications/ibus.desktop")
397
398         if not path.exists(link_file):
399             return False
400         if not path.islink(link_file):
401             return False
402         if path.realpath(link_file) != ibus_desktop:
403             return False
404         return True
405
406     def __checkbutton_auto_start_toggled_cb(self, button):
407         auto_start_dir = path.join(BaseDirectory.xdg_config_home, "autostart")
408         if not path.isdir(auto_start_dir):
409             os.makedirs(auto_start_dir)
410
411         link_file = path.join(BaseDirectory.xdg_config_home, "autostart/ibus.desktop")
412         ibus_desktop = path.join(os.getenv("IBUS_PREFIX"), "share/applications/ibus.desktop")
413         # unlink file
414         try:
415             os.unlink(link_file)
416         except:
417             pass
418         if self.__checkbutton_auto_start.get_active():
419             os.symlink(ibus_desktop, link_file)
420
421     def __combobox_lookup_table_orientation_changed_cb(self, combobox):
422         self.__bus.config_set_value(
423             "panel", "lookup_table_orientation",
424             self.__combobox_lookup_table_orientation.get_active())
425
426     def __checkbutton_auto_hide_toggled_cb(self, button):
427         self.__bus.config_set_value(
428             "panel", "auto_hide",
429             self.__checkbutton_auto_hide.get_active())
430
431     def __checkbutton_custom_font_toggled_cb(self, button):
432         if self.__checkbutton_custom_font.get_active():
433             self.__fontbutton_custom_font.set_sensitive(True)
434             self.__bus.config_set_value("panel", "use_custom_font", True)
435         else:
436             self.__fontbutton_custom_font.set_sensitive(False)
437             self.__bus.config_set_value("panel", "use_custom_font", False)
438
439     def __fontbutton_custom_font_notify_cb(self, button, arg):
440         font_name = self.__fontbutton_custom_font.get_font_name()
441         font_name = unicode(font_name, "utf-8")
442         self.__bus.config_set_value("panel", "custom_font", font_name)
443
444     def __config_value_changed_cb(self, bus, section, name, value):
445         pass
446
447     def __config_reloaded_cb(self, bus):
448         pass
449
450     def run(self):
451         return self.__dialog.run()
452
453 if __name__ == "__main__":
454     Setup().run()