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