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