Implement IME state per window.
[platform/upstream/ibus.git] / ui / gtk3 / panel.vala
1 /* vim:set et sts=4 sw=4:
2  *
3  * ibus - The Input Bus
4  *
5  * Copyright(c) 2011-2013 Peng Huang <shawn.p.huang@gmail.com>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
20  * USA
21  */
22
23 class Panel : IBus.PanelService {
24     private class Keybinding {
25         public Keybinding(uint keysym,
26                           Gdk.ModifierType modifiers,
27                           bool reverse) {
28             this.keysym = keysym;
29             this.modifiers = modifiers;
30             this.reverse = reverse;
31         }
32
33         public uint keysym { get; set; }
34         public Gdk.ModifierType modifiers { get; set; }
35         public bool reverse { get; set; }
36     }
37
38     private IBus.Bus m_bus;
39     private GLib.Settings m_settings_general = null;
40     private GLib.Settings m_settings_hotkey = null;
41     private GLib.Settings m_settings_panel = null;
42     private Gtk.StatusIcon m_status_icon;
43     private Gtk.Menu m_ime_menu;
44     private Gtk.Menu m_sys_menu;
45     private IBus.EngineDesc[] m_engines = {};
46     private GLib.HashTable<string, IBus.EngineDesc> m_engine_contexts =
47             new GLib.HashTable<string, IBus.EngineDesc>(GLib.str_hash,
48                                                         GLib.str_equal);
49     private string m_current_context_path = "";
50     private bool m_use_global_engine = true;
51     private CandidatePanel m_candidate_panel;
52     private Switcher m_switcher;
53     private bool m_switcher_is_running = false;
54     private PropertyManager m_property_manager;
55     private GLib.Pid m_setup_pid = 0;
56     private Gtk.AboutDialog m_about_dialog;
57     private Gtk.CssProvider m_css_provider;
58     private int m_switcher_delay_time = 400;
59     private bool m_use_system_keyboard_layout = false;
60
61     private GLib.List<Keybinding> m_keybindings = new GLib.List<Keybinding>();
62
63     public Panel(IBus.Bus bus) {
64         GLib.assert(bus.is_connected());
65         // Chain up base class constructor
66         GLib.Object(connection : bus.get_connection(),
67                     object_path : "/org/freedesktop/IBus/Panel");
68
69         m_bus = bus;
70
71         init_settings();
72
73         // init ui
74         m_status_icon = new Gtk.StatusIcon();
75         m_status_icon.set_name("ibus-ui-gtk");
76         m_status_icon.set_title("IBus Panel");
77         m_status_icon.popup_menu.connect(status_icon_popup_menu_cb);
78         m_status_icon.activate.connect(status_icon_activate_cb);
79         m_status_icon.set_from_icon_name("ibus-keyboard");
80
81         m_candidate_panel = new CandidatePanel();
82         m_candidate_panel.page_up.connect((w) => this.page_up());
83         m_candidate_panel.page_down.connect((w) => this.page_down());
84
85         m_switcher = new Switcher();
86         // The initial shortcut is "<Super>space"
87         bind_switch_shortcut();
88
89         if (m_switcher_delay_time >= 0) {
90             m_switcher.set_popup_delay_time((uint) m_switcher_delay_time);
91         }
92
93         m_property_manager = new PropertyManager();
94         m_property_manager.property_activate.connect((k, s) => {
95             property_activate(k, s);
96         });
97
98         state_changed();
99     }
100
101     ~Panel() {
102         unbind_switch_shortcut();
103     }
104
105     private void init_settings() {
106         m_settings_general = new GLib.Settings("org.freedesktop.ibus.general");
107         m_settings_hotkey =
108                 new GLib.Settings("org.freedesktop.ibus.general.hotkey");
109         m_settings_panel = new GLib.Settings("org.freedesktop.ibus.panel");
110
111         m_settings_general.changed["preload-engines"].connect((key) => {
112                 update_engines(m_settings_general.get_strv(key),
113                                null);
114         });
115
116         m_settings_general.changed["switcher-delay-time"].connect((key) => {
117                 set_switcher_delay_time();
118         });
119
120         m_settings_general.changed["use-system-keyboard-layout"].connect(
121             (key) => {
122                 set_use_system_keyboard_layout();
123         });
124
125         m_settings_general.changed["embed-preedit-text"].connect((key) => {
126                 set_embed_preedit_text();
127         });
128
129         m_settings_general.changed["use-global-engine"].connect((key) => {
130                 set_use_global_engine();
131         });
132
133         m_settings_hotkey.changed["triggers"].connect((key) => {
134                 unbind_switch_shortcut();
135                 bind_switch_shortcut();
136         });
137
138         m_settings_panel.changed["custom-font"].connect((key) => {
139                 set_custom_font();
140         });
141
142         m_settings_panel.changed["use-custom-font"].connect((key) => {
143                 set_custom_font();
144         });
145     }
146
147     private void keybinding_manager_bind(KeybindingManager keybinding_manager,
148                                          string?           accelerator) {
149         uint switch_keysym = 0;
150         Gdk.ModifierType switch_modifiers = 0;
151         Gdk.ModifierType reverse_modifier = Gdk.ModifierType.SHIFT_MASK;
152         Keybinding keybinding;
153
154         Gtk.accelerator_parse(accelerator,
155                 out switch_keysym, out switch_modifiers);
156
157         // Map virtual modifiers to (i.e. Mod2, Mod3, ...)
158         const Gdk.ModifierType VIRTUAL_MODIFIERS = (
159                 Gdk.ModifierType.SUPER_MASK |
160                 Gdk.ModifierType.HYPER_MASK |
161                 Gdk.ModifierType.META_MASK);
162         if ((switch_modifiers & VIRTUAL_MODIFIERS) != 0) {
163         // workaround a bug in gdk vapi vala > 0.18
164         // https://bugzilla.gnome.org/show_bug.cgi?id=677559
165 #if VALA_0_18
166             Gdk.Keymap.get_default().map_virtual_modifiers(
167                     ref switch_modifiers);
168 #else
169             if ((switch_modifiers & Gdk.ModifierType.SUPER_MASK) != 0)
170                 switch_modifiers |= Gdk.ModifierType.MOD4_MASK;
171             if ((switch_modifiers & Gdk.ModifierType.HYPER_MASK) != 0)
172                 switch_modifiers |= Gdk.ModifierType.MOD4_MASK;
173 #endif
174             switch_modifiers &= ~VIRTUAL_MODIFIERS;
175         }
176
177         if (switch_keysym == 0 && switch_modifiers == 0) {
178             warning("Parse accelerator '%s' failed!", accelerator);
179             return;
180         }
181
182         keybinding = new Keybinding(switch_keysym,
183                                     switch_modifiers,
184                                     false);
185         m_keybindings.append(keybinding);
186
187         keybinding_manager.bind(switch_keysym, switch_modifiers,
188                 (e) => handle_engine_switch(e, false));
189
190         // accelerator already has Shift mask
191         if ((switch_modifiers & reverse_modifier) != 0) {
192             return;
193         }
194
195         switch_modifiers |= reverse_modifier;
196
197         keybinding = new Keybinding(switch_keysym,
198                                     switch_modifiers,
199                                     true);
200         m_keybindings.append(keybinding);
201
202         keybinding_manager.bind(switch_keysym, switch_modifiers,
203                 (e) => handle_engine_switch(e, true));
204     }
205
206     private void bind_switch_shortcut() {
207         string[] accelerators = m_settings_hotkey.get_strv("triggers");
208
209         var keybinding_manager = KeybindingManager.get_instance();
210
211         foreach (var accelerator in accelerators) {
212             keybinding_manager_bind(keybinding_manager, accelerator);
213         }
214     }
215
216     private void unbind_switch_shortcut() {
217         var keybinding_manager = KeybindingManager.get_instance();
218
219         unowned GLib.List<Keybinding> keybindings = m_keybindings;
220
221         while (keybindings != null) {
222             Keybinding keybinding = keybindings.data;
223
224             keybinding_manager.unbind(keybinding.keysym,
225                                       keybinding.modifiers);
226             keybindings = keybindings.next;
227         }
228
229         m_keybindings = null;
230     }
231
232     private void set_custom_font() {
233         Gdk.Display display = Gdk.Display.get_default();
234         Gdk.Screen screen = (display != null) ?
235                 display.get_default_screen() : null;
236
237         if (screen == null) {
238             warning("Could not open display.");
239             return;
240         }
241
242         bool use_custom_font = m_settings_panel.get_boolean("use-custom-font");
243
244         if (m_css_provider != null) {
245             Gtk.StyleContext.remove_provider_for_screen(screen,
246                                                         m_css_provider);
247             m_css_provider = null;
248         }
249
250         if (use_custom_font == false) {
251             return;
252         }
253
254         string font_name = m_settings_panel.get_string("custom-font");
255
256         if (font_name == null) {
257             warning("No config panel:custom-font.");
258             return;
259         }
260
261         string data_format = "GtkLabel { font: %s; }";
262         string data = data_format.printf(font_name);
263         m_css_provider = new Gtk.CssProvider();
264
265         try {
266             m_css_provider.load_from_data(data, -1);
267         } catch (GLib.Error e) {
268             warning("Failed css_provider_from_data: %s: %s", font_name,
269                                                              e.message);
270             return;
271         }
272
273         Gtk.StyleContext.add_provider_for_screen(screen,
274                                                  m_css_provider,
275                                                  Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
276     }
277
278     private void set_switcher_delay_time() {
279         m_switcher_delay_time =
280                 m_settings_general.get_int("switcher-delay-time");
281
282         if (m_switcher_delay_time >= 0) {
283             m_switcher.set_popup_delay_time((uint) m_switcher_delay_time);
284         }
285     }
286
287     private void set_use_system_keyboard_layout() {
288         m_use_system_keyboard_layout =
289                 m_settings_general.get_boolean("use-system-keyboard-layout");
290     }
291
292     private void set_embed_preedit_text() {
293         Variant variant =
294                     m_settings_general.get_value("embed-preedit-text");
295
296         if (variant == null) {
297             return;
298         }
299
300         m_bus.set_ibus_property("EmbedPreeditText", variant);
301     }
302
303     private void set_use_global_engine() {
304         m_use_global_engine =
305                 m_settings_general.get_boolean("use-global-engine");
306     }
307
308     private int compare_versions(string version1, string version2) {
309         string[] version1_list = version1.split(".");
310         string[] version2_list = version2.split(".");
311         int major1, minor1, micro1, major2, minor2, micro2;
312
313         if (version1 == version2) {
314             return 0;
315         }
316
317         // The initial dconf value of "version" is "".
318         if (version1 == "") {
319             return -1;
320         }
321         if (version2 == "") {
322             return 1;
323         }
324
325         assert(version1_list.length >= 3);
326         assert(version2_list.length >= 3);
327
328         major1 = int.parse(version1_list[0]);
329         minor1 = int.parse(version1_list[1]);
330         micro1 = int.parse(version1_list[2]);
331
332         major2 = int.parse(version2_list[0]);
333         minor2 = int.parse(version2_list[1]);
334         micro2 = int.parse(version2_list[2]);
335
336         if (major1 == minor1 && minor1 == minor2 && micro1 == micro2) {
337             return 0;
338         }
339         if ((major1 > major2) ||
340             (major1 == major2 && minor1 > minor2) ||
341             (major1 == major2 && minor1 == minor2 &&
342              micro1 > micro2)) {
343             return 1;
344         }
345         return -1;
346     }
347
348     private void update_version_1_5_3() {
349 #if ENABLE_LIBNOTIFY
350         if (!Notify.is_initted()) {
351             Notify.init ("ibus");
352         }
353
354         var notification = new Notify.Notification(
355                 _("IBus Update"),
356                 _("Super+space is now the default hotkey."),
357                 "ibus");
358         notification.set_timeout(30 * 1000);
359         notification.set_category("hotkey");
360
361         try {
362             notification.show();
363         } catch (GLib.Error e){
364             warning ("Notification is failed for IBus 1.5.3: %s", e.message);
365         }
366 #else
367         warning(_("Super+space is now the default hotkey."));
368 #endif
369     }
370
371     private void set_version() {
372         string prev_version = m_settings_general.get_string("version");
373         string current_version = null;
374
375         if (compare_versions(prev_version, "1.5.3") < 0) {
376             update_version_1_5_3();
377         }
378
379         current_version = "%d.%d.%d".printf(IBus.MAJOR_VERSION,
380                                             IBus.MINOR_VERSION,
381                                             IBus.MICRO_VERSION);
382
383         if (prev_version == current_version) {
384             return;
385         }
386
387         m_settings_general.set_string("version", current_version);
388     }
389
390     public void load_settings() {
391         // Update m_use_system_keyboard_layout before update_engines()
392         // is called.
393         set_use_system_keyboard_layout();
394         set_use_global_engine();
395         update_engines(m_settings_general.get_strv("preload-engines"),
396                        m_settings_general.get_strv("engines-order"));
397         unbind_switch_shortcut();
398         bind_switch_shortcut();
399         set_switcher_delay_time();
400         set_embed_preedit_text();
401         set_custom_font();
402
403         set_version();
404     }
405
406     private void exec_setxkbmap(IBus.EngineDesc engine) {
407         string layout = engine.get_layout();
408         string variant = engine.get_layout_variant();
409         string option = engine.get_layout_option();
410         string standard_error = null;
411         int exit_status = 0;
412         string[] args = { "setxkbmap" };
413
414         if (layout != null && layout != "" && layout != "default") {
415             args += "-layout";
416             args += layout;
417         }
418         if (variant != null && variant != "" && variant != "default") {
419             args += "-variant";
420             args += variant;
421         }
422         if (option != null && option != "" && option != "default") {
423             /*TODO: Need to get the session XKB options */
424             args += "-option";
425             args += "-option";
426             args += option;
427         }
428
429         if (args.length == 1) {
430             return;
431         }
432
433         try {
434             if (!GLib.Process.spawn_sync(null, args, null,
435                                          GLib.SpawnFlags.SEARCH_PATH,
436                                          null, null,
437                                          out standard_error,
438                                          out exit_status)) {
439                 warning("Switch xkb layout to %s failed.",
440                         engine.get_layout());
441             }
442         } catch (GLib.SpawnError e) {
443             warning("Execute setxkbmap failed: %s", e.message);
444         }
445
446         if (exit_status != 0) {
447             warning("Execute setxkbmap failed: %s", standard_error ?? "(null)");
448         }
449     }
450
451     private void engine_contexts_insert(IBus.EngineDesc engine) {
452         if (m_use_global_engine)
453             return;
454
455         if (m_engine_contexts.size() >= 200) {
456             warning ("Contexts by windows are too much counted!");
457             m_engine_contexts.remove_all();
458         }
459
460         m_engine_contexts.replace(m_current_context_path, engine);
461     }
462
463     private void set_engine(IBus.EngineDesc engine) {
464         if (!m_bus.set_global_engine(engine.get_name())) {
465             warning("Switch engine to %s failed.", engine.get_name());
466             return;
467         }
468
469         // set xkb layout
470         if (!m_use_system_keyboard_layout)
471             exec_setxkbmap(engine);
472
473         engine_contexts_insert(engine);
474     }
475
476     private void switch_engine(int i, bool force = false) {
477         GLib.assert(i >= 0 && i < m_engines.length);
478
479         // Do not need switch
480         if (i == 0 && !force)
481             return;
482
483         IBus.EngineDesc engine = m_engines[i];
484
485         set_engine(engine);
486     }
487
488     private void handle_engine_switch(Gdk.Event event, bool revert) {
489         // Do not need switch IME
490         if (m_engines.length <= 1)
491             return;
492
493         uint keyval = event.key.keyval;
494         uint modifiers = KeybindingManager.MODIFIER_FILTER & event.key.state;
495
496         uint primary_modifiers =
497             KeybindingManager.get_primary_modifier(event.key.state);
498
499         bool pressed = KeybindingManager.primary_modifier_still_pressed(
500                 event, primary_modifiers);
501
502         if (revert) {
503             modifiers &= ~Gdk.ModifierType.SHIFT_MASK;
504         }
505
506         if (pressed && m_switcher_delay_time >= 0) {
507             int i = revert ? m_engines.length - 1 : 1;
508
509             /* The flag of m_switcher_is_running avoids the following problem:
510              *
511              * When an IME is chosen on m_switcher, focus_in() is called
512              * for the root window. If an engine is set in focus_in()
513              * during running m_switcher when m_use_global_engine is false,
514              * state_changed() is also called and m_engines[] is modified
515              * in state_changed() and m_switcher.run() returns the index
516              * for m_engines[] but m_engines[] was modified by state_changed()
517              * and the index is not correct. */
518             m_switcher_is_running = true;
519             i = m_switcher.run(keyval, modifiers, event, m_engines, i);
520             m_switcher_is_running = false;
521
522             if (i < 0) {
523                 debug("switch cancelled");
524             } else {
525                 GLib.assert(i < m_engines.length);
526                 switch_engine(i);
527             }
528         } else {
529             int i = revert ? m_engines.length - 1 : 1;
530             switch_engine(i);
531         }
532     }
533
534     private void run_preload_engines(IBus.EngineDesc[] engines, int index) {
535         string[] names = {};
536
537         if (engines.length <= index) {
538             return;
539         }
540
541         names += engines[index].get_name();
542         m_bus.preload_engines_async(names, -1, null);
543     }
544
545     private void update_engines(string[]? unowned_engine_names,
546                                 string[]? order_names) {
547         string[]? engine_names = unowned_engine_names;
548
549         if (engine_names == null || engine_names.length == 0)
550             engine_names = {"xkb:us::eng"};
551
552         string[] names = {};
553
554         foreach (var name in order_names) {
555             if (name in engine_names)
556                 names += name;
557         }
558
559         foreach (var name in engine_names) {
560             if (name in names)
561                 continue;
562             names += name;
563         }
564
565         var engines = m_bus.get_engines_by_names(names);
566
567         if (m_engines.length == 0) {
568             m_engines = engines;
569             switch_engine(0, true);
570             run_preload_engines(engines, 1);
571         } else {
572             var current_engine = m_engines[0];
573             m_engines = engines;
574             int i;
575             for (i = 0; i < m_engines.length; i++) {
576                 if (current_engine.get_name() == engines[i].get_name()) {
577                     switch_engine(i);
578                     if (i != 0) {
579                         run_preload_engines(engines, 0);
580                     } else {
581                         run_preload_engines(engines, 1);
582                     }
583                     return;
584                 }
585             }
586             switch_engine(0, true);
587             run_preload_engines(engines, 1);
588         }
589
590     }
591
592     private void show_setup_dialog() {
593         if (m_setup_pid != 0) {
594             if (Posix.kill(m_setup_pid, Posix.SIGUSR1) == 0)
595                 return;
596             m_setup_pid = 0;
597         }
598
599         string binary = GLib.Path.build_filename(Config.BINDIR, "ibus-setup");
600         try {
601             GLib.Process.spawn_async(null,
602                                      {binary, "ibus-setup"},
603                                      null,
604                                      GLib.SpawnFlags.DO_NOT_REAP_CHILD,
605                                      null,
606                                      out m_setup_pid);
607         } catch (GLib.SpawnError e) {
608             warning("Execute %s failed! %s", binary, e.message);
609             m_setup_pid = 0;
610         }
611
612         GLib.ChildWatch.add(m_setup_pid, (pid, state) => {
613             if (pid != m_setup_pid)
614                 return;
615             m_setup_pid = 0;
616             GLib.Process.close_pid(pid);
617         });
618     }
619
620     private void show_about_dialog() {
621         if (m_about_dialog == null) {
622             m_about_dialog = new Gtk.AboutDialog();
623             m_about_dialog.set_program_name("IBus");
624             m_about_dialog.set_version(Config.PACKAGE_VERSION);
625
626             string copyright = _(
627                 "Copyright (c) 2007-2012 Peng Huang\n" +
628                 "Copyright (c) 2007-2010 Red Hat, Inc.\n");
629
630             m_about_dialog.set_copyright(copyright);
631             m_about_dialog.set_license("LGPL");
632             m_about_dialog.set_comments(_("IBus is an intelligent input bus for Linux/Unix."));
633             m_about_dialog.set_website("http://code.google.com/p/ibus");
634             m_about_dialog.set_authors({"Peng Huang <shawn.p.huang@gmail.com>"});
635             m_about_dialog.set_documenters({"Peng Huang <shawn.p.huang@gmail.com>"});
636             m_about_dialog.set_translator_credits(_("translator-credits"));
637             m_about_dialog.set_logo_icon_name("ibus");
638             m_about_dialog.set_icon_name("ibus");
639         }
640
641         if (!m_about_dialog.get_visible()) {
642             m_about_dialog.run();
643             m_about_dialog.hide();
644         } else {
645             m_about_dialog.present();
646         }
647     }
648
649     private void status_icon_popup_menu_cb(Gtk.StatusIcon status_icon,
650                                            uint button,
651                                            uint activate_time) {
652         // Show system menu
653         if (m_sys_menu == null) {
654             Gtk.ImageMenuItem item;
655             m_sys_menu = new Gtk.Menu();
656
657             item = new Gtk.ImageMenuItem.from_stock(Gtk.Stock.PREFERENCES, null);
658             item.activate.connect((i) => show_setup_dialog());
659             m_sys_menu.append(item);
660
661             item = new Gtk.ImageMenuItem.from_stock(Gtk.Stock.ABOUT, null);
662             item.activate.connect((i) => show_about_dialog());
663             m_sys_menu.append(item);
664
665             m_sys_menu.append(new Gtk.SeparatorMenuItem());
666
667             item = new Gtk.ImageMenuItem.from_stock(Gtk.Stock.REFRESH, null);
668             item.set_label(_("Restart"));
669             item.activate.connect((i) => m_bus.exit(true));
670             m_sys_menu.append(item);
671
672             item = new Gtk.ImageMenuItem.from_stock(Gtk.Stock.QUIT, null);
673             item.activate.connect((i) => m_bus.exit(false));
674             m_sys_menu.append(item);
675
676             m_sys_menu.show_all();
677         }
678
679         m_sys_menu.popup(null,
680                          null,
681                          m_status_icon.position_menu,
682                          0,
683                          Gtk.get_current_event_time());
684     }
685
686     private void status_icon_activate_cb(Gtk.StatusIcon status_icon) {
687         m_ime_menu = new Gtk.Menu();
688
689         // Show properties and IME switching menu
690         m_property_manager.create_menu_items(m_ime_menu);
691
692         m_ime_menu.append(new Gtk.SeparatorMenuItem());
693
694         // Append IMEs
695         foreach (var engine in m_engines) {
696             var language = engine.get_language();
697             var longname = engine.get_longname();
698             var item = new Gtk.ImageMenuItem.with_label(
699                 "%s - %s".printf (IBus.get_language_name(language), longname));
700             if (engine.get_icon() != "") {
701                 var icon = new IconWidget(engine.get_icon(), Gtk.IconSize.MENU);
702                  item.set_image(icon);
703             }
704             // Make a copy of engine to workaround a bug in vala.
705             // https://bugzilla.gnome.org/show_bug.cgi?id=628336
706             var e = engine;
707             item.activate.connect((item) => {
708                 for (int i = 0; i < m_engines.length; i++) {
709                     if (e == m_engines[i]) {
710                         switch_engine(i);
711                         break;
712                     }
713                 }
714             });
715             m_ime_menu.add(item);
716         }
717
718         m_ime_menu.show_all();
719
720         // Do not take focuse to avoid some focus related issues.
721         m_ime_menu.set_take_focus(false);
722         m_ime_menu.popup(null,
723                          null,
724                          m_status_icon.position_menu,
725                          0,
726                          Gtk.get_current_event_time());
727     }
728
729     /* override virtual functions */
730     public override void set_cursor_location(int x, int y,
731                                              int width, int height) {
732         m_candidate_panel.set_cursor_location(x, y, width, height);
733     }
734
735     public override void focus_in(string input_context_path) {
736         if (m_use_global_engine)
737             return;
738
739         /* Do not change the order of m_engines during running switcher. */
740         if (m_switcher_is_running)
741             return;
742
743         m_current_context_path = input_context_path;
744
745         var engine = m_engine_contexts[m_current_context_path];
746
747         if (engine == null) {
748             /* If engine == null, need to call set_engine(m_engines[0])
749              * here and update m_engine_contexts[] to avoid the
750              * following problem:
751              *
752              * If context1 is focused and does not set an engine and
753              * return here, the current engine1 is used for context1.
754              * When context2 is focused and switch engine1 to engine2,
755              * the current engine becomes engine2.
756              * And when context1 is focused again, context1 still
757              * does not set an engine and return here,
758              * engine2 is used for context2 instead of engine1. */
759             engine = m_engines.length > 0 ? m_engines[0] : null;
760
761             if (engine == null)
762                 return;
763         } else {
764             bool in_engines = false;
765
766             foreach (var e in m_engines) {
767                 if (engine.get_name() == e.get_name()) {
768                     in_engines = true;
769                     break;
770                 }
771             }
772
773             /* The engine is deleted by ibus-setup before focus_in()
774              * is called. */
775             if (!in_engines)
776                 return;
777         }
778
779         set_engine(engine);
780     }
781
782     public override void focus_out(string input_context_path) {
783         if (m_use_global_engine)
784             return;
785
786         /* Do not change the order of m_engines during running switcher. */
787         if (m_switcher_is_running)
788             return;
789
790         m_current_context_path = "";
791     }
792
793     public override void destroy_context(string input_context_path) {
794         if (m_use_global_engine)
795             return;
796
797         m_engine_contexts.remove(input_context_path);
798     }
799
800     public override void register_properties(IBus.PropList props) {
801         m_property_manager.set_properties(props);
802     }
803
804     public override void update_property(IBus.Property prop) {
805         m_property_manager.update_property(prop);
806     }
807
808     public override void update_preedit_text(IBus.Text text,
809                                              uint cursor_pos,
810                                              bool visible) {
811         if (visible)
812             m_candidate_panel.set_preedit_text(text, cursor_pos);
813         else
814             m_candidate_panel.set_preedit_text(null, 0);
815     }
816
817     public override void hide_preedit_text() {
818         m_candidate_panel.set_preedit_text(null, 0);
819     }
820
821     public override void update_auxiliary_text(IBus.Text text,
822                                                bool visible) {
823         m_candidate_panel.set_auxiliary_text(visible ? text : null);
824     }
825
826     public override void hide_auxiliary_text() {
827         m_candidate_panel.set_auxiliary_text(null);
828     }
829
830     public override void update_lookup_table(IBus.LookupTable table,
831                                              bool visible) {
832         m_candidate_panel.set_lookup_table(visible ? table : null);
833     }
834
835     public override void hide_lookup_table() {
836         m_candidate_panel.set_lookup_table(null);
837     }
838
839     public override void state_changed() {
840         /* Do not change the order of m_engines during running switcher. */
841         if (m_switcher_is_running)
842             return;
843
844         var icon_name = "ibus-keyboard";
845
846         var engine = m_bus.get_global_engine();
847         if (engine != null)
848             icon_name = engine.get_icon();
849
850         if (icon_name[0] == '/')
851             m_status_icon.set_from_file(icon_name);
852         else {
853             var theme = Gtk.IconTheme.get_default();
854             if (theme.lookup_icon(icon_name, 48, 0) != null) {
855                 m_status_icon.set_from_icon_name(icon_name);
856             } else {
857                 m_status_icon.set_from_icon_name("ibus-engine");
858             }
859         }
860
861         if (engine == null)
862             return;
863
864         int i;
865         for (i = 0; i < m_engines.length; i++) {
866             if (m_engines[i].get_name() == engine.get_name())
867                 break;
868         }
869
870         // engine is first engine in m_engines.
871         if (i == 0)
872             return;
873
874         // engine is not in m_engines.
875         if (i >= m_engines.length)
876             return;
877
878         for (int j = i; j > 0; j--) {
879             m_engines[j] = m_engines[j - 1];
880         }
881         m_engines[0] = engine;
882
883         string[] names = {};
884         foreach(var desc in m_engines) {
885             names += desc.get_name();
886         }
887         m_settings_general.set_strv("engines-order", names);
888     }
889 }