Fix no gconf engines_order and the engine setup button on 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-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 os
24 import signal
25 import sys
26 import time
27
28 from gi.repository import GLib
29 from gi.repository import Gtk
30 from gi.repository import IBus
31 from os import path
32 from xdg import BaseDirectory
33
34 import keyboardshortcut
35 import locale
36 from enginecombobox import EngineComboBox
37 from enginetreeview import EngineTreeView
38 from engineabout import EngineAbout
39 from i18n import DOMAINNAME, _, N_, init as i18n_init
40
41 (
42     COLUMN_NAME,
43     COLUMN_ENABLE,
44     COLUMN_PRELOAD,
45     COLUMN_VISIBLE,
46     COLUMN_ICON,
47     COLUMN_DATA,
48 ) = range(6)
49
50 (
51     DATA_NAME,
52     DATA_LOCAL_NAME,
53     DATA_LANG,
54     DATA_ICON,
55     DATA_AUTHOR,
56     DATA_CREDITS,
57     DATA_EXEC,
58     DATA_STARTED,
59     DATA_PRELOAD
60 ) = range(9)
61
62 class Setup(object):
63     def __flush_gtk_events(self):
64         while Gtk.events_pending():
65             Gtk.main_iteration()
66
67     def __init__(self):
68         super(Setup, self).__init__()
69         gtk_builder_file = path.join(path.dirname(__file__), "./setup.ui")
70         self.__builder = Gtk.Builder()
71         self.__builder.set_translation_domain(DOMAINNAME)
72         self.__builder.add_from_file(gtk_builder_file);
73         self.__bus = None
74         self.__init_bus()
75         self.__init_ui()
76
77     def __init_hotkey(self):
78         default_values = {
79             "trigger" : (N_("trigger"), ["Control+space"]),
80             "enable_unconditional" : (N_("enable"), []),
81             "disable_unconditional" : (N_("disable"), [])
82         }
83
84         values = dict(self.__config.get_values("general/hotkey"))
85
86         for name, (label, shortcuts) in default_values.items():
87             shortcuts = values.get(name, shortcuts)
88             button = self.__builder.get_object("button_%s" % name)
89             entry = self.__builder.get_object("entry_%s" % name)
90             entry.set_text("; ".join(shortcuts))
91             entry.set_tooltip_text("\n".join(shortcuts))
92             button.connect("clicked", self.__shortcut_button_clicked_cb,
93                     label, "general/hotkey", name, entry)
94
95     def __init_panel(self):
96         values = dict(self.__config.get_values("panel"))
97
98         # lookup table orientation
99         self.__combobox_lookup_table_orientation = self.__builder.get_object(
100                 "combobox_lookup_table_orientation")
101         self.__combobox_lookup_table_orientation.set_active(
102                 values.get("lookup_table_orientation", 0))
103         self.__combobox_lookup_table_orientation.connect("changed",
104                 self.__combobox_lookup_table_orientation_changed_cb)
105
106         # auto hide
107         self.__combobox_panel_show = self.__builder.get_object(
108                 "combobox_panel_show")
109         self.__combobox_panel_show.set_active(values.get("show", 0))
110         self.__combobox_panel_show.connect("changed",
111                 self.__combobox_panel_show_changed_cb)
112
113         # panel position
114         self.__combobox_panel_position = self.__builder.get_object(
115                 "combobox_panel_position")
116         self.__combobox_panel_position.set_active(values.get("position", 3))
117         self.__combobox_panel_position.connect("changed",
118                 self.__combobox_panel_position_changed_cb)
119
120         # custom font
121         self.__checkbutton_custom_font = self.__builder.get_object(
122                 "checkbutton_custom_font")
123         self.__checkbutton_custom_font.set_active(
124                 values.get("use_custom_font", False))
125         self.__checkbutton_custom_font.connect("toggled",
126                 self.__checkbutton_custom_font_toggled_cb)
127
128         self.__fontbutton_custom_font = self.__builder.get_object(
129                 "fontbutton_custom_font")
130         if values.get("use_custom_font", False):
131             self.__fontbutton_custom_font.set_sensitive(True)
132         else:
133             self.__fontbutton_custom_font.set_sensitive(False)
134         font_name = Gtk.Settings.get_default().get_property("gtk-font-name")
135         font_name = unicode(font_name, "utf-8")
136         font_name = values.get("custom_font", font_name)
137         self.__fontbutton_custom_font.connect("notify::font-name",
138                 self.__fontbutton_custom_font_notify_cb)
139         self.__fontbutton_custom_font.set_font_name(font_name)
140
141         # show icon on system tray
142         self.__checkbutton_show_icon_on_systray = self.__builder.get_object(
143                 "checkbutton_show_icon_on_systray")
144         self.__checkbutton_show_icon_on_systray.set_active(
145                 values.get("show_icon_on_systray", True))
146         self.__checkbutton_show_icon_on_systray.connect("toggled",
147                 self.__checkbutton_show_icon_on_systray_toggled_cb)
148
149         # show ime name
150         self.__checkbutton_show_im_name = self.__builder.get_object(
151                 "checkbutton_show_im_name")
152         self.__checkbutton_show_im_name.set_active(
153                 values.get("show_im_name", False))
154         self.__checkbutton_show_im_name.connect("toggled",
155                 self.__checkbutton_show_im_name_toggled_cb)
156
157     def __init_general(self):
158         values = dict(self.__config.get_values("general"))
159
160         # embed preedit text
161         self.__checkbutton_embed_preedit_text = self.__builder.get_object(
162                 "checkbutton_embed_preedit_text")
163         self.__checkbutton_embed_preedit_text.set_active(
164                 values.get("embed_preedit_text", True))
165         self.__checkbutton_embed_preedit_text.connect("toggled",
166                 self.__checkbutton_embed_preedit_text_toggled_cb)
167
168         # use system keyboard layout setting
169         self.__checkbutton_use_sys_layout = self.__builder.get_object(
170                 "checkbutton_use_sys_layout")
171         self.__checkbutton_use_sys_layout.set_active(
172                 values.get("use_system_keyboard_layout", True))
173         self.__checkbutton_use_sys_layout.connect("toggled",
174                 self.__checkbutton_use_sys_layout_toggled_cb)
175
176         # use global ime setting
177         self.__checkbutton_use_global_engine = self.__builder.get_object(
178                 "checkbutton_use_global_engine")
179         self.__checkbutton_use_global_engine.set_active(
180                 values.get("use_global_engine", False))
181         self.__checkbutton_use_global_engine.connect("toggled",
182                 self.__checkbutton_use_global_engine_toggled_cb)
183
184         # init engine page
185         self.__engines = self.__bus.list_engines()
186         self.__combobox = self.__builder.get_object("combobox_engines")
187         self.__combobox.set_engines(self.__engines)
188
189         tmp_dict = {}
190         for e in self.__engines:
191             tmp_dict[e.get_name()] = e
192         engine_names = values.get("preload_engines", [])
193         engines = [tmp_dict[name] for name in engine_names if name in tmp_dict]
194
195         self.__treeview = self.__builder.get_object("treeview_engines")
196         self.__treeview.set_engines(engines)
197
198         button = self.__builder.get_object("button_engine_add")
199         button.connect("clicked", self.__button_engine_add_cb)
200         button = self.__builder.get_object("button_engine_remove")
201         button.connect("clicked", lambda *args:self.__treeview.remove_engine())
202         button = self.__builder.get_object("button_engine_up")
203         button.connect("clicked", lambda *args:self.__treeview.move_up_engine())
204
205         button = self.__builder.get_object("button_engine_down")
206         button.connect("clicked",
207                 lambda *args:self.__treeview.move_down_engine())
208
209         button = self.__builder.get_object("button_engine_about")
210         button.connect("clicked", self.__button_engine_about_cb)
211
212         self.__engine_setup_exec_list = {}
213         button = self.__builder.get_object("button_engine_preferences")
214         button.connect("clicked", self.__button_engine_preferences_cb)
215
216         self.__combobox.connect("notify::active-engine",
217                 self.__combobox_notify_active_engine_cb)
218         self.__treeview.connect("notify::active-engine", self.__treeview_notify_cb)
219         self.__treeview.connect("notify::engines", self.__treeview_notify_cb)
220
221     def __init_ui(self):
222         # add icon search path
223         self.__window = self.__builder.get_object("window_preferences")
224         self.__window.connect("delete-event", Gtk.main_quit)
225
226         self.__button_close = self.__builder.get_object("button_close")
227         self.__button_close.connect("clicked", Gtk.main_quit)
228
229         # auto start ibus
230         self.__checkbutton_auto_start = self.__builder.get_object(
231                 "checkbutton_auto_start")
232         self.__checkbutton_auto_start.set_active(self.__is_auto_start())
233         self.__checkbutton_auto_start.connect("toggled",
234                 self.__checkbutton_auto_start_toggled_cb)
235
236         self.__config = self.__bus.get_config()
237
238         self.__init_hotkey()
239         self.__init_panel()
240         self.__init_general()
241
242     def __combobox_notify_active_engine_cb(self, combobox, property):
243         engine = self.__combobox.get_active_engine()
244         button = self.__builder.get_object("button_engine_add")
245         button.set_sensitive(
246                 engine != None and engine not in self.__treeview.get_engines())
247
248     def __get_engine_setup_exec_args(self, engine):
249         args = []
250         if engine == None:
251            return args
252         setup = str(engine.get_setup())
253         if len(setup) != 0:
254             args = setup.split()
255             args.insert(1, path.basename(args[0]))
256             return args
257         name = str(engine.get_name())
258         libexecdir = os.environ['IBUS_LIBEXECDIR']
259         setup_path = (libexecdir + '/' + 'ibus-setup-' if libexecdir != None \
260             else 'ibus-setup-') + name.split(':')[0]
261         if path.exists(setup_path):
262             args.append(setup_path)
263             args.append(path.basename(setup_path))
264         return args
265
266     def __treeview_notify_cb(self, treeview, prop):
267         if prop.name not in ("active-engine", "engines"):
268             return
269
270         engines = self.__treeview.get_engines()
271         engine = self.__treeview.get_active_engine()
272
273         self.__builder.get_object("button_engine_remove").set_sensitive(engine != None)
274         self.__builder.get_object("button_engine_about").set_sensitive(engine != None)
275         self.__builder.get_object("button_engine_up").set_sensitive(engine not in engines[:1])
276         self.__builder.get_object("button_engine_down").set_sensitive(engine not in engines[-1:])
277
278         obj = self.__builder.get_object("button_engine_preferences")
279         if len(self.__get_engine_setup_exec_args(engine)) != 0:
280             obj.set_sensitive(True)
281         else:
282             obj.set_sensitive(False)
283
284         if prop.name == "engines":
285             engine_names = map(lambda e: e.get_name(), engines)
286             value = GLib.Variant.new_strv(engine_names)
287             self.__config.set_value("general", "preload_engines", value)
288
289     def __button_engine_add_cb(self, button):
290         engine = self.__combobox.get_active_engine()
291         self.__treeview.append_engine(engine)
292
293     def __button_engine_about_cb(self, button):
294         engine = self.__treeview.get_active_engine()
295         if engine:
296             about = EngineAbout(engine)
297             about.run()
298             about.destroy()
299
300     def __button_engine_preferences_cb(self, button):
301         engine = self.__treeview.get_active_engine()
302         args = self.__get_engine_setup_exec_args(engine)
303         if len(args) == 0:
304             return
305         name = engine.get_name()
306         if name in self.__engine_setup_exec_list.keys():
307             try:
308                 wpid, sts = os.waitpid(self.__engine_setup_exec_list[name],
309                                        os.WNOHANG)
310                 # the setup is still running.
311                 if wpid == 0:
312                     return
313             except OSError:
314                 pass
315             del self.__engine_setup_exec_list[name]
316         self.__engine_setup_exec_list[name] = os.spawnl(os.P_NOWAIT, *args)
317
318     def __init_bus(self):
319         try:
320             self.__bus = IBus.Bus()
321             # self.__bus.connect("config-value-changed", self.__config_value_changed_cb)
322             # self.__bus.connect("config-reloaded", self.__config_reloaded_cb)
323             # self.__bus.config_add_watch("/general")
324             # self.__bus.config_add_watch("/general/hotkey")
325             # self.__bus.config_add_watch("/panel")
326         except:
327             pass
328         finally:
329             if self.__bus != None and self.__bus.is_connected():
330                 return
331             while self.__bus == None or not self.__bus.is_connected():
332                 message = _("IBus daemon is not started. Do you want to start it now?")
333                 dlg = Gtk.MessageDialog(type = Gtk.MessageType.QUESTION,
334                         buttons = Gtk.ButtonsType.YES_NO,
335                         message_format = message)
336                 id = dlg.run()
337                 dlg.destroy()
338                 self.__flush_gtk_events()
339                 if id != Gtk.ResponseType.YES:
340                     sys.exit(0)
341                 pid = os.spawnlp(os.P_NOWAIT, "ibus-daemon", "ibus-daemon", "--xim")
342                 time.sleep(1)
343                 try:
344                     if self.__bus != None:
345                         self.__bus.destroy()
346                     self.__bus = IBus.Bus()
347                 except:
348                     continue
349             message = _("IBus has been started! "
350                 "If you can not use IBus, please add below lines in $HOME/.bashrc, and relogin your desktop.\n"
351                 "  export GTK_IM_MODULE=ibus\n"
352                 "  export XMODIFIERS=@im=ibus\n"
353                 "  export QT_IM_MODULE=ibus"
354                 )
355             dlg = Gtk.MessageDialog(type = Gtk.MessageType.INFO,
356                                     buttons = Gtk.ButtonsType.OK,
357                                     message_format = message)
358             id = dlg.run()
359             dlg.destroy()
360             self.__flush_gtk_events()
361
362     def __shortcut_button_clicked_cb(self, button, name, section, _name, entry):
363         buttons = (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
364                 Gtk.STOCK_OK, Gtk.ResponseType.OK)
365         title = _("Select keyboard shortcut for %s") %  _(name)
366         dialog = keyboardshortcut.KeyboardShortcutSelectionDialog(buttons = buttons, title = title)
367         text = entry.get_text()
368         if text:
369             shortcuts = text.split("; ")
370         else:
371             shortcuts = None
372         dialog.set_shortcuts(shortcuts)
373         id = dialog.run()
374         shortcuts = dialog.get_shortcuts()
375         dialog.destroy()
376         if id != Gtk.ResponseType.OK:
377             return
378         self.__config.set_value(section, _name, GLib.Variant.new_strv(shortcuts))
379         text = "; ".join(shortcuts)
380         entry.set_text(text)
381         entry.set_tooltip_text(text)
382
383
384     def __item_started_column_toggled_cb(self, cell, path_str, model):
385
386         # get toggled iter
387         iter = model.get_iter_from_string(path_str)
388         data = model.get_value(iter, COLUMN_DATA)
389
390         # do something with the value
391         if data[DATA_STARTED] == False:
392             try:
393                 self.__bus.register_start_engine(data[DATA_LANG], data[DATA_NAME])
394             except Exception, e:
395                 dlg = Gtk.MessageDialog(type = Gtk.MESSAGE_ERROR,
396                         buttons = Gtk.ButtonsType.CLOSE,
397                         message_format = str(e))
398                 dlg.run()
399                 dlg.destroy()
400                 self.__flush_gtk_events()
401                 return
402         else:
403             try:
404                 self.__bus.register_stop_engine(data[DATA_LANG], data[DATA_NAME])
405             except Exception, e:
406                 dlg = Gtk.MessageDialog(type = Gtk.MESSAGE_ERROR,
407                         buttons = Gtk.ButtonsType.CLOSE,
408                         message_format = str(e))
409                 dlg.run()
410                 dlg.destroy()
411                 self.__flush_gtk_events()
412                 return
413         data[DATA_STARTED] = not data[DATA_STARTED]
414
415         # set new value
416         model.set(iter, COLUMN_ENABLE, data[DATA_STARTED])
417
418     def __item_preload_column_toggled_cb(self, cell, path_str, model):
419
420         # get toggled iter
421         iter = model.get_iter_from_string(path_str)
422         data = model.get_value(iter, COLUMN_DATA)
423
424         data[DATA_PRELOAD] = not data[DATA_PRELOAD]
425         engine = "%s:%s" % (data[DATA_LANG], data[DATA_NAME])
426
427         if data[DATA_PRELOAD]:
428             if engine not in self.__preload_engines:
429                 self.__preload_engines.add(engine)
430                 value = GLib.Variant.new_strv(list(self.__preload_engines))
431                 self.__config.set_value("general", "preload_engines", value)
432         else:
433             if engine in self.__preload_engines:
434                 self.__preload_engines.remove(engine)
435                 value = GLib.Variant.new_strv(list(self.__preload_engines))
436                 self.__config.set_value("general", "preload_engines", value)
437
438         # set new value
439         model.set(iter, COLUMN_PRELOAD, data[DATA_PRELOAD])
440
441     def __is_auto_start(self):
442         link_file = path.join(BaseDirectory.xdg_config_home, "autostart/IBus.desktop")
443         ibus_desktop = path.join(os.getenv("IBUS_PREFIX"), "share/applications/IBus.desktop")
444
445         if not path.exists(link_file):
446             return False
447         if not path.islink(link_file):
448             return False
449         if path.realpath(link_file) != ibus_desktop:
450             return False
451         return True
452
453     def __checkbutton_auto_start_toggled_cb(self, button):
454         auto_start_dir = path.join(BaseDirectory.xdg_config_home, "autostart")
455         if not path.isdir(auto_start_dir):
456             os.makedirs(auto_start_dir)
457
458         link_file = path.join(BaseDirectory.xdg_config_home, "autostart/IBus.desktop")
459         ibus_desktop = path.join(os.getenv("IBUS_PREFIX"), "share/applications/IBus.desktop")
460         # unlink file
461         try:
462             os.unlink(link_file)
463         except:
464             pass
465         if self.__checkbutton_auto_start.get_active():
466             os.symlink(ibus_desktop, link_file)
467
468     def __combobox_lookup_table_orientation_changed_cb(self, combobox):
469         self.__config.set_value(
470                 "panel", "lookup_table_orientation",
471                 GLib.Variant.new_int32(self.__combobox_lookup_table_orientation.get_active()))
472
473     def __combobox_panel_show_changed_cb(self, combobox):
474         self.__config.set_value(
475                 "panel", "show",
476                 GLib.Variant.new_int32(self.__combobox_panel_show.get_active()))
477
478     def __combobox_panel_position_changed_cb(self, combobox):
479         self.__config.set_value(
480                 "panel", "position",
481                 GLib.Variant.new_int32(self.__combobox_panel_position.get_active()))
482
483     def __checkbutton_custom_font_toggled_cb(self, button):
484         if self.__checkbutton_custom_font.get_active():
485             self.__fontbutton_custom_font.set_sensitive(True)
486             self.__config.set_value("panel", "use_custom_font",
487                     GLib.Variant.new_boolean(True))
488         else:
489             self.__fontbutton_custom_font.set_sensitive(False)
490             self.__config.set_value("panel", "use_custom_font",
491                     GLib.Variant.new_boolean(False))
492
493     def __fontbutton_custom_font_notify_cb(self, button, arg):
494         font_name = self.__fontbutton_custom_font.get_font_name()
495         font_name = unicode(font_name, "utf-8")
496         self.__config.set_value("panel", "custom_font",
497                 GLib.Variant.new_string(font_name))
498
499     def __checkbutton_show_icon_on_systray_toggled_cb(self, button):
500         value = self.__checkbutton_show_icon_on_systray.get_active()
501         value = GLib.Variant.new_boolean(value)
502         self.__config.set_value("panel", "show_icon_on_systray", value)
503
504     def __checkbutton_show_im_name_toggled_cb(self, button):
505         value = self.__checkbutton_show_im_name.get_active()
506         value = GLib.Variant.new_boolean(value)
507         self.__config.set_value("panel", "show_im_name", value)
508
509     def __checkbutton_embed_preedit_text_toggled_cb(self, button):
510         value = self.__checkbutton_embed_preedit_text.get_active()
511         value = GLib.Variant.new_boolean(value)
512         self.__config.set_value("general", "embed_preedit_text", value)
513
514     def __checkbutton_use_sys_layout_toggled_cb(self, button):
515         value = self.__checkbutton_use_sys_layout.get_active()
516         value = GLib.Variant.new_boolean(value)
517         self.__config.set_value("general", "use_system_keyboard_layout", value)
518
519     def __checkbutton_use_global_engine_toggled_cb(self, button):
520         value = self.__checkbutton_use_global_engine.get_active()
521         value = GLib.Variant.new_boolean(value)
522         self.__config.set_value("general", "use_global_engine", value)
523
524     def __config_value_changed_cb(self, bus, section, name, value):
525         pass
526
527     def __config_reloaded_cb(self, bus):
528         pass
529
530     def __sigusr1_cb(self, *args):
531         self.__window.present()
532
533     def run(self):
534         self.__window.show_all()
535         signal.signal(signal.SIGUSR1, self.__sigusr1_cb)
536         Gtk.main()
537
538 if __name__ == "__main__":
539     locale.setlocale(locale.LC_ALL, '')
540     i18n_init()
541     setup = Setup()
542     setup.run()