1 /* vim:set et sts=4 sw=4:
5 * Copyright(c) 2011 Peng Huang <shawn.p.huang@gmail.com>
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.
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.
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
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;
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");
46 m_config = bus.get_config();
47 assert(m_config != null);
49 m_config.value_changed.connect(handle_config_value_changed);
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");
59 m_candidate_panel = new CandidatePanel();
61 m_candidate_panel.hide();
62 m_candidate_panel.show();
64 update_engines(m_config.get_value("general", "preload_engines"),
65 m_config.get_value("general", "engines_order"));
67 m_switcher = new Switcher();
69 KeybindingManager.get_instance().bind("<Control>space", (e) => {
70 handle_engine_switch(e, false);
73 KeybindingManager.get_instance().bind("<Control><Shift>space", (e) => {
74 handle_engine_switch(e, true);
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);
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);
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];
99 m_engines[0] = engine;
101 if (!m_bus.set_global_engine(engine.get_name())) {
102 warning("Switch engine to %s failed.", engine.get_name());
106 string cmdline = "setxkbmap %s".printf(engine.get_layout());
108 if (!GLib.Process.spawn_command_line_sync(cmdline)) {
109 warning("Switch xkb layout to %s failed.",
110 engine.get_layout());
112 } catch (GLib.SpawnError e) {
113 warning("execute setxkblayout failed");
117 foreach(var desc in m_engines) {
118 names += desc.get_name();
120 m_config.set_value("general",
122 new GLib.Variant.strv(names));
125 private void handle_config_value_changed(IBus.Config config,
129 if (section == "general" && name == "preload_engines") {
130 update_engines(variant, null);
134 private void handle_engine_switch(Gdk.Event event, bool revert) {
135 // Do not need switch IME
136 if (m_engines.length <= 1)
139 uint primary_modifiers =
140 KeybindingManager.get_primary_modifier(event.key.state);
142 bool pressed = KeybindingManager.primary_modifier_still_pressed(
143 event, primary_modifiers);
145 int i = revert ? m_engines.length - 1 : 1;
146 i = m_switcher.run(event, m_engines, i);
148 debug("switch cancelled");
150 assert(i < m_engines.length);
154 int i = revert ? m_engines.length - 1 : 1;
159 private void update_engines(GLib.Variant? var_engines,
160 GLib.Variant? var_order) {
161 string[] engine_names = null;
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"};
168 string[] order_names =
169 (var_order != null) ? var_order.dup_strv() : null;
173 foreach (var name in order_names) {
174 if (name in engine_names)
178 foreach (var name in engine_names) {
184 var engines = m_bus.get_engines_by_names(names);
186 if (m_engines.length == 0) {
188 switch_engine(0, true);
190 var current_engine = m_engines[0];
193 for (i = 0; i < m_engines.length; i++) {
194 if (current_engine.get_name() == engines[i].get_name()) {
199 switch_engine(0, true);
203 private void status_icon_popup_menu(Gtk.StatusIcon status_icon,
205 uint activate_time) {
206 Gtk.Menu menu = m_property_manager.get_menu();
211 menu.set_take_focus(false);
215 m_status_icon.position_menu,
217 Gtk.get_current_event_time());
220 private void status_icon_activate(Gtk.StatusIcon status_icon) {
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);
233 // Make a copy of engine to workaround a bug in vala.
234 // https://bugzilla.gnome.org/show_bug.cgi?id=628336
236 item.activate.connect((item) => {
237 for (int i = 0; i < m_engines.length; i++) {
238 if (e == m_engines[i]) {
244 m_ime_menu.add(item);
246 m_ime_menu.show_all();
247 m_ime_menu.set_take_focus(false);
249 m_ime_menu.popup(null,
251 m_status_icon.position_menu,
253 Gtk.get_current_event_time());
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);
262 public override void focus_in(string input_context_path) {
264 GLib.Cancellable cancellable = null;
266 new IBus.InputContext(input_context_path,
267 m_bus.get_connection(),
269 m_input_context.own = false;
270 } catch (GLib.Error e) {
275 public override void focus_out(string input_context_path) {
276 m_input_context = null;
279 public override void register_properties(IBus.PropList props) {
280 m_property_manager.set_properties(props);
283 public override void update_property(IBus.Property prop) {
284 m_property_manager.update_property(prop);
287 public override void update_preedit_text(IBus.Text text,
291 m_candidate_panel.set_preedit_text(text, cursor_pos);
293 m_candidate_panel.set_preedit_text(null, 0);
296 public override void hide_preedit_text() {
297 m_candidate_panel.set_preedit_text(null, 0);
300 public override void update_auxiliary_text(IBus.Text text,
302 m_candidate_panel.set_auxiliary_text(visible ? text : null);
305 public override void hide_auxiliary_text() {
306 m_candidate_panel.set_auxiliary_text(null);
309 public override void update_lookup_table(IBus.LookupTable table,
311 m_candidate_panel.set_lookup_table(visible ? table : null);
314 public override void hide_lookup_table() {
315 m_candidate_panel.set_lookup_table(null);
318 public override void state_changed() {
319 var icon_name = "ibus-keyboard";
321 var engine = m_bus.get_global_engine();
323 icon_name = engine.get_icon();
325 if (icon_name[0] == '/')
326 m_status_icon.set_from_file(icon_name);
328 m_status_icon.set_from_icon_name(icon_name);