Add xkb layouts switch support and add three demo xkb layouts.
[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
48         // init ui
49         m_status_icon = new Gtk.StatusIcon();
50         m_status_icon.set_name("ibus-ui-gtk");
51         m_status_icon.set_title("IBus Panel");
52         m_status_icon.popup_menu.connect(status_icon_popup_menu);
53         m_status_icon.activate.connect(status_icon_activate);
54         m_status_icon.set_from_icon_name("ibus-keyboard");
55
56         m_candidate_panel = new CandidatePanel();
57
58         m_candidate_panel.hide();
59         m_candidate_panel.show();
60
61         update_engines();
62
63         m_switcher = new Switcher();
64
65         KeybindingManager.get_instance().bind("<Control>space", (e) => {
66             handle_engine_switch(e, false);
67         });
68
69         KeybindingManager.get_instance().bind("<Control><Shift>space", (e) => {
70             handle_engine_switch(e, true);
71         });
72
73         m_property_manager = new PropertyManager();
74         m_property_manager.property_activate.connect((k, s) => {
75             if (m_input_context != null)
76                 m_input_context.property_activate(k, s);
77         });
78     }
79
80     private void switch_engine(int i) {
81         //  debug("switch_engine i = %d", i);
82         assert(i >= 0 && i < m_engines.length);
83
84         // Do not need siwtch
85         if (i == 0)
86             return;
87
88         // Move the target engine to the first place.
89         IBus.EngineDesc tmp = m_engines[i];
90         for (int j = i; j > 0; j--) {
91             m_engines[j] = m_engines[j - 1];
92         }
93         m_engines[0] = tmp;
94
95         switch_engine_by_desc(m_engines[0]);
96     }
97
98     private void handle_engine_switch(Gdk.Event event, bool revert) {
99         if (!KeybindingManager.primary_modifier_still_pressed(event)) {
100             int i = revert ? m_engines.length - 1 : 1;
101             switch_engine(i);
102         } else {
103             int i = revert ? m_engines.length - 1 : 1;
104             i = m_switcher.run(event, m_engines, i);
105             if (i < 0) {
106                 debug("switch cancelled");
107             } else {
108                 assert(i < m_engines.length);
109                 switch_engine(i);
110             }
111         }
112     }
113
114     private void update_engines() {
115         Variant variant = m_config.get_value("general", "preload_engines");
116         string[] engine_names;
117
118         if (variant != null)
119             engine_names = variant.dup_strv();
120         else
121             engine_names = {"xkb:layout:us", "pinyin", "anthy"};
122
123         m_engines = m_bus.get_engines_by_names(engine_names);
124         m_ime_menu = null;
125     }
126
127     private void status_icon_popup_menu(Gtk.StatusIcon status_icon,
128                                         uint button,
129                                         uint activate_time) {
130         Gtk.Menu menu = m_property_manager.get_menu();
131         if (menu == null)
132             return;
133
134         menu.show_all();
135         menu.set_take_focus(false);
136
137         menu.popup(null,
138                    null,
139                    m_status_icon.position_menu,
140                    0,
141                    Gtk.get_current_event_time());
142     }
143
144     private void switch_engine_by_desc(IBus.EngineDesc engine) {
145         if (!m_bus.set_global_engine(engine.get_name())) {
146             warning("Switch engine to %s failed.", engine.get_name());
147             return;
148         }
149         // set xkb layout
150         string cmdline = "setxkbmap %s".printf(engine.get_layout());
151         try {
152             if (!GLib.Process.spawn_command_line_sync(cmdline)) {
153                 warning("Switch xkb layout to %s failed.",
154                     engine.get_layout());
155             }
156         } catch (GLib.SpawnError e) {
157             warning("execute setxkblayout failed");
158         }
159     }
160
161     private void status_icon_activate(Gtk.StatusIcon status_icon) {
162         if (m_ime_menu == null) {
163             int width, height;
164             Gtk.icon_size_lookup(Gtk.IconSize.MENU, out width, out height);
165             m_ime_menu  = new Gtk.Menu();
166             foreach (var engine in m_engines) {
167                 var lang =  engine.get_language();
168                 var name = engine.get_name();
169                 var item = new Gtk.ImageMenuItem.with_label(lang + " - " + name);
170                 if (engine.get_icon() != "") {
171                     var icon = new IconWidget(engine.get_icon(), width);
172                      item.set_image(icon);
173                 }
174                 // Make a copy of engine to workaround a bug in vala.
175                 // https://bugzilla.gnome.org/show_bug.cgi?id=628336
176                 var e = engine;
177                 item.activate.connect((i) => {
178                     switch_engine_by_desc(e);
179                 });
180                 m_ime_menu.add(item);
181             }
182             m_ime_menu.show_all();
183             m_ime_menu.set_take_focus(false);
184         }
185         m_ime_menu.popup(null,
186                          null,
187                          m_status_icon.position_menu,
188                          0,
189                          Gtk.get_current_event_time());
190     }
191
192     /* override virtual functions */
193     public override void set_cursor_location(int x, int y,
194                                              int width, int height) {
195         m_candidate_panel.set_cursor_location(x, y, width, height);
196     }
197
198     public override void focus_in(string input_context_path) {
199         try {
200             GLib.Cancellable cancellable = null;
201             m_input_context =
202                 new IBus.InputContext(input_context_path,
203                                       m_bus.get_connection(),
204                                       cancellable);
205         } catch (GLib.Error e) {
206             debug("error");
207         }
208     }
209
210     public override void focus_out(string input_context_path) {
211         m_input_context = null;
212     }
213
214     public override void register_properties(IBus.PropList props) {
215         m_property_manager.set_properties(props);
216     }
217
218     public override void update_property(IBus.Property prop) {
219         m_property_manager.update_property(prop);
220     }
221
222     public override void update_preedit_text(IBus.Text text,
223                                              uint cursor_pos,
224                                              bool visible) {
225         if (visible)
226             m_candidate_panel.set_preedit_text(text, cursor_pos);
227         else
228             m_candidate_panel.set_preedit_text(null, 0);
229     }
230
231     public override void hide_preedit_text() {
232         m_candidate_panel.set_preedit_text(null, 0);
233     }
234
235     public override void update_auxiliary_text(IBus.Text text,
236                                                bool visible) {
237         m_candidate_panel.set_auxiliary_text(visible ? text : null);
238     }
239
240     public override void hide_auxiliary_text() {
241         m_candidate_panel.set_auxiliary_text(null);
242     }
243
244     public override void update_lookup_table(IBus.LookupTable table,
245                                              bool visible) {
246         m_candidate_panel.set_lookup_table(visible ? table : null);
247     }
248
249     public override void hide_lookup_table() {
250         m_candidate_panel.set_lookup_table(null);
251     }
252 }