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
23 class Switcher : Gtk.Window {
24 public extern const bool USE_SYMBOL_ICON;
25 private const int DEFAULT_FONT_SIZE = 16;
26 private const int DESC_LABEL_MAX_LEN = 20;
27 private const int ICON_SIZE = 48;
29 private class IBusEngineButton : Gtk.Button {
30 public IBusEngineButton(IBus.EngineDesc engine) {
33 this.longname = engine.get_longname();
35 Gtk.Alignment align = new Gtk.Alignment(0.5f, 0.5f, 0.0f, 0.0f);
38 if (!USE_SYMBOL_ICON) {
39 IconWidget icon = new IconWidget(engine.get_icon(), ICON_SIZE);
42 var language = engine.get_language();
43 var symbol = engine.get_symbol();
50 if (symbol.length != 0) {
54 Gtk.Label label = new Gtk.Label(id);
55 string id_font = "%d".printf(DEFAULT_FONT_SIZE);
56 string markup = "<span font=\"%s\">%s</span>".printf(id_font, id);
58 label.set_markup(markup);
63 public string longname { get; set; }
66 private Gtk.Box m_box;
67 private Gtk.Label m_label;
68 private IBusEngineButton[] m_buttons = {};
69 private IBus.EngineDesc[] m_engines;
70 private uint m_selected_engine;
71 private uint m_keyval;
72 private uint m_modifiers;
73 private Gdk.ModifierType m_primary_modifier;
74 private GLib.MainLoop m_loop;
76 private uint m_popup_delay_time = 400;
77 private uint m_popup_delay_time_id;
83 type : Gtk.WindowType.POPUP,
84 events : Gdk.EventMask.KEY_PRESS_MASK | Gdk.EventMask.KEY_RELEASE_MASK,
85 window_position : Gtk.WindowPosition.CENTER,
91 Gtk.Box vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
93 Gtk.Alignment align = new Gtk.Alignment(0.5f, 0.5f, 0.0f, 0.0f);
94 vbox.pack_start(align, true, true, 0);
95 m_box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
97 m_label = new Gtk.Label("");
99 /* Set the accessible role of the label to a status bar so it
100 * will emit name changed events that can be used by screen
103 Atk.Object obj = m_label.get_accessible();
104 obj.set_role (Atk.Role.STATUSBAR);
106 m_label.set_padding(3, 3);
107 vbox.pack_end(m_label, false, false, 0);
112 public int run(uint keyval,
115 IBus.EngineDesc[] engines,
117 assert (m_loop == null);
118 assert (index < engines.length);
123 KeybindingManager.get_primary_modifier(
124 state & KeybindingManager.MODIFIER_FILTER);
126 update_engines(engines);
127 /* Let gtk recalculate the window size. */
130 m_selected_engine = index;
131 m_label.set_text(m_buttons[index].longname);
132 m_buttons[index].grab_focus();
134 Gdk.Device device = event.get_device();
135 if (device == null) {
136 var display = get_display();
137 var device_manager = display.get_device_manager();
138 /* The macro VALA_X_Y supports even numbers.
139 * http://git.gnome.org/browse/vala/commit/?id=294b374af6
142 device = device_manager.list_devices(Gdk.DeviceType.MASTER).data;
144 unowned GLib.List<Gdk.Device> devices =
145 device_manager.list_devices(Gdk.DeviceType.MASTER);
146 device = devices.data;
152 if (device.get_source() == Gdk.InputSource.KEYBOARD) {
154 pointer = device.get_associated_device();
157 keyboard = device.get_associated_device();
160 get_position(out m_root_x, out m_root_y);
161 // Pull the window from the screen so that the window gets
162 // the key press and release events but mouse does not select
163 // an IME unexpectedly.
167 // Restore the window position after m_popup_delay_time
168 m_popup_delay_time_id = GLib.Timeout.add(m_popup_delay_time,
170 restore_window_position("timeout");
174 Gdk.GrabStatus status;
175 // Grab all keyboard events
176 status = keyboard.grab(get_window(),
177 Gdk.GrabOwnership.NONE,
179 Gdk.EventMask.KEY_PRESS_MASK |
180 Gdk.EventMask.KEY_RELEASE_MASK,
183 if (status != Gdk.GrabStatus.SUCCESS)
184 warning("Grab keyboard failed! status = %d", status);
185 // Grab all pointer events
186 status = pointer.grab(get_window(),
187 Gdk.GrabOwnership.NONE,
189 Gdk.EventMask.BUTTON_PRESS_MASK |
190 Gdk.EventMask.BUTTON_RELEASE_MASK,
193 if (status != Gdk.GrabStatus.SUCCESS)
194 warning("Grab pointer failed! status = %d", status);
197 m_loop = new GLib.MainLoop();
201 keyboard.ungrab(Gdk.CURRENT_TIME);
202 pointer.ungrab(Gdk.CURRENT_TIME);
205 // Make sure the switcher is hidden before returning from this function.
206 while (Gtk.events_pending())
207 Gtk.main_iteration ();
212 /* Based on metacity/src/ui/tabpopup.c:meta_ui_tab_popup_new */
213 private void update_engines(IBus.EngineDesc[] engines) {
214 foreach (var button in m_buttons) {
219 if (engines == null) {
225 int max_label_width = 0;
227 for (int i = 0; i < m_engines.length; i++) {
229 var engine = m_engines[i];
230 var button = new IBusEngineButton(engine);
231 var longname = engine.get_longname();
232 button.set_relief(Gtk.ReliefStyle.NONE);
235 button.enter_notify_event.connect((e) => {
237 m_selected_engine = index;
241 button.button_press_event.connect((e) => {
242 m_selected_engine = index;
243 m_result = (int)m_selected_engine;
248 if (longname.length > DESC_LABEL_MAX_LEN) {
249 longname = longname[0:DESC_LABEL_MAX_LEN];
252 button.longname = longname;
253 m_label.set_label(longname);
256 m_label.get_preferred_width(null, out width);
257 max_label_width = int.max(max_label_width, width);
259 m_box.pack_start(button, true, true);
263 m_label.set_text(m_buttons[0].longname);
264 m_label.set_ellipsize(Pango.EllipsizeMode.END);
266 Gdk.Display display = Gdk.Display.get_default();
267 Gdk.Screen screen = (display != null) ?
268 display.get_default_screen() : null;
269 int screen_width = 0;
271 if (screen != null) {
272 screen_width = screen.get_width();
275 if (screen_width > 0 && max_label_width > (screen_width / 4)) {
276 max_label_width = screen_width / 4;
279 /* add random padding */
280 max_label_width += 20;
281 set_default_size(max_label_width, -1);
284 private void next_engine() {
285 if (m_selected_engine == m_engines.length - 1)
286 m_selected_engine = 0;
288 m_selected_engine ++;
289 m_label.set_text(m_buttons[m_selected_engine].longname);
290 set_focus(m_buttons[m_selected_engine]);
293 private void previous_engine() {
294 if (m_selected_engine == 0)
295 m_selected_engine = m_engines.length - 1;
297 m_selected_engine --;
298 m_label.set_text(m_buttons[m_selected_engine].longname);
299 set_focus(m_buttons[m_selected_engine]);
302 private void restore_window_position(string debug_str) {
303 debug("restore_window_position %s: (%ld, %ld)\n",
304 debug_str, m_root_x, m_root_y);
306 if (m_popup_delay_time_id == 0) {
310 GLib.Source.remove(m_popup_delay_time_id);
311 m_popup_delay_time_id = 0;
312 move(m_root_x, m_root_y);
315 /* override virtual functions */
316 public override void show() {
318 set_focus_visible(true);
321 public override bool key_press_event(Gdk.EventKey e) {
323 Gdk.EventKey *pe = &e;
325 restore_window_position("pressed");
328 uint modifiers = KeybindingManager.MODIFIER_FILTER & pe->state;
330 if ((modifiers != m_modifiers) &&
331 (modifiers != (m_modifiers | Gdk.ModifierType.SHIFT_MASK))) {
335 if (pe->keyval == m_keyval) {
336 if (modifiers == m_modifiers)
338 else // modififers == m_modifiers | SHIFT_MASK
343 switch (pe->keyval) {
344 case 0x08fb: /* leftarrow */
345 case 0xff51: /* Left */
348 case 0x08fc: /* uparrow */
349 case 0xff52: /* Up */
351 case 0x08fd: /* rightarrow */
352 case 0xff53: /* Right */
355 case 0x08fe: /* downarrow */
356 case 0xff54: /* Down */
359 debug("0x%04x", pe->keyval);
366 public override bool key_release_event(Gdk.EventKey e) {
367 Gdk.EventKey *pe = &e;
369 if (KeybindingManager.primary_modifier_still_pressed((Gdk.Event *)pe,
370 m_primary_modifier)) {
374 // if e.type == Gdk.EventType.KEY_RELEASE, m_loop is already null.
375 if (m_loop == null) {
379 if (m_popup_delay_time_id != 0) {
380 GLib.Source.remove(m_popup_delay_time_id);
381 m_popup_delay_time_id = 0;
385 m_result = (int)m_selected_engine;
389 public void set_popup_delay_time(uint popup_delay_time) {
390 m_popup_delay_time = popup_delay_time;