Upstream version 7.35.139.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / libgtk2ui / x11_input_method_context_impl_gtk2.cc
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/ui/libgtk2ui/x11_input_method_context_impl_gtk2.h"
6
7 #include <gdk/gdk.h>
8 #include <gdk/gdkkeysyms.h>
9 #include <gdk/gdkx.h>
10
11 #include <gtk/gtk.h>
12
13 #include <X11/X.h>
14 #include <X11/Xlib.h>
15
16 #include "base/event_types.h"
17 #include "base/message_loop/message_loop.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "ui/base/ime/composition_text.h"
20 #include "ui/base/ime/composition_text_util_pango.h"
21 #include "ui/base/ime/text_input_client.h"
22 #include "ui/events/event.h"
23
24 namespace {
25
26 // Constructs a GdkEventKey from a XKeyEvent and returns it.  Otherwise,
27 // returns NULL.  The returned GdkEvent must be freed by gdk_event_free.
28 GdkEvent* GdkEventFromXKeyEvent(XKeyEvent& xkey, bool is_modifier) {
29   DCHECK(xkey.type == KeyPress || xkey.type == KeyRelease);
30
31   // Get a GdkDisplay.
32   GdkDisplay* display = gdk_x11_lookup_xdisplay(xkey.display);
33   if (!display) {
34     // Fall back to the default display.
35     display = gdk_display_get_default();
36   }
37   if (!display) {
38     LOG(ERROR) << "Cannot get a GdkDisplay for a key event.";
39     return NULL;
40   }
41   // Get a keysym and group.
42   KeySym keysym = NoSymbol;
43   guint8 keyboard_group = 0;
44   XLookupString(&xkey, NULL, 0, &keysym, NULL);
45   GdkKeymap* keymap = gdk_keymap_get_for_display(display);
46   GdkKeymapKey* keys = NULL;
47   guint* keyvals = NULL;
48   gint n_entries = 0;
49   if (keymap &&
50       gdk_keymap_get_entries_for_keycode(keymap, xkey.keycode,
51                                          &keys, &keyvals, &n_entries)) {
52     for (gint i = 0; i < n_entries; ++i) {
53       if (keyvals[i] == keysym) {
54         keyboard_group = keys[i].group;
55         break;
56       }
57     }
58   }
59   g_free(keys);
60   keys = NULL;
61   g_free(keyvals);
62   keyvals = NULL;
63   // Get a GdkWindow.
64   GdkWindow* window = gdk_x11_window_lookup_for_display(display, xkey.window);
65   if (window)
66     g_object_ref(window);
67   else
68     window = gdk_x11_window_foreign_new_for_display(display, xkey.window);
69   if (!window) {
70     LOG(ERROR) << "Cannot get a GdkWindow for a key event.";
71     return NULL;
72   }
73
74   // Create a GdkEvent.
75   GdkEventType event_type = xkey.type == KeyPress ?
76                             GDK_KEY_PRESS : GDK_KEY_RELEASE;
77   GdkEvent* event = gdk_event_new(event_type);
78   event->key.type = event_type;
79   event->key.window = window;
80   // GdkEventKey and XKeyEvent share the same definition for time and state.
81   event->key.send_event = xkey.send_event;
82   event->key.time = xkey.time;
83   event->key.state = xkey.state;
84   event->key.keyval = keysym;
85   event->key.length = 0;
86   event->key.string = NULL;
87   event->key.hardware_keycode = xkey.keycode;
88   event->key.group = keyboard_group;
89   event->key.is_modifier = is_modifier;
90   return event;
91 }
92
93 }  // namespace
94
95 namespace libgtk2ui {
96
97 X11InputMethodContextImplGtk2::X11InputMethodContextImplGtk2(
98     ui::LinuxInputMethodContextDelegate* delegate)
99     : delegate_(delegate),
100       gtk_context_simple_(NULL),
101       gtk_multicontext_(NULL),
102       gtk_context_(NULL),
103       gdk_last_set_client_window_(NULL) {
104   CHECK(delegate_);
105
106   {
107     XModifierKeymap* keymap = XGetModifierMapping(
108         base::MessagePumpForUI::GetDefaultXDisplay());
109     for (int i = 0; i < 8 * keymap->max_keypermod; ++i) {
110       if (keymap->modifiermap[i])
111         modifier_keycodes_.insert(keymap->modifiermap[i]);
112     }
113     XFreeModifiermap(keymap);
114   }
115
116   gtk_context_simple_ = gtk_im_context_simple_new();
117   gtk_multicontext_ = gtk_im_multicontext_new();
118
119   GtkIMContext* contexts[] = {gtk_context_simple_, gtk_multicontext_};
120   for (size_t i = 0; i < arraysize(contexts); ++i) {
121     g_signal_connect(contexts[i], "commit",
122                      G_CALLBACK(OnCommitThunk), this);
123     g_signal_connect(contexts[i], "preedit-changed",
124                      G_CALLBACK(OnPreeditChangedThunk), this);
125     g_signal_connect(contexts[i], "preedit-end",
126                      G_CALLBACK(OnPreeditEndThunk), this);
127     g_signal_connect(contexts[i], "preedit-start",
128                      G_CALLBACK(OnPreeditStartThunk), this);
129     // TODO(yukishiino): Handle operations on surrounding text.
130     // "delete-surrounding" and "retrieve-surrounding" signals should be
131     // handled.
132   }
133 }
134
135 X11InputMethodContextImplGtk2::~X11InputMethodContextImplGtk2() {
136   gtk_context_ = NULL;
137   if (gtk_context_simple_) {
138     g_object_unref(gtk_context_simple_);
139     gtk_context_simple_ = NULL;
140   }
141   if (gtk_multicontext_) {
142     g_object_unref(gtk_multicontext_);
143     gtk_multicontext_ = NULL;
144   }
145 }
146
147 // Overriden from ui::LinuxInputMethodContext
148
149 bool X11InputMethodContextImplGtk2::DispatchKeyEvent(
150     const ui::KeyEvent& key_event) {
151   if (!key_event.HasNativeEvent())
152     return false;
153
154   // The caller must call Focus() first.
155   if (!gtk_context_)
156     return false;
157
158   // Translate a XKeyEvent to a GdkEventKey.
159   const base::NativeEvent& native_key_event = key_event.native_event();
160   GdkEvent* event = GdkEventFromXKeyEvent(
161       native_key_event->xkey,
162       IsKeycodeModifierKey(native_key_event->xkey.keycode));
163   if (!event) {
164     LOG(ERROR) << "Cannot translate a XKeyEvent to a GdkEvent.";
165     return false;
166   }
167
168   // Set the client window and cursor location.
169   if (event->key.window != gdk_last_set_client_window_) {
170     gtk_im_context_set_client_window(gtk_context_, event->key.window);
171     gdk_last_set_client_window_ = event->key.window;
172   }
173   // Convert the last known caret bounds relative to the screen coordinates
174   // to a GdkRectangle relative to the client window.
175   gint x = 0;
176   gint y = 0;
177   gdk_window_get_origin(event->key.window, &x, &y);
178   GdkRectangle rect = {last_caret_bounds_.x() - x,
179                        last_caret_bounds_.y() - y,
180                        last_caret_bounds_.width(),
181                        last_caret_bounds_.height()};
182   gtk_im_context_set_cursor_location(gtk_context_, &rect);
183
184   // Let an IME handle the key event.
185   commit_signal_trap_.StartTrap(event->key.keyval);
186   const gboolean handled = gtk_im_context_filter_keypress(gtk_context_,
187                                                           &event->key);
188   commit_signal_trap_.StopTrap();
189   gdk_event_free(event);
190
191   return handled && !commit_signal_trap_.IsSignalCaught();
192 }
193
194 void X11InputMethodContextImplGtk2::Reset() {
195   // Reset all the states of the context, not only preedit, caret but also
196   // focus.
197   gtk_context_ = NULL;
198   gtk_im_context_reset(gtk_context_simple_);
199   gtk_im_context_reset(gtk_multicontext_);
200   gtk_im_context_focus_out(gtk_context_simple_);
201   gtk_im_context_focus_out(gtk_multicontext_);
202   gdk_last_set_client_window_ = NULL;
203 }
204
205 void X11InputMethodContextImplGtk2::OnTextInputTypeChanged(
206     ui::TextInputType text_input_type) {
207   switch (text_input_type) {
208     case ui::TEXT_INPUT_TYPE_NONE:
209     case ui::TEXT_INPUT_TYPE_PASSWORD:
210       gtk_context_ = gtk_context_simple_;
211       break;
212     default:
213       gtk_context_ = gtk_multicontext_;
214   }
215   gtk_im_context_focus_in(gtk_context_);
216 }
217
218 void X11InputMethodContextImplGtk2::OnCaretBoundsChanged(
219     const gfx::Rect& caret_bounds) {
220   // Remember the caret bounds so that we can set the cursor location later.
221   // gtk_im_context_set_cursor_location() takes the location relative to the
222   // client window, which is unknown at this point.  So we'll call
223   // gtk_im_context_set_cursor_location() later in ProcessKeyEvent() where
224   // (and only where) we know the client window.
225   last_caret_bounds_ = caret_bounds;
226 }
227
228 // private:
229
230 bool X11InputMethodContextImplGtk2::IsKeycodeModifierKey(
231     unsigned int keycode) const {
232   return modifier_keycodes_.find(keycode) != modifier_keycodes_.end();
233 }
234
235 // GtkIMContext event handlers.
236
237 void X11InputMethodContextImplGtk2::OnCommit(GtkIMContext* context,
238                                              gchar* text) {
239   if (context != gtk_context_)
240     return;
241
242   const base::string16& text_in_utf16 = base::UTF8ToUTF16(text);
243   // If an underlying IME is emitting the "commit" signal to insert a character
244   // for a direct input key event, ignores the insertion of the character at
245   // this point, because we have to call DispatchKeyEventPostIME() for direct
246   // input key events.  DispatchKeyEvent() takes care of the trapped character
247   // and calls DispatchKeyEventPostIME().
248   if (commit_signal_trap_.Trap(text_in_utf16))
249     return;
250
251   delegate_->OnCommit(text_in_utf16);
252 }
253
254 void X11InputMethodContextImplGtk2::OnPreeditChanged(GtkIMContext* context) {
255   if (context != gtk_context_)
256     return;
257
258   gchar* str = NULL;
259   PangoAttrList* attrs = NULL;
260   gint cursor_pos = 0;
261   gtk_im_context_get_preedit_string(context, &str, &attrs, &cursor_pos);
262   ui::CompositionText composition_text;
263   ui::ExtractCompositionTextFromGtkPreedit(str, attrs, cursor_pos,
264                                            &composition_text);
265   g_free(str);
266   pango_attr_list_unref(attrs);
267
268   delegate_->OnPreeditChanged(composition_text);
269 }
270
271 void X11InputMethodContextImplGtk2::OnPreeditEnd(GtkIMContext* context) {
272   if (context != gtk_context_)
273     return;
274
275   delegate_->OnPreeditEnd();
276 }
277
278 void X11InputMethodContextImplGtk2::OnPreeditStart(GtkIMContext* context) {
279   if (context != gtk_context_)
280     return;
281
282   delegate_->OnPreeditStart();
283 }
284
285 // GtkCommitSignalTrap
286
287 X11InputMethodContextImplGtk2::GtkCommitSignalTrap::GtkCommitSignalTrap()
288     : is_trap_enabled_(false),
289       gdk_event_key_keyval_(GDK_KEY_VoidSymbol),
290       is_signal_caught_(false) {}
291
292 void X11InputMethodContextImplGtk2::GtkCommitSignalTrap::StartTrap(
293     guint keyval) {
294   is_signal_caught_ = false;
295   gdk_event_key_keyval_ = keyval;
296   is_trap_enabled_ = true;
297 }
298
299 void X11InputMethodContextImplGtk2::GtkCommitSignalTrap::StopTrap() {
300   is_trap_enabled_ = false;
301 }
302
303 bool X11InputMethodContextImplGtk2::GtkCommitSignalTrap::Trap(
304     const base::string16& text) {
305   DCHECK(!is_signal_caught_);
306   if (is_trap_enabled_ &&
307       text.length() == 1 &&
308       text[0] == gdk_keyval_to_unicode(gdk_event_key_keyval_)) {
309     is_signal_caught_ = true;
310     return true;
311   } else {
312     return false;
313   }
314 }
315
316 }  // namespace libgtk2ui