Always save the content-type cache for gnome-shell password.
[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.1 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 GNU
16 # 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 library; if not, write to the Free Software
20 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
21 # USA
22
23 import os
24 import signal
25 import sys
26 import time
27
28 from gi.repository import Gio
29 from gi.repository import GLib
30 from gi.repository import Gtk
31 from gi.repository import IBus
32 from os import path
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
70         self.__settings_general = Gio.Settings("org.freedesktop.ibus.general");
71         self.__settings_hotkey = Gio.Settings(
72                 "org.freedesktop.ibus.general.hotkey");
73         self.__settings_panel = Gio.Settings("org.freedesktop.ibus.panel");
74
75         # IBus.Bus() calls ibus_bus_new().
76         # Gtk.Builder().add_from_file() also calls ibus_bus_new_async()
77         # via ibus_im_context_new().
78         # Then if IBus.Bus() is called after Gtk.Builder().add_from_file(),
79         # the connection delay would be happened without an async
80         # finish function.
81         self.__bus = None
82         self.__init_bus()
83
84         gtk_builder_file = path.join(path.dirname(__file__), "./setup.ui")
85         self.__builder = Gtk.Builder()
86         self.__builder.set_translation_domain(DOMAINNAME)
87         self.__builder.add_from_file(gtk_builder_file);
88         self.__init_ui()
89
90     def __init_hotkey(self):
91         name = 'triggers'
92         label = 'switch_engine'
93         shortcuts = self.__settings_hotkey.get_strv(name)
94         button = self.__builder.get_object("button_%s" % label)
95         entry = self.__builder.get_object("entry_%s" % label)
96         entry.set_text("; ".join(shortcuts))
97         tooltip = "\n".join(shortcuts)
98         tooltip += "\n" + \
99             _("Use shortcut with shift to switch to the previous input method") 
100         entry.set_tooltip_text(tooltip)
101         button.connect("clicked", self.__shortcut_button_clicked_cb,
102                 name, "general/hotkey", label, entry)
103
104     def __init_panel(self):
105         # lookup table orientation
106         self.__combobox_lookup_table_orientation = self.__builder.get_object(
107                 "combobox_lookup_table_orientation")
108         self.__settings_panel.bind('lookup-table-orientation',
109                                    self.__combobox_lookup_table_orientation,
110                                    'active',
111                                    Gio.SettingsBindFlags.DEFAULT)
112
113         # auto hide
114         self.__combobox_panel_show = self.__builder.get_object(
115                 "combobox_panel_show")
116         self.__settings_panel.bind('show',
117                                    self.__combobox_panel_show,
118                                    'active',
119                                    Gio.SettingsBindFlags.DEFAULT)
120
121         # panel position
122         self.__combobox_panel_position = self.__builder.get_object(
123                 "combobox_panel_position")
124         self.__combobox_panel_position.set_active(3)
125         #self.__settings_panel.bind('position',
126         #                           self.__combobox_panel_position,
127         #                           'active',
128         #                           Gio.SettingsBindFlags.DEFAULT)
129
130         # custom font
131         self.__checkbutton_custom_font = self.__builder.get_object(
132                 "checkbutton_custom_font")
133         self.__settings_panel.bind('use-custom-font',
134                                    self.__checkbutton_custom_font,
135                                    'active',
136                                    Gio.SettingsBindFlags.DEFAULT)
137
138         self.__fontbutton_custom_font = self.__builder.get_object(
139                 "fontbutton_custom_font")
140         self.__settings_panel.bind('custom-font',
141                                     self.__fontbutton_custom_font,
142                                    'font-name',
143                                    Gio.SettingsBindFlags.DEFAULT)
144         self.__settings_panel.bind('use-custom-font',
145                                     self.__fontbutton_custom_font,
146                                    'sensitive',
147                                    Gio.SettingsBindFlags.GET)
148
149         # show icon on system tray
150         self.__checkbutton_show_icon_on_systray = self.__builder.get_object(
151                 "checkbutton_show_icon_on_systray")
152         self.__settings_panel.bind('show-icon-on-systray',
153                                    self.__checkbutton_show_icon_on_systray,
154                                    'active',
155                                    Gio.SettingsBindFlags.DEFAULT)
156
157         # show ime name
158         self.__checkbutton_show_im_name = self.__builder.get_object(
159                 "checkbutton_show_im_name")
160         self.__settings_panel.bind('show-im-name',
161                                    self.__checkbutton_show_im_name,
162                                    'active',
163                                    Gio.SettingsBindFlags.DEFAULT)
164
165     def __init_general(self):
166         # embed preedit text
167         self.__checkbutton_embed_preedit_text = self.__builder.get_object(
168                 "checkbutton_embed_preedit_text")
169         self.__settings_general.bind('embed-preedit-text',
170                                     self.__checkbutton_embed_preedit_text,
171                                     'active',
172                                     Gio.SettingsBindFlags.DEFAULT)
173
174         # use system keyboard layout setting
175         self.__checkbutton_use_sys_layout = self.__builder.get_object(
176                 "checkbutton_use_sys_layout")
177         self.__settings_general.bind('use-system-keyboard-layout',
178                                     self.__checkbutton_use_sys_layout,
179                                     'active',
180                                     Gio.SettingsBindFlags.DEFAULT)
181
182         # use global ime setting
183         self.__checkbutton_use_global_engine = self.__builder.get_object(
184                 "checkbutton_use_global_engine")
185         self.__settings_general.bind('use-global-engine',
186                                     self.__checkbutton_use_global_engine,
187                                     'active',
188                                     Gio.SettingsBindFlags.DEFAULT)
189
190         # init engine page
191         self.__engines = self.__bus.list_engines()
192         self.__combobox = self.__builder.get_object("combobox_engines")
193         self.__combobox.set_engines(self.__engines)
194
195         tmp_dict = {}
196         for e in self.__engines:
197             tmp_dict[e.get_name()] = e
198         engine_names = self.__settings_general.get_strv('preload-engines')
199         engines = [tmp_dict[name] for name in engine_names if name in tmp_dict]
200
201         self.__treeview = self.__builder.get_object("treeview_engines")
202         self.__treeview.set_engines(engines)
203
204         button = self.__builder.get_object("button_engine_add")
205         button.connect("clicked", self.__button_engine_add_cb)
206         button = self.__builder.get_object("button_engine_remove")
207         button.connect("clicked", lambda *args:self.__treeview.remove_engine())
208         button = self.__builder.get_object("button_engine_up")
209         button.connect("clicked", lambda *args:self.__treeview.move_up_engine())
210
211         button = self.__builder.get_object("button_engine_down")
212         button.connect("clicked",
213                 lambda *args:self.__treeview.move_down_engine())
214
215         button = self.__builder.get_object("button_engine_about")
216         button.connect("clicked", self.__button_engine_about_cb)
217
218         self.__engine_setup_exec_list = {}
219         button = self.__builder.get_object("button_engine_preferences")
220         button.connect("clicked", self.__button_engine_preferences_cb)
221
222         self.__combobox.connect("notify::active-engine",
223                 self.__combobox_notify_active_engine_cb)
224         self.__treeview.connect("notify::active-engine", self.__treeview_notify_cb)
225         self.__treeview.connect("notify::engines", self.__treeview_notify_cb)
226
227     def __init_ui(self):
228         # add icon search path
229         self.__window = self.__builder.get_object("window_preferences")
230         self.__window.connect("delete-event", Gtk.main_quit)
231
232         self.__button_close = self.__builder.get_object("button_close")
233         self.__button_close.connect("clicked", Gtk.main_quit)
234
235         # auto start ibus
236         self.__checkbutton_auto_start = self.__builder.get_object(
237                 "checkbutton_auto_start")
238         self.__checkbutton_auto_start.set_active(self.__is_auto_start())
239         self.__checkbutton_auto_start.connect("toggled",
240                 self.__checkbutton_auto_start_toggled_cb)
241
242         self.__init_hotkey()
243         self.__init_panel()
244         self.__init_general()
245
246     def __combobox_notify_active_engine_cb(self, combobox, property):
247         engine = self.__combobox.get_active_engine()
248         button = self.__builder.get_object("button_engine_add")
249         button.set_sensitive(
250                 engine != None and engine not in self.__treeview.get_engines())
251
252     def __get_engine_setup_exec_args(self, engine):
253         args = []
254         if engine == None:
255            return args
256         setup = str(engine.get_setup())
257         if len(setup) != 0:
258             args = setup.split()
259             args.insert(1, path.basename(args[0]))
260             return args
261         name = str(engine.get_name())
262         libexecdir = os.environ['IBUS_LIBEXECDIR']
263         setup_path = (libexecdir + '/' + 'ibus-setup-' if libexecdir != None \
264             else 'ibus-setup-') + name.split(':')[0]
265         if path.exists(setup_path):
266             args.append(setup_path)
267             args.append(path.basename(setup_path))
268         return args
269
270     def __treeview_notify_cb(self, treeview, prop):
271         if prop.name not in ("active-engine", "engines"):
272             return
273
274         engines = self.__treeview.get_engines()
275         engine = self.__treeview.get_active_engine()
276
277         self.__builder.get_object("button_engine_remove").set_sensitive(engine != None)
278         self.__builder.get_object("button_engine_about").set_sensitive(engine != None)
279         self.__builder.get_object("button_engine_up").set_sensitive(engine not in engines[:1])
280         self.__builder.get_object("button_engine_down").set_sensitive(engine not in engines[-1:])
281
282         obj = self.__builder.get_object("button_engine_preferences")
283         if len(self.__get_engine_setup_exec_args(engine)) != 0:
284             obj.set_sensitive(True)
285         else:
286             obj.set_sensitive(False)
287
288         if prop.name == "engines":
289             engine_names = map(lambda e: e.get_name(), engines)
290             self.__settings_general.set_strv('preload-engines', engine_names)
291
292     def __button_engine_add_cb(self, button):
293         engine = self.__combobox.get_active_engine()
294         self.__treeview.append_engine(engine)
295
296     def __button_engine_about_cb(self, button):
297         engine = self.__treeview.get_active_engine()
298         if engine:
299             about = EngineAbout(engine)
300             about.run()
301             about.destroy()
302
303     def __button_engine_preferences_cb(self, button):
304         engine = self.__treeview.get_active_engine()
305         args = self.__get_engine_setup_exec_args(engine)
306         if len(args) == 0:
307             return
308         name = engine.get_name()
309         if name in self.__engine_setup_exec_list.keys():
310             try:
311                 wpid, sts = os.waitpid(self.__engine_setup_exec_list[name],
312                                        os.WNOHANG)
313                 # the setup is still running.
314                 if wpid == 0:
315                     return
316             except OSError:
317                 pass
318             del self.__engine_setup_exec_list[name]
319         self.__engine_setup_exec_list[name] = os.spawnl(os.P_NOWAIT, *args)
320
321     def __init_bus(self):
322         self.__bus = IBus.Bus()
323         if self.__bus.is_connected():
324             return
325
326         message = _("The IBus daemon is not running. Do you wish to start it?")
327         dlg = Gtk.MessageDialog(type = Gtk.MessageType.QUESTION,
328                                 buttons = Gtk.ButtonsType.YES_NO,
329                                 text = message)
330         id = dlg.run()
331         dlg.destroy()
332         self.__flush_gtk_events()
333         if id != Gtk.ResponseType.YES:
334             sys.exit(0)
335
336         main_loop = GLib.MainLoop()
337
338         timeout = 5
339         GLib.timeout_add_seconds(timeout, lambda *args: main_loop.quit())
340         self.__bus.connect("connected", lambda *args: main_loop.quit())
341
342         os.spawnlp(os.P_NOWAIT, "ibus-daemon", "ibus-daemon", "--xim")
343
344         main_loop.run()
345
346         if self.__bus.is_connected():
347             message = _("IBus has been started! "
348                 "If you cannot use IBus, add the following lines to your $HOME/.bashrc; then relog into your desktop.\n"
349                 "  export GTK_IM_MODULE=ibus\n"
350                 "  export XMODIFIERS=@im=ibus\n"
351                 "  export QT_IM_MODULE=ibus"
352                 )
353             dlg = Gtk.MessageDialog(type = Gtk.MessageType.INFO,
354                                     buttons = Gtk.ButtonsType.OK,
355                                     text = message)
356             id = dlg.run()
357             dlg.destroy()
358             self.__flush_gtk_events()
359         else:
360             # Translators: %d == 5 currently
361             message = _("IBus daemon could not be started in %d seconds")
362             dlg = Gtk.MessageDialog(type = Gtk.MessageType.INFO,
363                                     buttons = Gtk.ButtonsType.OK,
364                                     text = message % timeout)
365             id = dlg.run()
366             dlg.destroy()
367             self.__flush_gtk_events()
368             sys.exit(0)
369
370     def __shortcut_button_clicked_cb(self, button, name, section, _name, entry):
371         buttons = (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
372                 Gtk.STOCK_OK, Gtk.ResponseType.OK)
373         title = _("Select keyboard shortcut for %s") % \
374                 _("switching input methods")
375         dialog = keyboardshortcut.KeyboardShortcutSelectionDialog(buttons = buttons, title = title)
376         text = entry.get_text()
377         if text:
378             shortcuts = text.split("; ")
379         else:
380             shortcuts = None
381         dialog.set_shortcuts(shortcuts)
382         id = dialog.run()
383         shortcuts = dialog.get_shortcuts()
384         dialog.destroy()
385         if id != Gtk.ResponseType.OK:
386             return
387         self.__settings_hotkey.set_strv(name, shortcuts)
388         text = "; ".join(shortcuts)
389         entry.set_text(text)
390         tooltip = "\n".join(shortcuts)
391         tooltip += "\n" + \
392             _("Use shortcut with shift to switch to the previous input method") 
393         entry.set_tooltip_text(tooltip)
394
395     def __item_started_column_toggled_cb(self, cell, path_str, model):
396
397         # get toggled iter
398         iter = model.get_iter_from_string(path_str)
399         data = model.get_value(iter, COLUMN_DATA)
400
401         # do something with the value
402         if data[DATA_STARTED] == False:
403             try:
404                 self.__bus.register_start_engine(data[DATA_LANG], data[DATA_NAME])
405             except Exception, e:
406                 dlg = Gtk.MessageDialog(type = Gtk.MessageType.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         else:
414             try:
415                 self.__bus.register_stop_engine(data[DATA_LANG], data[DATA_NAME])
416             except Exception, e:
417                 dlg = Gtk.MessageDialog(type = Gtk.MessageType.ERROR,
418                         buttons = Gtk.ButtonsType.CLOSE,
419                         message_format = str(e))
420                 dlg.run()
421                 dlg.destroy()
422                 self.__flush_gtk_events()
423                 return
424         data[DATA_STARTED] = not data[DATA_STARTED]
425
426         # set new value
427         model.set(iter, COLUMN_ENABLE, data[DATA_STARTED])
428
429     def __item_preload_column_toggled_cb(self, cell, path_str, model):
430
431         # get toggled iter
432         iter = model.get_iter_from_string(path_str)
433         data = model.get_value(iter, COLUMN_DATA)
434
435         data[DATA_PRELOAD] = not data[DATA_PRELOAD]
436         engine = "%s:%s" % (data[DATA_LANG], data[DATA_NAME])
437
438         if data[DATA_PRELOAD]:
439             if engine not in self.__preload_engines:
440                 self.__preload_engines.add(engine)
441                 self.__settings_general.set_strv('preload-engines',
442                                                  list(self.__preload_engines))
443         else:
444             if engine in self.__preload_engines:
445                 self.__preload_engines.remove(engine)
446                 self.__settings_general.set_strv('preload-engines',
447                                                  list(self.__preload_engines))
448
449         # set new value
450         model.set(iter, COLUMN_PRELOAD, data[DATA_PRELOAD])
451
452     def __is_auto_start(self):
453         link_file = path.join(GLib.get_user_config_dir(),
454                               "autostart/ibus.desktop")
455         ibus_desktop = path.join(os.getenv("IBUS_PREFIX"),
456                                  "share/applications/ibus.desktop")
457
458         if not path.exists(link_file):
459             return False
460         if not path.islink(link_file):
461             return False
462         if path.realpath(link_file) != ibus_desktop:
463             return False
464         return True
465
466     def __checkbutton_auto_start_toggled_cb(self, button):
467         auto_start_dir = path.join(GLib.get_user_config_dir(), "autostart")
468         if not path.isdir(auto_start_dir):
469             os.makedirs(auto_start_dir)
470
471         link_file = path.join(GLib.get_user_config_dir(),
472                               "autostart/ibus.desktop")
473         ibus_desktop = path.join(os.getenv("IBUS_PREFIX"),
474                                  "share/applications/ibus.desktop")
475         # unlink file
476         try:
477             os.unlink(link_file)
478         except:
479             pass
480         if self.__checkbutton_auto_start.get_active():
481             os.symlink(ibus_desktop, link_file)
482
483     def __sigusr1_cb(self, *args):
484         self.__window.present()
485
486     def run(self):
487         self.__window.show_all()
488         signal.signal(signal.SIGUSR1, self.__sigusr1_cb)
489         Gtk.main()
490
491 if __name__ == "__main__":
492     try:
493         locale.setlocale(locale.LC_ALL, '')
494     except locale.Error:
495         print >> sys.stderr, "Using the fallback 'C' locale"
496         locale.setlocale(locale.LC_ALL, 'C')
497
498     i18n_init()
499     setup = Setup()
500     setup.run()