wip
[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 engine = m_engines[i];
90         for (int j = i; j > 0; j--) {
91             m_engines[j] = m_engines[j - 1];
92         }
93         m_engines[0] = engine;
94
95         if (!m_bus.set_global_engine(engine.get_name())) {
96             warning("Switch engine to %s failed.", engine.get_name());
97             return;
98         }
99         // set xkb layout
100         string cmdline = "setxkbmap %s".printf(engine.get_layout());
101         try {
102             if (!GLib.Process.spawn_command_line_sync(cmdline)) {
103                 warning("Switch xkb layout to %s failed.",
104                     engine.get_layout());
105             }
106         } catch (GLib.SpawnError e) {
107             warning("execute setxkblayout failed");
108         }
109     }
110
111     private void handle_engine_switch(Gdk.Event event, bool revert) {
112         if (!KeybindingManager.primary_modifier_still_pressed(event)) {
113             int i = revert ? m_engines.length - 1 : 1;
114             switch_engine(i);
115         } else {
116             int i = revert ? m_engines.length - 1 : 1;
117             i = m_switcher.run(event, m_engines, i);
118             if (i < 0) {
119                 debug("switch cancelled");
120             } else {
121                 assert(i < m_engines.length);
122                 switch_engine(i);
123             }
124         }
125     }
126
127     private void update_engines() {
128         Variant variant = m_config.get_value("general", "preload_engines");
129         string[] engine_names;
130
131         if (variant != null)
132             engine_names = variant.dup_strv();
133         else
134             engine_names = {"xkb:layout:us", "pinyin", "anthy"};
135
136         m_engines = m_bus.get_engines_by_names(engine_names);
137     }
138
139     private void status_icon_popup_menu(Gtk.StatusIcon status_icon,
140                                         uint button,
141                                         uint activate_time) {
142         Gtk.Menu menu = m_property_manager.get_menu();
143         if (menu == null)
144             return;
145
146         menu.show_all();
147         menu.set_take_focus(false);
148
149         menu.popup(null,
150                    null,
151                    m_status_icon.position_menu,
152                    0,
153                    Gtk.get_current_event_time());
154     }
155
156     private void status_icon_activate(Gtk.StatusIcon status_icon) {
157         int width, height;
158         Gtk.icon_size_lookup(Gtk.IconSize.MENU, out width, out height);
159         if (m_ime_menu == null) {
160             m_ime_menu = new Gtk.Menu();
161             foreach (var engine in m_engines) {
162                 var lang =  engine.get_language();
163                 var name = engine.get_name();
164                 var item = new Gtk.ImageMenuItem.with_label(lang + " - " + name);
165                 if (engine.get_icon() != "") {
166                     var icon = new IconWidget(engine.get_icon(), width);
167                      item.set_image(icon);
168                 }
169                 // Make a copy of engine to workaround a bug in vala.
170                 // https://bugzilla.gnome.org/show_bug.cgi?id=628336
171                 var e = engine;
172                 item.activate.connect((item) => {
173                     for (int i = 0; i < m_engines.length; i++) {
174                         if (e == m_engines[i]) {
175                             switch_engine(i);
176                             break;
177                         }
178                     }
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 }