0c793f43f7f374c29919d897d74ce283d6ad8dd2
[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 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 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
15  * GNU 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 program; if not, write to the
19  * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
20  * Boston, MA  02111-1307  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 IBus.Config m_config;
40     private Gtk.StatusIcon m_status_icon;
41     private Gtk.Menu m_ime_menu;
42     private Gtk.Menu m_sys_menu;
43     private IBus.EngineDesc[] m_engines = {};
44     private CandidatePanel m_candidate_panel;
45     private Switcher m_switcher;
46     private PropertyManager m_property_manager;
47     private GLib.Pid m_setup_pid = 0;
48     private Gtk.AboutDialog m_about_dialog;
49     private Gtk.CssProvider m_css_provider;
50     private int m_switcher_delay_time = 400;
51     private bool m_use_system_keyboard_layout = false;
52     private const string ACCELERATOR_SWITCH_IME_FOREWARD = "<Super>space";
53
54     private GLib.List<Keybinding> m_keybindings = new GLib.List<Keybinding>();
55
56     public Panel(IBus.Bus bus) {
57         GLib.assert(bus.is_connected());
58         // Chain up base class constructor
59         GLib.Object(connection : bus.get_connection(),
60                     object_path : "/org/freedesktop/IBus/Panel");
61
62         m_bus = bus;
63
64         // init ui
65         m_status_icon = new Gtk.StatusIcon();
66         m_status_icon.set_name("ibus-ui-gtk");
67         m_status_icon.set_title("IBus Panel");
68         m_status_icon.popup_menu.connect(status_icon_popup_menu_cb);
69         m_status_icon.activate.connect(status_icon_activate_cb);
70         m_status_icon.set_from_icon_name("ibus-keyboard");
71
72         m_candidate_panel = new CandidatePanel();
73         m_candidate_panel.page_up.connect((w) => this.page_up());
74         m_candidate_panel.page_down.connect((w) => this.page_down());
75
76         m_switcher = new Switcher();
77         // The initial shortcut is "<Super>space"
78         bind_switch_shortcut(null);
79
80         if (m_switcher_delay_time >= 0) {
81             m_switcher.set_popup_delay_time((uint) m_switcher_delay_time);
82         }
83
84         m_property_manager = new PropertyManager();
85         m_property_manager.property_activate.connect((k, s) => {
86             property_activate(k, s);
87         });
88
89         state_changed();
90     }
91
92     ~Panel() {
93         unbind_switch_shortcut();
94     }
95
96     private void keybinding_manager_bind(KeybindingManager keybinding_manager,
97                                          string?           accelerator) {
98         uint switch_keysym = 0;
99         Gdk.ModifierType switch_modifiers = 0;
100         Gdk.ModifierType reverse_modifier = Gdk.ModifierType.SHIFT_MASK;
101         Keybinding keybinding;
102
103         Gtk.accelerator_parse(accelerator,
104                 out switch_keysym, out switch_modifiers);
105
106         // Map virtual modifiers to (i.e. Mod2, Mod3, ...)
107         const Gdk.ModifierType VIRTUAL_MODIFIERS = (
108                 Gdk.ModifierType.SUPER_MASK |
109                 Gdk.ModifierType.HYPER_MASK |
110                 Gdk.ModifierType.META_MASK);
111         if ((switch_modifiers & VIRTUAL_MODIFIERS) != 0) {
112         // workaround a bug in gdk vapi vala > 0.18
113         // https://bugzilla.gnome.org/show_bug.cgi?id=677559
114 #if VALA_0_18
115             Gdk.Keymap.get_default().map_virtual_modifiers(
116                     ref switch_modifiers);
117 #else
118             if ((switch_modifiers & Gdk.ModifierType.SUPER_MASK) != 0)
119                 switch_modifiers |= Gdk.ModifierType.MOD4_MASK;
120             if ((switch_modifiers & Gdk.ModifierType.HYPER_MASK) != 0)
121                 switch_modifiers |= Gdk.ModifierType.MOD4_MASK;
122 #endif
123             switch_modifiers &= ~VIRTUAL_MODIFIERS;
124         }
125
126         if (switch_keysym == 0 && switch_modifiers == 0) {
127             warning("Parse accelerator '%s' failed!", accelerator);
128             return;
129         }
130
131         keybinding = new Keybinding(switch_keysym,
132                                     switch_modifiers,
133                                     false);
134         m_keybindings.append(keybinding);
135
136         keybinding_manager.bind(switch_keysym, switch_modifiers,
137                 (e) => handle_engine_switch(e, false));
138
139         // accelerator already has Shift mask
140         if ((switch_modifiers & reverse_modifier) != 0) {
141             return;
142         }
143
144         switch_modifiers |= reverse_modifier;
145
146         keybinding = new Keybinding(switch_keysym,
147                                     switch_modifiers,
148                                     true);
149         m_keybindings.append(keybinding);
150
151         keybinding_manager.bind(switch_keysym, switch_modifiers,
152                 (e) => handle_engine_switch(e, true));
153     }
154
155     private void bind_switch_shortcut(Variant? variant) {
156         string[] accelerators = {};
157         Variant var_trigger = variant;
158
159         if (var_trigger == null && m_config != null) {
160             var_trigger = m_config.get_value("general/hotkey",
161                                              "triggers");
162         }
163
164         if (var_trigger != null) {
165             accelerators = var_trigger.dup_strv();
166         } else {
167             accelerators += ACCELERATOR_SWITCH_IME_FOREWARD;
168         }
169
170         var keybinding_manager = KeybindingManager.get_instance();
171
172         foreach (var accelerator in accelerators) {
173             keybinding_manager_bind(keybinding_manager, accelerator);
174         }
175     }
176
177     private void unbind_switch_shortcut() {
178         var keybinding_manager = KeybindingManager.get_instance();
179
180         unowned GLib.List<Keybinding> keybindings = m_keybindings;
181
182         while (keybindings != null) {
183             Keybinding keybinding = keybindings.data;
184
185             keybinding_manager.unbind(keybinding.keysym,
186                                       keybinding.modifiers);
187             keybindings = keybindings.next;
188         }
189
190         m_keybindings = null;
191     }
192
193     private void set_custom_font() {
194         Gdk.Display display = Gdk.Display.get_default();
195         Gdk.Screen screen = (display != null) ?
196                 display.get_default_screen() : null;
197
198         if (screen == null) {
199             warning("Could not open display.");
200             return;
201         }
202
203         bool use_custom_font = false;
204         GLib.Variant var_use_custom_font = m_config.get_value("panel",
205                                                               "use_custom_font");
206
207         if (var_use_custom_font != null) {
208             use_custom_font = var_use_custom_font.get_boolean();
209         }
210
211         if (m_css_provider != null) {
212             Gtk.StyleContext.remove_provider_for_screen(screen,
213                                                         m_css_provider);
214             m_css_provider = null;
215         }
216
217         if (use_custom_font == false) {
218             return;
219         }
220
221         string font_name = null;
222         GLib.Variant var_custom_font = m_config.get_value("panel",
223                                                           "custom_font");
224         if (var_custom_font != null) {
225             font_name = var_custom_font.dup_string();
226         }
227
228         if (font_name == null) {
229             warning("No config panel:custom_font.");
230             return;
231         }
232
233         string data_format = "GtkLabel { font: %s; }";
234         string data = data_format.printf(font_name);
235         m_css_provider = new Gtk.CssProvider();
236
237         try {
238             m_css_provider.load_from_data(data, -1);
239         } catch (GLib.Error e) {
240             warning("Failed css_provider_from_data: %s: %s", font_name,
241                                                              e.message);
242             return;
243         }
244
245         Gtk.StyleContext.add_provider_for_screen(screen,
246                                                  m_css_provider,
247                                                  Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
248     }
249
250     private void set_switcher_delay_time(Variant? variant) {
251         Variant var_switcher_delay_time = variant;
252
253         if (var_switcher_delay_time == null) {
254             var_switcher_delay_time = m_config.get_value("general",
255                                                          "switcher-delay-time");
256         }
257
258         if (var_switcher_delay_time == null) {
259             return;
260         }
261
262         m_switcher_delay_time = var_switcher_delay_time.get_int32();
263
264         if (m_switcher_delay_time >= 0) {
265             m_switcher.set_popup_delay_time((uint) m_switcher_delay_time);
266         }
267     }
268
269     private void set_use_system_keyboard_layout(Variant? variant) {
270         Variant var_use_system_kbd_layout = variant;
271
272         if (var_use_system_kbd_layout == null) {
273             var_use_system_kbd_layout = m_config.get_value(
274                     "general",
275                     "use_system_keyboard_layout");
276         }
277
278         if (var_use_system_kbd_layout == null) {
279             return;
280         }
281
282         m_use_system_keyboard_layout = var_use_system_kbd_layout.get_boolean();
283     }
284
285     private void set_embed_preedit_text(Variant? variant) {
286         Variant var_embed_preedit = variant;
287
288         if (var_embed_preedit == null) {
289             var_embed_preedit = m_config.get_value("general",
290                                                    "embed_preedit_text");
291         }
292
293         if (var_embed_preedit == null) {
294             return;
295         }
296
297         m_bus.set_ibus_property("EmbedPreeditText",
298                                 var_embed_preedit);
299     }
300
301     public void set_config(IBus.Config config) {
302         if (m_config != null) {
303             m_config.value_changed.disconnect(config_value_changed_cb);
304             m_config.watch(null, null);
305             m_config = null;
306         }
307
308         m_config = config;
309         if (m_config != null) {
310             m_config.value_changed.connect(config_value_changed_cb);
311             m_config.watch("general", "preload_engines");
312             m_config.watch("general", "embed_preedit_text");
313             m_config.watch("general", "engines_order");
314             m_config.watch("general", "switcher_delay_time");
315             m_config.watch("general", "use_system_keyboard_layout");
316             m_config.watch("general/hotkey", "triggers");
317             m_config.watch("panel", "custom_font");
318             m_config.watch("panel", "use_custom_font");
319             // Update m_use_system_keyboard_layout before update_engines()
320             // is called.
321             set_use_system_keyboard_layout(null);
322             update_engines(m_config.get_value("general", "preload_engines"),
323                            m_config.get_value("general", "engines_order"));
324             unbind_switch_shortcut();
325             bind_switch_shortcut(null);
326             set_switcher_delay_time(null);
327             set_embed_preedit_text(null);
328         } else {
329             update_engines(null, null);
330         }
331
332         set_custom_font();
333     }
334
335     private void exec_setxkbmap(IBus.EngineDesc engine) {
336         string layout = engine.get_layout();
337         string variant = engine.get_layout_variant();
338         string option = engine.get_layout_option();
339         string standard_error = null;
340         int exit_status = 0;
341         string[] args = { "setxkbmap" };
342
343         if (layout != null && layout != "" && layout != "default") {
344             args += "-layout";
345             args += layout;
346         }
347         if (variant != null && variant != "" && variant != "default") {
348             args += "-variant";
349             args += variant;
350         }
351         if (option != null && option != "" && option != "default") {
352             /*TODO: Need to get the session XKB options */
353             args += "-option";
354             args += "-option";
355             args += option;
356         }
357
358         if (args.length == 1) {
359             return;
360         }
361
362         try {
363             if (!GLib.Process.spawn_sync(null, args, null,
364                                          GLib.SpawnFlags.SEARCH_PATH,
365                                          null, null,
366                                          out standard_error,
367                                          out exit_status)) {
368                 warning("Switch xkb layout to %s failed.",
369                         engine.get_layout());
370             }
371         } catch (GLib.SpawnError e) {
372             warning("Execute setxkbmap failed: %s", e.message);
373         }
374
375         if (exit_status != 0) {
376             warning("Execute setxkbmap failed: %s", standard_error ?? "(null)");
377         }
378     }
379
380     private void switch_engine(int i, bool force = false) {
381         GLib.assert(i >= 0 && i < m_engines.length);
382
383         // Do not need switch
384         if (i == 0 && !force)
385             return;
386
387         IBus.EngineDesc engine = m_engines[i];
388
389         if (!m_bus.set_global_engine(engine.get_name())) {
390             warning("Switch engine to %s failed.", engine.get_name());
391             return;
392         }
393         // set xkb layout
394         if (!m_use_system_keyboard_layout) {
395             exec_setxkbmap(engine);
396         }
397     }
398
399     private void config_value_changed_cb(IBus.Config config,
400                                          string section,
401                                          string name,
402                                          Variant variant) {
403         if (section == "general" && name == "preload_engines") {
404             update_engines(variant, null);
405             return;
406         }
407
408         if (section == "general/hotkey" && name == "triggers") {
409             unbind_switch_shortcut();
410             bind_switch_shortcut(variant);
411             return;
412         }
413
414         if (section == "panel" && (name == "custom_font" ||
415                                    name == "use_custom_font")) {
416             set_custom_font();
417             return;
418         }
419
420         if (section == "general" && name == "switcher_delay_time") {
421             set_switcher_delay_time(variant);
422             return;
423         }
424
425         if (section == "general" && name == "use_system_keyboard_layout") {
426             set_use_system_keyboard_layout(variant);
427             return;
428         }
429
430         if (section == "general" && name == "embed_preedit_text") {
431             set_embed_preedit_text(variant);
432             return;
433         }
434     }
435
436     private void handle_engine_switch(Gdk.Event event, bool revert) {
437         // Do not need switch IME
438         if (m_engines.length <= 1)
439             return;
440
441         uint keyval = event.key.keyval;
442         uint modifiers = KeybindingManager.MODIFIER_FILTER & event.key.state;
443
444         uint primary_modifiers =
445             KeybindingManager.get_primary_modifier(event.key.state);
446
447         bool pressed = KeybindingManager.primary_modifier_still_pressed(
448                 event, primary_modifiers);
449
450         if (revert) {
451             modifiers &= ~Gdk.ModifierType.SHIFT_MASK;
452         }
453
454         if (pressed && m_switcher_delay_time >= 0) {
455             int i = revert ? m_engines.length - 1 : 1;
456             i = m_switcher.run(keyval, modifiers, event, m_engines, i);
457             if (i < 0) {
458                 debug("switch cancelled");
459             } else {
460                 GLib.assert(i < m_engines.length);
461                 switch_engine(i);
462             }
463         } else {
464             int i = revert ? m_engines.length - 1 : 1;
465             switch_engine(i);
466         }
467     }
468
469     private void run_preload_engines(IBus.EngineDesc[] engines, int index) {
470         string[] names = {};
471
472         if (engines.length <= index) {
473             return;
474         }
475
476         names += engines[index].get_name();
477         m_bus.preload_engines_async(names, -1, null);
478     }
479
480     private void update_engines(GLib.Variant? var_engines,
481                                 GLib.Variant? var_order) {
482         string[] engine_names = null;
483
484         if (var_engines != null)
485             engine_names = var_engines.dup_strv();
486         if (engine_names == null || engine_names.length == 0)
487             engine_names = {"xkb:us::eng"};
488
489         string[] order_names =
490             (var_order != null) ? var_order.dup_strv() : null;
491
492         string[] names = {};
493
494         foreach (var name in order_names) {
495             if (name in engine_names)
496                 names += name;
497         }
498
499         foreach (var name in engine_names) {
500             if (name in names)
501                 continue;
502             names += name;
503         }
504
505         var engines = m_bus.get_engines_by_names(names);
506
507         if (m_engines.length == 0) {
508             m_engines = engines;
509             switch_engine(0, true);
510             run_preload_engines(engines, 1);
511         } else {
512             var current_engine = m_engines[0];
513             m_engines = engines;
514             int i;
515             for (i = 0; i < m_engines.length; i++) {
516                 if (current_engine.get_name() == engines[i].get_name()) {
517                     switch_engine(i);
518                     if (i != 0) {
519                         run_preload_engines(engines, 0);
520                     } else {
521                         run_preload_engines(engines, 1);
522                     }
523                     return;
524                 }
525             }
526             switch_engine(0, true);
527             run_preload_engines(engines, 1);
528         }
529
530     }
531
532     private void show_setup_dialog() {
533         if (m_setup_pid != 0) {
534             if (Posix.kill(m_setup_pid, Posix.SIGUSR1) == 0)
535                 return;
536             m_setup_pid = 0;
537         }
538
539         string binary = GLib.Path.build_filename(Config.BINDIR, "ibus-setup");
540         try {
541             GLib.Process.spawn_async(null,
542                                      {binary, "ibus-setup"},
543                                      null,
544                                      GLib.SpawnFlags.DO_NOT_REAP_CHILD,
545                                      null,
546                                      out m_setup_pid);
547         } catch (GLib.SpawnError e) {
548             warning("Execute %s failed! %s", binary, e.message);
549             m_setup_pid = 0;
550         }
551
552         GLib.ChildWatch.add(m_setup_pid, (pid, state) => {
553             if (pid != m_setup_pid)
554                 return;
555             m_setup_pid = 0;
556             GLib.Process.close_pid(pid);
557         });
558     }
559
560     private void show_about_dialog() {
561         if (m_about_dialog == null) {
562             m_about_dialog = new Gtk.AboutDialog();
563             m_about_dialog.set_program_name("IBus");
564             m_about_dialog.set_version(Config.PACKAGE_VERSION);
565
566             string copyright = _(
567                 "Copyright (c) 2007-2012 Peng Huang\n" +
568                 "Copyright (c) 2007-2010 Red Hat, Inc.\n");
569
570             m_about_dialog.set_copyright(copyright);
571             m_about_dialog.set_license("LGPL");
572             m_about_dialog.set_comments(_("IBus is an intelligent input bus for Linux/Unix."));
573             m_about_dialog.set_website("http://code.google.com/p/ibus");
574             m_about_dialog.set_authors({"Peng Huang <shawn.p.huang@gmail.com>"});
575             m_about_dialog.set_documenters({"Peng Huang <shawn.p.huang@gmail.com>"});
576             m_about_dialog.set_translator_credits(_("translator-credits"));
577             m_about_dialog.set_logo_icon_name("ibus");
578             m_about_dialog.set_icon_name("ibus");
579         }
580
581         if (!m_about_dialog.get_visible()) {
582             m_about_dialog.run();
583             m_about_dialog.hide();
584         } else {
585             m_about_dialog.present();
586         }
587     }
588
589     private void status_icon_popup_menu_cb(Gtk.StatusIcon status_icon,
590                                            uint button,
591                                            uint activate_time) {
592         // Show system menu
593         if (m_sys_menu == null) {
594             Gtk.ImageMenuItem item;
595             m_sys_menu = new Gtk.Menu();
596
597             item = new Gtk.ImageMenuItem.from_stock(Gtk.Stock.PREFERENCES, null);
598             item.activate.connect((i) => show_setup_dialog());
599             m_sys_menu.append(item);
600
601             item = new Gtk.ImageMenuItem.from_stock(Gtk.Stock.ABOUT, null);
602             item.activate.connect((i) => show_about_dialog());
603             m_sys_menu.append(item);
604
605             m_sys_menu.append(new Gtk.SeparatorMenuItem());
606
607             item = new Gtk.ImageMenuItem.from_stock(Gtk.Stock.REFRESH, null);
608             item.set_label(_("Restart"));
609             item.activate.connect((i) => m_bus.exit(true));
610             m_sys_menu.append(item);
611
612             item = new Gtk.ImageMenuItem.from_stock(Gtk.Stock.QUIT, null);
613             item.activate.connect((i) => m_bus.exit(false));
614             m_sys_menu.append(item);
615
616             m_sys_menu.show_all();
617         }
618
619         m_sys_menu.popup(null,
620                          null,
621                          m_status_icon.position_menu,
622                          0,
623                          Gtk.get_current_event_time());
624     }
625
626     private void status_icon_activate_cb(Gtk.StatusIcon status_icon) {
627         m_ime_menu = new Gtk.Menu();
628
629         // Show properties and IME switching menu
630         m_property_manager.create_menu_items(m_ime_menu);
631
632         m_ime_menu.append(new Gtk.SeparatorMenuItem());
633
634         // Append IMEs
635         foreach (var engine in m_engines) {
636             var language = engine.get_language();
637             var longname = engine.get_longname();
638             var item = new Gtk.ImageMenuItem.with_label(
639                 "%s - %s".printf (IBus.get_language_name(language), longname));
640             if (engine.get_icon() != "") {
641                 var icon = new IconWidget(engine.get_icon(), Gtk.IconSize.MENU);
642                  item.set_image(icon);
643             }
644             // Make a copy of engine to workaround a bug in vala.
645             // https://bugzilla.gnome.org/show_bug.cgi?id=628336
646             var e = engine;
647             item.activate.connect((item) => {
648                 for (int i = 0; i < m_engines.length; i++) {
649                     if (e == m_engines[i]) {
650                         switch_engine(i);
651                         break;
652                     }
653                 }
654             });
655             m_ime_menu.add(item);
656         }
657
658         m_ime_menu.show_all();
659
660         // Do not take focuse to avoid some focus related issues.
661         m_ime_menu.set_take_focus(false);
662         m_ime_menu.popup(null,
663                          null,
664                          m_status_icon.position_menu,
665                          0,
666                          Gtk.get_current_event_time());
667     }
668
669     /* override virtual functions */
670     public override void set_cursor_location(int x, int y,
671                                              int width, int height) {
672         m_candidate_panel.set_cursor_location(x, y, width, height);
673     }
674
675     public override void focus_in(string input_context_path) {
676     }
677
678     public override void focus_out(string input_context_path) {
679     }
680
681     public override void register_properties(IBus.PropList props) {
682         m_property_manager.set_properties(props);
683     }
684
685     public override void update_property(IBus.Property prop) {
686         m_property_manager.update_property(prop);
687     }
688
689     public override void update_preedit_text(IBus.Text text,
690                                              uint cursor_pos,
691                                              bool visible) {
692         if (visible)
693             m_candidate_panel.set_preedit_text(text, cursor_pos);
694         else
695             m_candidate_panel.set_preedit_text(null, 0);
696     }
697
698     public override void hide_preedit_text() {
699         m_candidate_panel.set_preedit_text(null, 0);
700     }
701
702     public override void update_auxiliary_text(IBus.Text text,
703                                                bool visible) {
704         m_candidate_panel.set_auxiliary_text(visible ? text : null);
705     }
706
707     public override void hide_auxiliary_text() {
708         m_candidate_panel.set_auxiliary_text(null);
709     }
710
711     public override void update_lookup_table(IBus.LookupTable table,
712                                              bool visible) {
713         m_candidate_panel.set_lookup_table(visible ? table : null);
714     }
715
716     public override void hide_lookup_table() {
717         m_candidate_panel.set_lookup_table(null);
718     }
719
720     public override void state_changed() {
721         var icon_name = "ibus-keyboard";
722
723         var engine = m_bus.get_global_engine();
724         if (engine != null)
725             icon_name = engine.get_icon();
726
727         if (icon_name[0] == '/')
728             m_status_icon.set_from_file(icon_name);
729         else {
730             var theme = Gtk.IconTheme.get_default();
731             if (theme.lookup_icon(icon_name, 48, 0) != null) {
732                 m_status_icon.set_from_icon_name(icon_name);
733             } else {
734                 m_status_icon.set_from_icon_name("ibus-engine");
735             }
736         }
737
738         if (engine == null)
739             return;
740
741         int i;
742         for (i = 0; i < m_engines.length; i++) {
743             if (m_engines[i].get_name() == engine.get_name())
744                 break;
745         }
746
747         // engine is first engine in m_engines.
748         if (i == 0)
749             return;
750
751         // engine is not in m_engines.
752         if (i >= m_engines.length)
753             return;
754
755         for (int j = i; j > 0; j--) {
756             m_engines[j] = m_engines[j - 1];
757         }
758         m_engines[0] = engine;
759
760         string[] names = {};
761         foreach(var desc in m_engines) {
762             names += desc.get_name();
763         }
764         if (m_config != null)
765             m_config.set_value("general",
766                                "engines_order",
767                                new GLib.Variant.strv(names));
768     }
769 }