1 # vim:set et sts=4 sw=4:
5 # Copyright (c) 2007-2010 Peng Huang <shawn.p.huang@gmail.com>
6 # Copyright (c) 2007-2010 Red Hat, Inc.
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.
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.
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
28 from gi.repository import GLib
29 from gi.repository import Gtk
30 from gi.repository import IBus
34 from xdg.BaseDirectory import xdg_config_home
36 xdg_config_home = ".config"
38 import keyboardshortcut
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
67 def __flush_gtk_events(self):
68 while Gtk.events_pending():
72 super(Setup, self).__init__()
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
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);
89 def __init_hotkey(self):
91 label = 'switch_engine'
92 variant = self.__config.get_value('general/hotkey', name)
94 shortcuts = variant.unpack()
96 shortcuts = ['<Super>space']
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)
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)
108 def __init_panel(self):
109 values = dict(self.__config.get_values("panel"))
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)
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)
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)
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)
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)
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)
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)
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)
170 def __init_general(self):
171 values = dict(self.__config.get_values("general"))
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)
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)
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)
198 self.__engines = self.__bus.list_engines()
199 self.__combobox = self.__builder.get_object("combobox_engines")
200 self.__combobox.set_engines(self.__engines)
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]
208 self.__treeview = self.__builder.get_object("treeview_engines")
209 self.__treeview.set_engines(engines)
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())
218 button = self.__builder.get_object("button_engine_down")
219 button.connect("clicked",
220 lambda *args:self.__treeview.move_down_engine())
222 button = self.__builder.get_object("button_engine_about")
223 button.connect("clicked", self.__button_engine_about_cb)
225 self.__engine_setup_exec_list = {}
226 button = self.__builder.get_object("button_engine_preferences")
227 button.connect("clicked", self.__button_engine_preferences_cb)
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)
235 # add icon search path
236 self.__window = self.__builder.get_object("window_preferences")
237 self.__window.connect("delete-event", Gtk.main_quit)
239 self.__button_close = self.__builder.get_object("button_close")
240 self.__button_close.connect("clicked", Gtk.main_quit)
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)
249 self.__config = self.__bus.get_config()
253 self.__init_general()
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())
261 def __get_engine_setup_exec_args(self, engine):
265 setup = str(engine.get_setup())
268 args.insert(1, path.basename(args[0]))
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))
279 def __treeview_notify_cb(self, treeview, prop):
280 if prop.name not in ("active-engine", "engines"):
283 engines = self.__treeview.get_engines()
284 engine = self.__treeview.get_active_engine()
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:])
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)
295 obj.set_sensitive(False)
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)
302 def __button_engine_add_cb(self, button):
303 engine = self.__combobox.get_active_engine()
304 self.__treeview.append_engine(engine)
306 def __button_engine_about_cb(self, button):
307 engine = self.__treeview.get_active_engine()
309 about = EngineAbout(engine)
313 def __button_engine_preferences_cb(self, button):
314 engine = self.__treeview.get_active_engine()
315 args = self.__get_engine_setup_exec_args(engine)
318 name = engine.get_name()
319 if name in self.__engine_setup_exec_list.keys():
321 wpid, sts = os.waitpid(self.__engine_setup_exec_list[name],
323 # the setup is still running.
328 del self.__engine_setup_exec_list[name]
329 self.__engine_setup_exec_list[name] = os.spawnl(os.P_NOWAIT, *args)
331 def __init_bus(self):
332 self.__bus = IBus.Bus()
333 if self.__bus.is_connected():
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,
342 self.__flush_gtk_events()
343 if id != Gtk.ResponseType.YES:
346 main_loop = GLib.MainLoop()
349 GLib.timeout_add_seconds(timeout, lambda *args: main_loop.quit())
350 self.__bus.connect("connected", lambda *args: main_loop.quit())
352 os.spawnlp(os.P_NOWAIT, "ibus-daemon", "ibus-daemon", "--xim")
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"
363 dlg = Gtk.MessageDialog(type = Gtk.MessageType.INFO,
364 buttons = Gtk.ButtonsType.OK,
368 self.__flush_gtk_events()
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)
377 self.__flush_gtk_events()
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()
388 shortcuts = text.split("; ")
391 dialog.set_shortcuts(shortcuts)
393 shortcuts = dialog.get_shortcuts()
395 if id != Gtk.ResponseType.OK:
397 self.__config.set_value(section, name, GLib.Variant.new_strv(shortcuts))
398 text = "; ".join(shortcuts)
400 tooltip = "\n".join(shortcuts)
402 _("Use shortcut with shift to switch to the previous input method")
403 entry.set_tooltip_text(tooltip)
405 def __item_started_column_toggled_cb(self, cell, path_str, model):
408 iter = model.get_iter_from_string(path_str)
409 data = model.get_value(iter, COLUMN_DATA)
411 # do something with the value
412 if data[DATA_STARTED] == False:
414 self.__bus.register_start_engine(data[DATA_LANG], data[DATA_NAME])
416 dlg = Gtk.MessageDialog(type = Gtk.MessageType.ERROR,
417 buttons = Gtk.ButtonsType.CLOSE,
418 message_format = str(e))
421 self.__flush_gtk_events()
425 self.__bus.register_stop_engine(data[DATA_LANG], data[DATA_NAME])
427 dlg = Gtk.MessageDialog(type = Gtk.MessageType.ERROR,
428 buttons = Gtk.ButtonsType.CLOSE,
429 message_format = str(e))
432 self.__flush_gtk_events()
434 data[DATA_STARTED] = not data[DATA_STARTED]
437 model.set(iter, COLUMN_ENABLE, data[DATA_STARTED])
439 def __item_preload_column_toggled_cb(self, cell, path_str, model):
442 iter = model.get_iter_from_string(path_str)
443 data = model.get_value(iter, COLUMN_DATA)
445 data[DATA_PRELOAD] = not data[DATA_PRELOAD]
446 engine = "%s:%s" % (data[DATA_LANG], data[DATA_NAME])
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)
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)
460 model.set(iter, COLUMN_PRELOAD, data[DATA_PRELOAD])
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")
467 if not path.exists(link_file):
469 if not path.islink(link_file):
471 if path.realpath(link_file) != ibus_desktop:
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)
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")
488 if self.__checkbutton_auto_start.get_active():
489 os.symlink(ibus_desktop, link_file)
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()))
496 def __combobox_panel_show_changed_cb(self, combobox):
497 self.__config.set_value(
499 GLib.Variant.new_int32(self.__combobox_panel_show.get_active()))
501 def __combobox_panel_position_changed_cb(self, combobox):
502 self.__config.set_value(
504 GLib.Variant.new_int32(self.__combobox_panel_position.get_active()))
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))
512 self.__fontbutton_custom_font.set_sensitive(False)
513 self.__config.set_value("panel", "use_custom_font",
514 GLib.Variant.new_boolean(False))
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))
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)
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)
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)
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)
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)
547 def __config_value_changed_cb(self, bus, section, name, value):
550 def __config_reloaded_cb(self, bus):
553 def __sigusr1_cb(self, *args):
554 self.__window.present()
557 self.__window.show_all()
558 signal.signal(signal.SIGUSR1, self.__sigusr1_cb)
561 if __name__ == "__main__":
563 locale.setlocale(locale.LC_ALL, '')
565 print >> sys.stderr, "Using the fallback 'C' locale"
566 locale.setlocale(locale.LC_ALL, 'C')