Change IME icon on systray.
[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
27 class Panel : IBus.PanelService {
28     private IBus.Bus m_bus;
29     private IBus.Config m_config;
30     private Gtk.StatusIcon m_status_icon;
31     private Gtk.Menu m_ime_menu;
32     private IBus.EngineDesc[] m_engines = {};
33     private CandidatePanel m_candidate_panel;
34     private Switcher m_switcher;
35     private PropertyManager m_property_manager;
36     private IBus.InputContext m_input_context;
37
38     public Panel(IBus.Bus bus) {
39         assert(bus.is_connected());
40         // Chain up base class constructor
41         GLib.Object(connection : bus.get_connection(),
42                     object_path : "/org/freedesktop/IBus/Panel");
43
44         m_bus = bus;
45
46         m_config = bus.get_config();
47         assert(m_config != null);
48
49         m_config.value_changed.connect(handle_config_value_changed);
50
51         // init ui
52         m_status_icon = new Gtk.StatusIcon();
53         m_status_icon.set_name("ibus-ui-gtk");
54         m_status_icon.set_title("IBus Panel");
55         m_status_icon.popup_menu.connect(status_icon_popup_menu);
56         m_status_icon.activate.connect(status_icon_activate);
57         m_status_icon.set_from_icon_name("ibus-keyboard");
58
59         m_candidate_panel = new CandidatePanel();
60
61         m_candidate_panel.hide();
62         m_candidate_panel.show();
63
64         update_engines(m_config.get_value("general", "preload_engines"),
65                        m_config.get_value("general", "engines_order"));
66
67         m_switcher = new Switcher();
68
69         KeybindingManager.get_instance().bind("<Control>space", (e) => {
70             handle_engine_switch(e, false);
71         });
72
73         KeybindingManager.get_instance().bind("<Control><Shift>space", (e) => {
74             handle_engine_switch(e, true);
75         });
76
77         m_property_manager = new PropertyManager();
78         m_property_manager.property_activate.connect((k, s) => {
79             if (m_input_context != null)
80                 m_input_context.property_activate(k, s);
81         });
82
83         state_changed();
84     }
85
86     private void switch_engine(int i, bool force = false) {
87         //  debug("switch_engine i = %d", i);
88         assert(i >= 0 && i < m_engines.length);
89
90         // Do not need switch
91         if (i == 0 && !force)
92             return;
93
94         // Move the target engine to the first place.
95         IBus.EngineDesc engine = m_engines[i];
96         for (int j = i; j > 0; j--) {
97             m_engines[j] = m_engines[j - 1];
98         }
99         m_engines[0] = engine;
100
101         if (!m_bus.set_global_engine(engine.get_name())) {
102             warning("Switch engine to %s failed.", engine.get_name());
103             return;
104         }
105         // set xkb layout
106         string cmdline = "setxkbmap %s".printf(engine.get_layout());
107         try {
108             if (!GLib.Process.spawn_command_line_sync(cmdline)) {
109                 warning("Switch xkb layout to %s failed.",
110                     engine.get_layout());
111             }
112         } catch (GLib.SpawnError e) {
113             warning("execute setxkblayout failed");
114         }
115
116         string[] names = {};
117         foreach(var desc in m_engines) {
118             names += desc.get_name();
119         }
120         m_config.set_value("general",
121                            "engines_order",
122                            new GLib.Variant.strv(names));
123     }
124
125     private void handle_config_value_changed(IBus.Config config,
126                                              string section,
127                                              string name,
128                                              Variant variant) {
129         if (section == "general" && name == "preload_engines") {
130             update_engines(variant, null);
131         }
132     }
133
134     private void handle_engine_switch(Gdk.Event event, bool revert) {
135         // Do not need switch IME
136         if (m_engines.length <= 1)
137             return;
138
139         uint primary_modifiers =
140             KeybindingManager.get_primary_modifier(event.key.state);
141
142         bool pressed = KeybindingManager.primary_modifier_still_pressed(
143                 event, primary_modifiers);
144         if (pressed) {
145             int i = revert ? m_engines.length - 1 : 1;
146             i = m_switcher.run(event, m_engines, i);
147             if (i < 0) {
148                 debug("switch cancelled");
149             } else {
150                 assert(i < m_engines.length);
151                 switch_engine(i);
152             }
153         } else {
154             int i = revert ? m_engines.length - 1 : 1;
155             switch_engine(i);
156         }
157     }
158
159     private void update_engines(GLib.Variant? var_engines,
160                                 GLib.Variant? var_order) {
161         string[] engine_names = null;
162
163         if (var_engines != null)
164             engine_names = var_engines.dup_strv();
165         if (engine_names == null || engine_names.length == 0)
166             engine_names = {"xkb:layout:us"};
167
168         string[] order_names =
169             (var_order != null) ? var_order.dup_strv() : null;
170
171         string[] names = {};
172
173         foreach (var name in order_names) {
174             if (name in engine_names)
175                 names += name;
176         }
177
178         foreach (var name in engine_names) {
179             if (name in names)
180                 continue;
181             names += name;
182         }
183
184         var engines = m_bus.get_engines_by_names(names);
185
186         if (m_engines.length == 0) {
187             m_engines = engines;
188             switch_engine(0, true);
189         } else {
190             var current_engine = m_engines[0];
191             m_engines = engines;
192             int i;
193             for (i = 0; i < m_engines.length; i++) {
194                 if (current_engine.get_name() == engines[i].get_name()) {
195                     switch_engine(i);
196                     return;
197                 }
198             }
199             switch_engine(0, true);
200         }
201     }
202
203     private void status_icon_popup_menu(Gtk.StatusIcon status_icon,
204                                         uint button,
205                                         uint activate_time) {
206         Gtk.Menu menu = m_property_manager.get_menu();
207         if (menu == null)
208             return;
209
210         menu.show_all();
211         menu.set_take_focus(false);
212
213         menu.popup(null,
214                    null,
215                    m_status_icon.position_menu,
216                    0,
217                    Gtk.get_current_event_time());
218     }
219
220     private void status_icon_activate(Gtk.StatusIcon status_icon) {
221         int width, height;
222         Gtk.icon_size_lookup(Gtk.IconSize.MENU, out width, out height);
223         if (m_ime_menu == null) {
224             m_ime_menu = new Gtk.Menu();
225             foreach (var engine in m_engines) {
226                 var lang =  engine.get_language();
227                 var name = engine.get_name();
228                 var item = new Gtk.ImageMenuItem.with_label(lang + " - " + name);
229                 if (engine.get_icon() != "") {
230                     var icon = new IconWidget(engine.get_icon(), width);
231                      item.set_image(icon);
232                 }
233                 // Make a copy of engine to workaround a bug in vala.
234                 // https://bugzilla.gnome.org/show_bug.cgi?id=628336
235                 var e = engine;
236                 item.activate.connect((item) => {
237                     for (int i = 0; i < m_engines.length; i++) {
238                         if (e == m_engines[i]) {
239                             switch_engine(i);
240                             break;
241                         }
242                     }
243                 });
244                 m_ime_menu.add(item);
245             }
246             m_ime_menu.show_all();
247             m_ime_menu.set_take_focus(false);
248         }
249         m_ime_menu.popup(null,
250                          null,
251                          m_status_icon.position_menu,
252                          0,
253                          Gtk.get_current_event_time());
254     }
255
256     /* override virtual functions */
257     public override void set_cursor_location(int x, int y,
258                                              int width, int height) {
259         m_candidate_panel.set_cursor_location(x, y, width, height);
260     }
261
262     public override void focus_in(string input_context_path) {
263         try {
264             GLib.Cancellable cancellable = null;
265             m_input_context =
266                 new IBus.InputContext(input_context_path,
267                                       m_bus.get_connection(),
268                                       cancellable);
269             m_input_context.own = false;
270         } catch (GLib.Error e) {
271             debug("error");
272         }
273     }
274
275     public override void focus_out(string input_context_path) {
276         m_input_context = null;
277     }
278
279     public override void register_properties(IBus.PropList props) {
280         m_property_manager.set_properties(props);
281     }
282
283     public override void update_property(IBus.Property prop) {
284         m_property_manager.update_property(prop);
285     }
286
287     public override void update_preedit_text(IBus.Text text,
288                                              uint cursor_pos,
289                                              bool visible) {
290         if (visible)
291             m_candidate_panel.set_preedit_text(text, cursor_pos);
292         else
293             m_candidate_panel.set_preedit_text(null, 0);
294     }
295
296     public override void hide_preedit_text() {
297         m_candidate_panel.set_preedit_text(null, 0);
298     }
299
300     public override void update_auxiliary_text(IBus.Text text,
301                                                bool visible) {
302         m_candidate_panel.set_auxiliary_text(visible ? text : null);
303     }
304
305     public override void hide_auxiliary_text() {
306         m_candidate_panel.set_auxiliary_text(null);
307     }
308
309     public override void update_lookup_table(IBus.LookupTable table,
310                                              bool visible) {
311         m_candidate_panel.set_lookup_table(visible ? table : null);
312     }
313
314     public override void hide_lookup_table() {
315         m_candidate_panel.set_lookup_table(null);
316     }
317
318     public override void state_changed() {
319         var icon_name = "ibus-keyboard";
320
321         var engine = m_bus.get_global_engine();
322         if (engine != null)
323             icon_name = engine.get_icon();
324
325         if (icon_name[0] == '/')
326             m_status_icon.set_from_file(icon_name);
327         else
328             m_status_icon.set_from_icon_name(icon_name);
329     }
330 }