WIP fix gtk3 setup issues
[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.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.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             while self.__bus == None:
328                 message = _("IBus daemon is not started. Do you want to start it now?")
329                 dlg = Gtk.MessageDialog(type = Gtk.MESSAGE_QUESTION,
330                         buttons = Gtk.ButtonsType.YES_NO,
331                         message_format = message)
332                 id = dlg.run()
333                 dlg.destroy()
334                 self.__flush_gtk_events()
335                 if id != Gtk.ResponseType.YES:
336                     sys.exit(0)
337                 pid = os.spawnlp(os.P_NOWAIT, "ibus-daemon", "ibus-daemon", "--xim")
338                 time.sleep(1)
339                 try:
340                     self.__bus = IBus.Bus()
341                 except:
342                     continue
343                 message = _("IBus has been started! "
344                     "If you can not use IBus, please add below lines in $HOME/.bashrc, and relogin your desktop.\n"
345                     "  export GTK_IM_MODULE=ibus\n"
346                     "  export XMODIFIERS=@im=ibus\n"
347                     "  export QT_IM_MODULE=ibus"
348                     )
349                 dlg = Gtk.MessageDialog(type = Gtk.MESSAGE_INFO,
350                                         buttons = Gtk.ButtonsType.OK,
351                                         message_format = message)
352                 id = dlg.run()
353                 dlg.destroy()
354                 self.__flush_gtk_events()
355
356     def __shortcut_button_clicked_cb(self, button, name, section, _name, entry):
357         buttons = (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
358                 Gtk.STOCK_OK, Gtk.ResponseType.OK)
359         title = _("Select keyboard shortcut for %s") %  _(name)
360         dialog = keyboardshortcut.KeyboardShortcutSelectionDialog(buttons = buttons, title = title)
361         text = entry.get_text()
362         if text:
363             shortcuts = text.split("; ")
364         else:
365             shortcuts = None
366         dialog.set_shortcuts(shortcuts)
367         id = dialog.run()
368         shortcuts = dialog.get_shortcuts()
369         dialog.destroy()
370         if id != Gtk.ResponseType.OK:
371             return
372         self.__config.set_value(section, _name, GLib.Variant.new_strv(shortcuts))
373         text = "; ".join(shortcuts)
374         entry.set_text(text)
375         entry.set_tooltip_text(text)
376
377
378     def __item_started_column_toggled_cb(self, cell, path_str, model):
379
380         # get toggled iter
381         iter = model.get_iter_from_string(path_str)
382         data = model.get_value(iter, COLUMN_DATA)
383
384         # do something with the value
385         if data[DATA_STARTED] == False:
386             try:
387                 self.__bus.register_start_engine(data[DATA_LANG], data[DATA_NAME])
388             except Exception, e:
389                 dlg = Gtk.MessageDialog(type = Gtk.MESSAGE_ERROR,
390                         buttons = Gtk.ButtonsType.CLOSE,
391                         message_format = str(e))
392                 dlg.run()
393                 dlg.destroy()
394                 self.__flush_gtk_events()
395                 return
396         else:
397             try:
398                 self.__bus.register_stop_engine(data[DATA_LANG], data[DATA_NAME])
399             except Exception, e:
400                 dlg = Gtk.MessageDialog(type = Gtk.MESSAGE_ERROR,
401                         buttons = Gtk.ButtonsType.CLOSE,
402                         message_format = str(e))
403                 dlg.run()
404                 dlg.destroy()
405                 self.__flush_gtk_events()
406                 return
407         data[DATA_STARTED] = not data[DATA_STARTED]
408
409         # set new value
410         model.set(iter, COLUMN_ENABLE, data[DATA_STARTED])
411
412     def __item_preload_column_toggled_cb(self, cell, path_str, model):
413
414         # get toggled iter
415         iter = model.get_iter_from_string(path_str)
416         data = model.get_value(iter, COLUMN_DATA)
417
418         data[DATA_PRELOAD] = not data[DATA_PRELOAD]
419         engine = "%s:%s" % (data[DATA_LANG], data[DATA_NAME])
420
421         if data[DATA_PRELOAD]:
422             if engine not in self.__preload_engines:
423                 self.__preload_engines.add(engine)
424                 value = GLib.Variant.new_strv(list(self.__preload_engines))
425                 self.__config.set_value("general", "preload_engines", value)
426         else:
427             if engine in self.__preload_engines:
428                 self.__preload_engines.remove(engine)
429                 value = GLib.Variant.new_strv(list(self.__preload_engines))
430                 self.__config.set_value("general", "preload_engines", value)
431
432         # set new value
433         model.set(iter, COLUMN_PRELOAD, data[DATA_PRELOAD])
434
435     def __is_auto_start(self):
436         link_file = path.join(BaseDirectory.xdg_config_home, "autostart/IBus.desktop")
437         ibus_desktop = path.join(os.getenv("IBUS_PREFIX"), "share/applications/IBus.desktop")
438
439         if not path.exists(link_file):
440             return False
441         if not path.islink(link_file):
442             return False
443         if path.realpath(link_file) != ibus_desktop:
444             return False
445         return True
446
447     def __checkbutton_auto_start_toggled_cb(self, button):
448         auto_start_dir = path.join(BaseDirectory.xdg_config_home, "autostart")
449         if not path.isdir(auto_start_dir):
450             os.makedirs(auto_start_dir)
451
452         link_file = path.join(BaseDirectory.xdg_config_home, "autostart/IBus.desktop")
453         ibus_desktop = path.join(os.getenv("IBUS_PREFIX"), "share/applications/IBus.desktop")
454         # unlink file
455         try:
456             os.unlink(link_file)
457         except:
458             pass
459         if self.__checkbutton_auto_start.get_active():
460             os.symlink(ibus_desktop, link_file)
461
462     def __combobox_lookup_table_orientation_changed_cb(self, combobox):
463         self.__config.set_value(
464                 "panel", "lookup_table_orientation",
465                 GLib.Variant.new_int32(self.__combobox_lookup_table_orientation.get_active()))
466
467     def __combobox_panel_show_changed_cb(self, combobox):
468         self.__config.set_value(
469                 "panel", "show",
470                 GLib.Variant.new_int32(self.__combobox_panel_show.get_active()))
471
472     def __combobox_panel_position_changed_cb(self, combobox):
473         self.__config.set_value(
474                 "panel", "position",
475                 GLib.Variant.new_int32(self.__combobox_panel_position.get_active()))
476
477     def __checkbutton_custom_font_toggled_cb(self, button):
478         if self.__checkbutton_custom_font.get_active():
479             self.__fontbutton_custom_font.set_sensitive(True)
480             self.__config.set_value("panel", "use_custom_font",
481                     GLib.Variant.new_boolean(True))
482         else:
483             self.__fontbutton_custom_font.set_sensitive(False)
484             self.__config.set_value("panel", "use_custom_font",
485                     GLib.Variant.new_boolean(False))
486
487     def __fontbutton_custom_font_notify_cb(self, button, arg):
488         font_name = self.__fontbutton_custom_font.get_font_name()
489         font_name = unicode(font_name, "utf-8")
490         self.__config.set_value("panel", "custom_font",
491                 GLib.Variant.new_string(font_name))
492
493     def __checkbutton_show_icon_on_systray_toggled_cb(self, button):
494         value = self.__checkbutton_show_icon_on_systray.get_active()
495         value = GLib.Variant.new_boolean(value)
496         self.__config.set_value("panel", "show_icon_on_systray", value)
497
498     def __checkbutton_show_im_name_toggled_cb(self, button):
499         value = self.__checkbutton_show_im_name.get_active()
500         value = GLib.Variant.new_boolean(value)
501         self.__config.set_value("panel", "show_im_name", value)
502
503     def __checkbutton_embed_preedit_text_toggled_cb(self, button):
504         value = self.__checkbutton_embed_preedit_text.get_active()
505         value = GLib.Variant.new_boolean(value)
506         self.__config.set_value("general", "embed_preedit_text", value)
507
508     def __checkbutton_use_sys_layout_toggled_cb(self, button):
509         value = self.__checkbutton_use_sys_layout.get_active()
510         value = GLib.Variant.new_boolean(value)
511         self.__config.set_value("general", "use_system_keyboard_layout", value)
512
513     def __checkbutton_use_global_engine_toggled_cb(self, button):
514         value = self.__checkbutton_use_global_engine.get_active()
515         value = GLib.Variant.new_boolean(value)
516         self.__config.set_value("general", "use_global_engine", value)
517
518     def __config_value_changed_cb(self, bus, section, name, value):
519         pass
520
521     def __config_reloaded_cb(self, bus):
522         pass
523
524     def __sigusr1_cb(self, *args):
525         self.__window.present()
526
527     def run(self):
528         self.__window.show_all()
529         signal.signal(signal.SIGUSR1, self.__sigusr1_cb)
530         Gtk.main()
531
532 if __name__ == "__main__":
533     locale.setlocale(locale.LC_ALL, '')
534     i18n_init()
535     setup = Setup()
536     setup.run()