Fix issue - keybinding with Super, Hyper and Meta Modifiers does not work.
[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 using IBus;
24 using GLib;
25 using Gtk;
26 using Posix;
27
28 public extern const string IBUS_VERSION;
29 public extern const string BINDIR;
30
31 class Panel : IBus.PanelService {
32     private IBus.Bus m_bus;
33     private IBus.Config m_config;
34     private Gtk.StatusIcon m_status_icon;
35     private Gtk.Menu m_ime_menu;
36     private Gtk.Menu m_sys_menu;
37     private IBus.EngineDesc[] m_engines = {};
38     private CandidatePanel m_candidate_panel;
39     private Switcher m_switcher;
40     private PropertyManager m_property_manager;
41     private GLib.Pid m_setup_pid = 0;
42     private Gtk.AboutDialog m_about_dialog;
43     private Gtk.CssProvider m_css_provider;
44     private const string ACCELERATOR_SWITCH_IME_FOREWARD = "<Control>space";
45
46     private uint m_switch_keysym = 0;
47     private Gdk.ModifierType m_switch_modifiers = 0;
48
49     public Panel(IBus.Bus bus) {
50         GLib.assert(bus.is_connected());
51         // Chain up base class constructor
52         GLib.Object(connection : bus.get_connection(),
53                     object_path : "/org/freedesktop/IBus/Panel");
54
55         m_bus = bus;
56
57         // init ui
58         m_status_icon = new Gtk.StatusIcon();
59         m_status_icon.set_name("ibus-ui-gtk");
60         m_status_icon.set_title("IBus Panel");
61         m_status_icon.popup_menu.connect(status_icon_popup_menu_cb);
62         m_status_icon.activate.connect(status_icon_activate_cb);
63         m_status_icon.set_from_icon_name("ibus-keyboard");
64
65         m_candidate_panel = new CandidatePanel();
66         m_candidate_panel.page_up.connect((w) => this.page_up());
67         m_candidate_panel.page_down.connect((w) => this.page_down());
68
69         m_switcher = new Switcher();
70         bind_switch_shortcut();
71
72         m_property_manager = new PropertyManager();
73         m_property_manager.property_activate.connect((k, s) => {
74             property_activate(k, s);
75         });
76
77         state_changed();
78     }
79
80     ~Panel() {
81         unbind_switch_shortcut();
82     }
83
84     private void bind_switch_shortcut() {
85         var keybinding_manager = KeybindingManager.get_instance();
86
87         var accelerator = ACCELERATOR_SWITCH_IME_FOREWARD;
88         Gtk.accelerator_parse(accelerator,
89                 out m_switch_keysym, out m_switch_modifiers);
90
91         // Map virtual modifiers to (i.e.Mod2, Mod3, ...)
92         const Gdk.ModifierType VIRTUAL_MODIFIERS = (
93                 Gdk.ModifierType.SUPER_MASK |
94                 Gdk.ModifierType.HYPER_MASK |
95                 Gdk.ModifierType.META_MASK);
96         if ((m_switch_modifiers & VIRTUAL_MODIFIERS) != 0) {
97         // workaround a bug in gdk vapi vala > 0.18
98         // https://bugzilla.gnome.org/show_bug.cgi?id=677559
99 #if VALA_0_18
100             Gdk.Keymap.get_default().map_virtual_modifiers(
101                     ref m_switch_modifiers);
102 #else
103             if ((m_switch_modifiers & Gdk.ModifierType.SUPER_MASK) != 0)
104                 m_switch_modifiers |= Gdk.ModifierType.MOD4_MASK;
105             if ((m_switch_modifiers & Gdk.ModifierType.HYPER_MASK) != 0)
106                 m_switch_modifiers |= Gdk.ModifierType.MOD4_MASK;
107 #endif
108             m_switch_modifiers &= ~VIRTUAL_MODIFIERS;
109         }
110
111         if (m_switch_keysym == 0 && m_switch_modifiers == 0) {
112             warning("Parse accelerator '%s' failed!", accelerator);
113             return;
114         }
115
116         keybinding_manager.bind(m_switch_keysym, m_switch_modifiers,
117                 (e) => handle_engine_switch(e, false));
118
119         // accelerator already has Shift mask
120         if ((m_switch_modifiers & Gdk.ModifierType.SHIFT_MASK) != 0)
121             return;
122
123         keybinding_manager.bind(m_switch_keysym,
124                 m_switch_modifiers | Gdk.ModifierType.SHIFT_MASK,
125                 (e) => handle_engine_switch(e, true));
126     }
127
128     private void unbind_switch_shortcut() {
129         var keybinding_manager = KeybindingManager.get_instance();
130
131         if (m_switch_keysym == 0 && m_switch_modifiers == 0)
132             return;
133
134         keybinding_manager.unbind(m_switch_keysym, m_switch_modifiers);
135         keybinding_manager.unbind(m_switch_keysym,
136                 m_switch_modifiers | Gdk.ModifierType.SHIFT_MASK);
137
138         m_switch_keysym = 0;
139         m_switch_modifiers = 0;
140     }
141
142     private void set_custom_font() {
143         Gdk.Display display = Gdk.Display.get_default();
144         Gdk.Screen screen = (display != null) ?
145                 display.get_default_screen() : null;
146
147         if (screen == null) {
148             warning("Could not open display.");
149             return;
150         }
151
152         bool use_custom_font = false;
153         GLib.Variant var_use_custom_font = m_config.get_value("panel",
154                                                               "use_custom_font");
155
156         if (var_use_custom_font != null) {
157             use_custom_font = var_use_custom_font.get_boolean();
158         }
159
160         if (m_css_provider != null) {
161             Gtk.StyleContext.remove_provider_for_screen(screen,
162                                                         m_css_provider);
163             m_css_provider = null;
164         }
165
166         if (use_custom_font == false) {
167             return;
168         }
169
170         string font_name = null;
171         GLib.Variant var_custom_font = m_config.get_value("panel",
172                                                           "custom_font");
173         if (var_custom_font != null) {
174             font_name = var_custom_font.dup_string();
175         }
176
177         if (font_name == null) {
178             warning("No config panel:custom_font.");
179             return;
180         }
181
182         string data_format = "GtkLabel { font: %s; }";
183         string data = data_format.printf(font_name);
184         m_css_provider = new Gtk.CssProvider();
185
186         try {
187             m_css_provider.load_from_data(data, -1);
188         } catch (GLib.Error e) {
189             warning("Failed css_provider_from_data: %s: %s", font_name,
190                                                              e.message);
191             return;
192         }
193
194         Gtk.StyleContext.add_provider_for_screen(screen,
195                                                  m_css_provider,
196                                                  Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
197     }
198
199     public void set_config(IBus.Config config) {
200         if (m_config != null) {
201             m_config.value_changed.disconnect(config_value_changed_cb);
202             m_config.watch(null, null);
203             m_config = null;
204         }
205
206         m_config = config;
207         if (m_config != null) {
208             m_config.value_changed.connect(config_value_changed_cb);
209             m_config.watch("general", "preload_engines");
210             m_config.watch("general", "engines_order");
211             m_config.watch("panel", "custom_font");
212             m_config.watch("panel", "use_custom_font");
213             update_engines(m_config.get_value("general", "preload_engines"),
214                            m_config.get_value("general", "engines_order"));
215         } else {
216             update_engines(null, null);
217         }
218
219         set_custom_font();
220     }
221
222     private void switch_engine(int i, bool force = false) {
223         GLib.assert(i >= 0 && i < m_engines.length);
224
225         // Do not need switch
226         if (i == 0 && !force)
227             return;
228
229         IBus.EngineDesc engine = m_engines[i];
230
231         if (!m_bus.set_global_engine(engine.get_name())) {
232             warning("Switch engine to %s failed.", engine.get_name());
233             return;
234         }
235         // set xkb layout
236         string cmdline = "setxkbmap %s".printf(engine.get_layout());
237         try {
238             if (!GLib.Process.spawn_command_line_sync(cmdline)) {
239                 warning("Switch xkb layout to %s failed.",
240                     engine.get_layout());
241             }
242         } catch (GLib.SpawnError e) {
243             warning("execute setxkblayout failed");
244         }
245     }
246
247     private void config_value_changed_cb(IBus.Config config,
248                                          string section,
249                                          string name,
250                                          Variant variant) {
251         if (section == "general" && name == "preload_engines") {
252             update_engines(variant, null);
253             return;
254         }
255
256         if (section == "panel" && (name == "custom_font" ||
257                                    name == "use_custom_font")) {
258             set_custom_font();
259             return;
260         }
261     }
262
263     private void handle_engine_switch(Gdk.Event event, bool revert) {
264         // Do not need switch IME
265         if (m_engines.length <= 1)
266             return;
267
268         uint primary_modifiers =
269             KeybindingManager.get_primary_modifier(event.key.state);
270
271         bool pressed = KeybindingManager.primary_modifier_still_pressed(
272                 event, primary_modifiers);
273         if (pressed) {
274             int i = revert ? m_engines.length - 1 : 1;
275             i = m_switcher.run(m_switch_keysym, m_switch_modifiers, event,
276                     m_engines, i);
277             if (i < 0) {
278                 debug("switch cancelled");
279             } else {
280                 GLib.assert(i < m_engines.length);
281                 switch_engine(i);
282             }
283         } else {
284             int i = revert ? m_engines.length - 1 : 1;
285             switch_engine(i);
286         }
287     }
288
289     private void update_engines(GLib.Variant? var_engines,
290                                 GLib.Variant? var_order) {
291         string[] engine_names = null;
292
293         if (var_engines != null)
294             engine_names = var_engines.dup_strv();
295         if (engine_names == null || engine_names.length == 0)
296             engine_names = {"xkb:us::eng"};
297
298         string[] order_names =
299             (var_order != null) ? var_order.dup_strv() : null;
300
301         string[] names = {};
302
303         foreach (var name in order_names) {
304             if (name in engine_names)
305                 names += name;
306         }
307
308         foreach (var name in engine_names) {
309             if (name in names)
310                 continue;
311             names += name;
312         }
313
314         var engines = m_bus.get_engines_by_names(names);
315
316         if (m_engines.length == 0) {
317             m_engines = engines;
318             switch_engine(0, true);
319         } else {
320             var current_engine = m_engines[0];
321             m_engines = engines;
322             int i;
323             for (i = 0; i < m_engines.length; i++) {
324                 if (current_engine.get_name() == engines[i].get_name()) {
325                     switch_engine(i);
326                     return;
327                 }
328             }
329             switch_engine(0, true);
330         }
331
332     }
333
334     private void show_setup_dialog() {
335         if (m_setup_pid != 0) {
336             if (Posix.kill(m_setup_pid, Posix.SIGUSR1) == 0)
337                 return;
338             m_setup_pid = 0;
339         }
340
341         string binary = GLib.Path.build_filename(BINDIR, "ibus-setup");
342         try {
343             GLib.Process.spawn_async(null,
344                                      {binary, "ibus-setup"},
345                                      null,
346                                      GLib.SpawnFlags.DO_NOT_REAP_CHILD,
347                                      null,
348                                      out m_setup_pid);
349         } catch (GLib.SpawnError e) {
350             warning("Execute %s failed! %s", binary, e.message);
351             m_setup_pid = 0;
352         }
353
354         GLib.ChildWatch.add(m_setup_pid, (pid, state) => {
355             if (pid != m_setup_pid)
356                 return;
357             m_setup_pid = 0;
358             GLib.Process.close_pid(pid);
359         });
360     }
361
362     private void show_about_dialog() {
363         if (m_about_dialog == null) {
364             m_about_dialog = new Gtk.AboutDialog();
365             m_about_dialog.set_program_name("IBus");
366             m_about_dialog.set_version(IBUS_VERSION);
367
368             string copyright = _(
369                 "Copyright (c) 2007-2012 Peng Huang\n" +
370                 "Copyright (c) 2007-2010 Red Hat, Inc.\n");
371
372             m_about_dialog.set_copyright(copyright);
373             m_about_dialog.set_license("LGPL");
374             m_about_dialog.set_comments(_("IBus is an intelligent input bus for Linux/Unix."));
375             m_about_dialog.set_website("http://code.google.com/p/ibus");
376             m_about_dialog.set_authors({"Peng Huang <shawn.p.huang@gmail.com>"});
377             m_about_dialog.set_documenters({"Peng Huang <shawn.p.huang@gmail.com>"});
378             m_about_dialog.set_translator_credits(_("translator-credits"));
379             m_about_dialog.set_logo_icon_name("ibus");
380             m_about_dialog.set_icon_name("ibus");
381         }
382
383         if (!m_about_dialog.get_visible()) {
384             m_about_dialog.run();
385             m_about_dialog.hide();
386         } else {
387             m_about_dialog.present();
388         }
389     }
390
391     private void status_icon_popup_menu_cb(Gtk.StatusIcon status_icon,
392                                            uint button,
393                                            uint activate_time) {
394         // Show system menu
395         if (m_sys_menu == null) {
396             Gtk.ImageMenuItem item;
397             m_sys_menu = new Gtk.Menu();
398
399             item = new Gtk.ImageMenuItem.from_stock(Gtk.Stock.PREFERENCES, null);
400             item.activate.connect((i) => show_setup_dialog());
401             m_sys_menu.append(item);
402
403             item = new Gtk.ImageMenuItem.from_stock(Gtk.Stock.ABOUT, null);
404             item.activate.connect((i) => show_about_dialog());
405             m_sys_menu.append(item);
406
407             m_sys_menu.append(new SeparatorMenuItem());
408
409             item = new Gtk.ImageMenuItem.from_stock(Gtk.Stock.REFRESH, null);
410             item.set_label(_("Restart"));
411             item.activate.connect((i) => m_bus.exit(true));
412             m_sys_menu.append(item);
413
414             item = new Gtk.ImageMenuItem.from_stock(Gtk.Stock.QUIT, null);
415             item.activate.connect((i) => m_bus.exit(false));
416             m_sys_menu.append(item);
417
418             m_sys_menu.show_all();
419         }
420
421         m_sys_menu.popup(null,
422                          null,
423                          m_status_icon.position_menu,
424                          0,
425                          Gtk.get_current_event_time());
426     }
427
428     private void status_icon_activate_cb(Gtk.StatusIcon status_icon) {
429         m_ime_menu = new Gtk.Menu();
430
431         // Show properties and IME switching menu
432         m_property_manager.create_menu_items(m_ime_menu);
433
434         m_ime_menu.append(new SeparatorMenuItem());
435
436         int width, height;
437         Gtk.icon_size_lookup(Gtk.IconSize.MENU, out width, out height);
438
439         // Append IMEs
440         foreach (var engine in m_engines) {
441             var language = engine.get_language();
442             var longname = engine.get_longname();
443             var item = new Gtk.ImageMenuItem.with_label(
444                 "%s - %s".printf (IBus.get_language_name(language), longname));
445             if (engine.get_icon() != "") {
446                 var icon = new IconWidget(engine.get_icon(), width);
447                  item.set_image(icon);
448             }
449             // Make a copy of engine to workaround a bug in vala.
450             // https://bugzilla.gnome.org/show_bug.cgi?id=628336
451             var e = engine;
452             item.activate.connect((item) => {
453                 for (int i = 0; i < m_engines.length; i++) {
454                     if (e == m_engines[i]) {
455                         switch_engine(i);
456                         break;
457                     }
458                 }
459             });
460             m_ime_menu.add(item);
461         }
462
463         m_ime_menu.show_all();
464
465         // Do not take focuse to avoid some focus related issues.
466         m_ime_menu.set_take_focus(false);
467         m_ime_menu.popup(null,
468                          null,
469                          m_status_icon.position_menu,
470                          0,
471                          Gtk.get_current_event_time());
472     }
473
474     /* override virtual functions */
475     public override void set_cursor_location(int x, int y,
476                                              int width, int height) {
477         m_candidate_panel.set_cursor_location(x, y, width, height);
478     }
479
480     public override void focus_in(string input_context_path) {
481     }
482
483     public override void focus_out(string input_context_path) {
484     }
485
486     public override void register_properties(IBus.PropList props) {
487         m_property_manager.set_properties(props);
488     }
489
490     public override void update_property(IBus.Property prop) {
491         m_property_manager.update_property(prop);
492     }
493
494     public override void update_preedit_text(IBus.Text text,
495                                              uint cursor_pos,
496                                              bool visible) {
497         if (visible)
498             m_candidate_panel.set_preedit_text(text, cursor_pos);
499         else
500             m_candidate_panel.set_preedit_text(null, 0);
501     }
502
503     public override void hide_preedit_text() {
504         m_candidate_panel.set_preedit_text(null, 0);
505     }
506
507     public override void update_auxiliary_text(IBus.Text text,
508                                                bool visible) {
509         m_candidate_panel.set_auxiliary_text(visible ? text : null);
510     }
511
512     public override void hide_auxiliary_text() {
513         m_candidate_panel.set_auxiliary_text(null);
514     }
515
516     public override void update_lookup_table(IBus.LookupTable table,
517                                              bool visible) {
518         m_candidate_panel.set_lookup_table(visible ? table : null);
519     }
520
521     public override void hide_lookup_table() {
522         m_candidate_panel.set_lookup_table(null);
523     }
524
525     public override void state_changed() {
526         var icon_name = "ibus-keyboard";
527
528         var engine = m_bus.get_global_engine();
529         if (engine != null)
530             icon_name = engine.get_icon();
531
532         if (icon_name[0] == '/')
533             m_status_icon.set_from_file(icon_name);
534         else
535             m_status_icon.set_from_icon_name(icon_name);
536
537         if (engine == null)
538             return;
539
540         int i;
541         for (i = 0; i < m_engines.length; i++) {
542             if (m_engines[i].get_name() == engine.get_name())
543                 break;
544         }
545
546         // engine is first engine in m_engines.
547         if (i == 0)
548             return;
549
550         // engine is not in m_engines.
551         if (i >= m_engines.length)
552             return;
553
554         for (int j = i; j > 0; j--) {
555             m_engines[j] = m_engines[j - 1];
556         }
557         m_engines[0] = engine;
558
559         string[] names = {};
560         foreach(var desc in m_engines) {
561             names += desc.get_name();
562         }
563         if (m_config != null)
564             m_config.set_value("general",
565                                "engines_order",
566                                new GLib.Variant.strv(names));
567     }
568 }