Add some milliseconds interval before IME switcher is shown.
[platform/upstream/ibus.git] / ui / gtk3 / switcher.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 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;
28
29     private class IBusEngineButton : Gtk.Button {
30         public IBusEngineButton(IBus.EngineDesc engine) {
31             GLib.Object();
32
33             this.longname = engine.get_longname();
34
35             Gtk.Alignment align = new Gtk.Alignment(0.5f, 0.5f, 0.0f, 0.0f);
36             add(align);
37
38             if (!USE_SYMBOL_ICON) {
39                 IconWidget icon = new IconWidget(engine.get_icon(), ICON_SIZE);
40                 align.add(icon);
41             } else {
42                 var language = engine.get_language();
43                 var symbol = engine.get_symbol();
44                 var id = language;
45
46                 if (id.length > 2) {
47                     id = id[0:2];
48                 }
49
50                 if (symbol.length != 0) {
51                     id = symbol;
52                 }
53
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);
57
58                 label.set_markup(markup);
59                 align.add(label);
60             }
61         }
62
63         public string longname { get; set; }
64     }
65
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;
75     private int m_result;
76     private uint m_popup_delay_time = 400;
77     private uint m_popup_delay_time_id;
78     private int m_root_x;
79     private int m_root_y;
80
81     public Switcher() {
82         GLib.Object(
83             type : Gtk.WindowType.POPUP,
84             events : Gdk.EventMask.KEY_PRESS_MASK | Gdk.EventMask.KEY_RELEASE_MASK,
85             window_position : Gtk.WindowPosition.CENTER,
86             accept_focus : true,
87             decorated : false,
88             modal : true,
89             focus_visible : true
90         );
91         Gtk.Box vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
92         add(vbox);
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);
96         align.add(m_box);
97         m_label = new Gtk.Label("");
98
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
101          * readers.
102          */
103         Atk.Object obj = m_label.get_accessible();
104         obj.set_role (Atk.Role.STATUSBAR);
105
106         m_label.set_padding(3, 3);
107         vbox.pack_end(m_label, false, false, 0);
108
109         grab_focus();
110     }
111
112     public int run(uint keyval,
113                    uint state,
114                    Gdk.Event event,
115                    IBus.EngineDesc[] engines,
116                    int index) {
117         assert (m_loop == null);
118         assert (index < engines.length);
119
120         m_keyval = keyval;
121         m_modifiers = state;
122         m_primary_modifier =
123             KeybindingManager.get_primary_modifier(
124                 state & KeybindingManager.MODIFIER_FILTER);
125
126         update_engines(engines);
127         /* Let gtk recalculate the window size. */
128         resize(1, 1);
129
130         m_selected_engine = index;
131         m_label.set_text(m_buttons[index].longname);
132         m_buttons[index].grab_focus();
133
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
140  */
141 #if VALA_0_16
142             device = device_manager.list_devices(Gdk.DeviceType.MASTER).data;
143 #else
144             unowned GLib.List<Gdk.Device> devices =
145                     device_manager.list_devices(Gdk.DeviceType.MASTER);
146             device = devices.data;
147 #endif
148         }
149
150         Gdk.Device keyboard;
151         Gdk.Device pointer;
152         if (device.get_source() == Gdk.InputSource.KEYBOARD) {
153             keyboard = device;
154             pointer = device.get_associated_device();
155         } else {
156             pointer = device;
157             keyboard = device.get_associated_device();
158         }
159
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.
164         move(-1000, -1000);
165         show_all();
166
167         // Restore the window position after m_popup_delay_time
168         m_popup_delay_time_id = GLib.Timeout.add(m_popup_delay_time,
169                                                  () => {
170             restore_window_position("timeout");
171             return false;
172         });
173
174         Gdk.GrabStatus status;
175         // Grab all keyboard events
176         status = keyboard.grab(get_window(),
177                                Gdk.GrabOwnership.NONE,
178                                true,
179                                Gdk.EventMask.KEY_PRESS_MASK |
180                                Gdk.EventMask.KEY_RELEASE_MASK,
181                                null,
182                                Gdk.CURRENT_TIME);
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,
188                               true,
189                               Gdk.EventMask.BUTTON_PRESS_MASK |
190                               Gdk.EventMask.BUTTON_RELEASE_MASK,
191                               null,
192                               Gdk.CURRENT_TIME);
193         if (status != Gdk.GrabStatus.SUCCESS)
194             warning("Grab pointer failed! status = %d", status);
195
196
197         m_loop = new GLib.MainLoop();
198         m_loop.run();
199         m_loop = null;
200
201         keyboard.ungrab(Gdk.CURRENT_TIME);
202         pointer.ungrab(Gdk.CURRENT_TIME);
203
204         hide();
205         // Make sure the switcher is hidden before returning from this function.
206         while (Gtk.events_pending())
207             Gtk.main_iteration ();
208
209         return m_result;
210     }
211
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) {
215             button.destroy();
216         }
217         m_buttons = {};
218
219         if (engines == null) {
220             m_engines = {};
221             return;
222         }
223
224         m_engines = engines;
225         int max_label_width = 0;
226
227         for (int i = 0; i < m_engines.length; i++) {
228             var index = 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);
233             button.show();
234
235             button.enter_notify_event.connect((e) => {
236                 button.grab_focus();
237                 m_selected_engine = index;
238                 return true;
239             });
240
241             button.button_press_event.connect((e) => {
242                 m_selected_engine = index;
243                 m_result = (int)m_selected_engine;
244                 m_loop.quit();
245                 return true;
246             });
247
248             if (longname.length > DESC_LABEL_MAX_LEN) {
249                 longname = longname[0:DESC_LABEL_MAX_LEN];
250             }
251
252             button.longname = longname;
253             m_label.set_label(longname);
254
255             int width;
256             m_label.get_preferred_width(null, out width);
257             max_label_width = int.max(max_label_width, width);
258
259             m_box.pack_start(button, true, true);
260             m_buttons += button;
261         }
262
263         m_label.set_text(m_buttons[0].longname);
264         m_label.set_ellipsize(Pango.EllipsizeMode.END);
265
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;
270
271         if (screen != null) {
272             screen_width = screen.get_width();
273         }
274
275         if (screen_width > 0 && max_label_width > (screen_width / 4)) {
276             max_label_width = screen_width / 4;
277         }
278
279         /* add random padding */
280         max_label_width += 20;
281         set_default_size(max_label_width, -1);
282     }
283
284     private void next_engine() {
285         if (m_selected_engine == m_engines.length - 1)
286             m_selected_engine = 0;
287         else
288             m_selected_engine ++;
289         m_label.set_text(m_buttons[m_selected_engine].longname);
290         set_focus(m_buttons[m_selected_engine]);
291     }
292
293     private void previous_engine() {
294         if (m_selected_engine == 0)
295             m_selected_engine = m_engines.length - 1;
296         else
297             m_selected_engine --;
298         m_label.set_text(m_buttons[m_selected_engine].longname);
299         set_focus(m_buttons[m_selected_engine]);
300     }
301
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);
305
306         if (m_popup_delay_time_id == 0) {
307             return;
308         }
309
310         GLib.Source.remove(m_popup_delay_time_id);
311         m_popup_delay_time_id = 0;
312         move(m_root_x, m_root_y);
313     }
314
315     /* override virtual functions */
316     public override void show() {
317         base.show();
318         set_focus_visible(true);
319     }
320
321     public override bool key_press_event(Gdk.EventKey e) {
322         bool retval = true;
323         Gdk.EventKey *pe = &e;
324
325         restore_window_position("pressed");
326
327         do {
328             uint modifiers = KeybindingManager.MODIFIER_FILTER & pe->state;
329
330             if ((modifiers != m_modifiers) &&
331                 (modifiers != (m_modifiers | Gdk.ModifierType.SHIFT_MASK))) {
332                 break;
333             }
334
335             if (pe->keyval == m_keyval) {
336                 if (modifiers == m_modifiers)
337                     next_engine();
338                 else // modififers == m_modifiers | SHIFT_MASK
339                     previous_engine();
340                 break;
341             }
342
343             switch (pe->keyval) {
344                 case 0x08fb: /* leftarrow */
345                 case 0xff51: /* Left */
346                     previous_engine();
347                     break;
348                 case 0x08fc: /* uparrow */
349                 case 0xff52: /* Up */
350                     break;
351                 case 0x08fd: /* rightarrow */
352                 case 0xff53: /* Right */
353                     next_engine();
354                     break;
355                 case 0x08fe: /* downarrow */
356                 case 0xff54: /* Down */
357                     break;
358                 default:
359                     debug("0x%04x", pe->keyval);
360                     break;
361             }
362         } while (false);
363         return retval;
364     }
365
366     public override bool key_release_event(Gdk.EventKey e) {
367         Gdk.EventKey *pe = &e;
368
369         if (KeybindingManager.primary_modifier_still_pressed((Gdk.Event *)pe,
370             m_primary_modifier)) {
371             return true;
372         }
373
374         // if e.type == Gdk.EventType.KEY_RELEASE, m_loop is already null.
375         if (m_loop == null) {
376             return false;
377         }
378
379         if (m_popup_delay_time_id != 0) {
380             GLib.Source.remove(m_popup_delay_time_id);
381             m_popup_delay_time_id = 0;
382         }
383
384         m_loop.quit();
385         m_result = (int)m_selected_engine;
386         return true;
387     }
388
389     public void set_popup_delay_time(uint popup_delay_time) {
390         m_popup_delay_time = popup_delay_time;
391     }
392 }