OLD | NEW |
1 // Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/ui/libgtk2ui/x11_input_method_context_impl_gtk2.h" | 5 #include "chrome/browser/ui/libgtk2ui/x11_input_method_context_impl_gtk2.h" |
6 | 6 |
7 #include <gdk/gdk.h> | 7 #include <gdk/gdk.h> |
8 #include <gdk/gdkkeysyms.h> | 8 #include <gdk/gdkkeysyms.h> |
9 #include <gdk/gdkx.h> | 9 #include <gdk/gdkx.h> |
10 | 10 |
11 #include <gtk/gtk.h> | 11 #include <gtk/gtk.h> |
12 | 12 |
13 #include <X11/X.h> | 13 #include <X11/X.h> |
14 #include <X11/Xlib.h> | 14 #include <X11/Xlib.h> |
15 | 15 |
16 #include "base/message_loop/message_loop.h" | 16 #include "base/message_loop/message_loop.h" |
17 #include "base/strings/utf_string_conversions.h" | 17 #include "base/strings/utf_string_conversions.h" |
18 #include "ui/base/ime/composition_text.h" | 18 #include "ui/base/ime/composition_text.h" |
19 #include "ui/base/ime/composition_text_util_pango.h" | 19 #include "ui/base/ime/composition_text_util_pango.h" |
20 #include "ui/base/ime/text_input_client.h" | 20 #include "ui/base/ime/text_input_client.h" |
21 #include "ui/events/event.h" | 21 #include "ui/events/event.h" |
22 #include "ui/events/keycodes/keyboard_code_conversion_x.h" | 22 #include "ui/events/keycodes/keyboard_code_conversion_x.h" |
23 #include "ui/gfx/x/x11_types.h" | 23 #include "ui/gfx/x/x11_types.h" |
24 | 24 |
25 namespace libgtk2ui { | 25 namespace libgtk2ui { |
26 | 26 |
27 X11InputMethodContextImplGtk2::X11InputMethodContextImplGtk2( | 27 X11InputMethodContextImplGtk2::X11InputMethodContextImplGtk2( |
28 ui::LinuxInputMethodContextDelegate* delegate) | 28 ui::LinuxInputMethodContextDelegate* delegate, |
| 29 bool is_simple) |
29 : delegate_(delegate), | 30 : delegate_(delegate), |
30 gtk_context_simple_(NULL), | |
31 gtk_multicontext_(NULL), | |
32 gtk_context_(NULL), | 31 gtk_context_(NULL), |
33 gdk_last_set_client_window_(NULL) { | 32 gdk_last_set_client_window_(NULL) { |
34 CHECK(delegate_); | 33 CHECK(delegate_); |
35 | 34 |
36 ResetXModifierKeycodesCache(); | 35 ResetXModifierKeycodesCache(); |
37 | 36 |
38 gtk_context_simple_ = gtk_im_context_simple_new(); | 37 gtk_context_ = |
39 gtk_multicontext_ = gtk_im_multicontext_new(); | 38 is_simple ? gtk_im_context_simple_new() : gtk_im_multicontext_new(); |
40 | 39 |
41 GtkIMContext* contexts[] = {gtk_context_simple_, gtk_multicontext_}; | 40 g_signal_connect(gtk_context_, "commit", G_CALLBACK(OnCommitThunk), this); |
42 for (size_t i = 0; i < arraysize(contexts); ++i) { | 41 g_signal_connect(gtk_context_, "preedit-changed", |
43 g_signal_connect(contexts[i], "commit", | 42 G_CALLBACK(OnPreeditChangedThunk), this); |
44 G_CALLBACK(OnCommitThunk), this); | 43 g_signal_connect(gtk_context_, "preedit-end", G_CALLBACK(OnPreeditEndThunk), |
45 g_signal_connect(contexts[i], "preedit-changed", | 44 this); |
46 G_CALLBACK(OnPreeditChangedThunk), this); | 45 g_signal_connect(gtk_context_, "preedit-start", |
47 g_signal_connect(contexts[i], "preedit-end", | 46 G_CALLBACK(OnPreeditStartThunk), this); |
48 G_CALLBACK(OnPreeditEndThunk), this); | 47 // TODO(shuchen): Handle operations on surrounding text. |
49 g_signal_connect(contexts[i], "preedit-start", | 48 // "delete-surrounding" and "retrieve-surrounding" signals should be |
50 G_CALLBACK(OnPreeditStartThunk), this); | 49 // handled. |
51 // TODO(yukishiino): Handle operations on surrounding text. | 50 } |
52 // "delete-surrounding" and "retrieve-surrounding" signals should be | 51 |
53 // handled. | 52 X11InputMethodContextImplGtk2::~X11InputMethodContextImplGtk2() { |
| 53 if (gtk_context_) { |
| 54 g_object_unref(gtk_context_); |
| 55 gtk_context_ = NULL; |
54 } | 56 } |
55 } | 57 } |
56 | 58 |
57 X11InputMethodContextImplGtk2::~X11InputMethodContextImplGtk2() { | |
58 gtk_context_ = NULL; | |
59 if (gtk_context_simple_) { | |
60 g_object_unref(gtk_context_simple_); | |
61 gtk_context_simple_ = NULL; | |
62 } | |
63 if (gtk_multicontext_) { | |
64 g_object_unref(gtk_multicontext_); | |
65 gtk_multicontext_ = NULL; | |
66 } | |
67 } | |
68 | |
69 // Overriden from ui::LinuxInputMethodContext | 59 // Overriden from ui::LinuxInputMethodContext |
70 | 60 |
71 bool X11InputMethodContextImplGtk2::DispatchKeyEvent( | 61 bool X11InputMethodContextImplGtk2::DispatchKeyEvent( |
72 const ui::KeyEvent& key_event) { | 62 const ui::KeyEvent& key_event) { |
73 if (!key_event.HasNativeEvent()) | 63 if (!key_event.HasNativeEvent() || !gtk_context_) |
74 return false; | |
75 | |
76 // The caller must call Focus() first. | |
77 if (!gtk_context_) | |
78 return false; | 64 return false; |
79 | 65 |
80 // Translate a XKeyEvent to a GdkEventKey. | 66 // Translate a XKeyEvent to a GdkEventKey. |
81 GdkEvent* event = GdkEventFromNativeEvent(key_event.native_event()); | 67 GdkEvent* event = GdkEventFromNativeEvent(key_event.native_event()); |
82 if (!event) { | 68 if (!event) { |
83 LOG(ERROR) << "Cannot translate a XKeyEvent to a GdkEvent."; | 69 LOG(ERROR) << "Cannot translate a XKeyEvent to a GdkEvent."; |
84 return false; | 70 return false; |
85 } | 71 } |
86 | 72 |
87 // Set the client window and cursor location. | |
88 if (event->key.window != gdk_last_set_client_window_) { | 73 if (event->key.window != gdk_last_set_client_window_) { |
89 gtk_im_context_set_client_window(gtk_context_, event->key.window); | 74 gtk_im_context_set_client_window(gtk_context_, event->key.window); |
90 gdk_last_set_client_window_ = event->key.window; | 75 gdk_last_set_client_window_ = event->key.window; |
91 } | 76 } |
| 77 |
92 // Convert the last known caret bounds relative to the screen coordinates | 78 // Convert the last known caret bounds relative to the screen coordinates |
93 // to a GdkRectangle relative to the client window. | 79 // to a GdkRectangle relative to the client window. |
94 gint x = 0; | 80 gint x = 0; |
95 gint y = 0; | 81 gint y = 0; |
96 gdk_window_get_origin(event->key.window, &x, &y); | 82 gdk_window_get_origin(event->key.window, &x, &y); |
97 GdkRectangle rect = {last_caret_bounds_.x() - x, | 83 GdkRectangle rect = {last_caret_bounds_.x() - x, |
98 last_caret_bounds_.y() - y, | 84 last_caret_bounds_.y() - y, |
99 last_caret_bounds_.width(), | 85 last_caret_bounds_.width(), |
100 last_caret_bounds_.height()}; | 86 last_caret_bounds_.height()}; |
101 gtk_im_context_set_cursor_location(gtk_context_, &rect); | 87 gtk_im_context_set_cursor_location(gtk_context_, &rect); |
102 | 88 |
103 // Let an IME handle the key event. | 89 const bool handled = |
104 commit_signal_trap_.StartTrap(event->key.keyval); | 90 gtk_im_context_filter_keypress(gtk_context_, &event->key); |
105 const gboolean handled = gtk_im_context_filter_keypress(gtk_context_, | |
106 &event->key); | |
107 commit_signal_trap_.StopTrap(); | |
108 gdk_event_free(event); | 91 gdk_event_free(event); |
109 | 92 return handled; |
110 return handled && !commit_signal_trap_.IsSignalCaught(); | |
111 } | 93 } |
112 | 94 |
113 void X11InputMethodContextImplGtk2::Reset() { | 95 void X11InputMethodContextImplGtk2::Reset() { |
114 // Reset all the states of the context, not only preedit, caret but also | 96 gtk_im_context_reset(gtk_context_); |
115 // focus. | |
116 gtk_context_ = NULL; | |
117 gtk_im_context_reset(gtk_context_simple_); | |
118 gtk_im_context_reset(gtk_multicontext_); | |
119 gtk_im_context_focus_out(gtk_context_simple_); | |
120 gtk_im_context_focus_out(gtk_multicontext_); | |
121 gdk_last_set_client_window_ = NULL; | |
122 } | 97 } |
123 | 98 |
124 void X11InputMethodContextImplGtk2::OnTextInputTypeChanged( | 99 void X11InputMethodContextImplGtk2::Focus() { |
125 ui::TextInputType text_input_type) { | |
126 switch (text_input_type) { | |
127 case ui::TEXT_INPUT_TYPE_NONE: | |
128 case ui::TEXT_INPUT_TYPE_PASSWORD: | |
129 gtk_context_ = gtk_context_simple_; | |
130 break; | |
131 default: | |
132 gtk_context_ = gtk_multicontext_; | |
133 } | |
134 gtk_im_context_focus_in(gtk_context_); | 100 gtk_im_context_focus_in(gtk_context_); |
135 } | 101 } |
136 | 102 |
137 void X11InputMethodContextImplGtk2::OnCaretBoundsChanged( | 103 void X11InputMethodContextImplGtk2::Blur() { |
138 const gfx::Rect& caret_bounds) { | 104 gtk_im_context_focus_out(gtk_context_); |
| 105 } |
| 106 |
| 107 void X11InputMethodContextImplGtk2::SetCursorLocation(const gfx::Rect& rect) { |
139 // Remember the caret bounds so that we can set the cursor location later. | 108 // Remember the caret bounds so that we can set the cursor location later. |
140 // gtk_im_context_set_cursor_location() takes the location relative to the | 109 // gtk_im_context_set_cursor_location() takes the location relative to the |
141 // client window, which is unknown at this point. So we'll call | 110 // client window, which is unknown at this point. So we'll call |
142 // gtk_im_context_set_cursor_location() later in ProcessKeyEvent() where | 111 // gtk_im_context_set_cursor_location() later in ProcessKeyEvent() where |
143 // (and only where) we know the client window. | 112 // (and only where) we know the client window. |
144 last_caret_bounds_ = caret_bounds; | 113 last_caret_bounds_ = rect; |
145 } | 114 } |
146 | 115 |
147 // private: | 116 // private: |
148 | 117 |
149 void X11InputMethodContextImplGtk2::ResetXModifierKeycodesCache() { | 118 void X11InputMethodContextImplGtk2::ResetXModifierKeycodesCache() { |
150 modifier_keycodes_.clear(); | 119 modifier_keycodes_.clear(); |
151 meta_keycodes_.clear(); | 120 meta_keycodes_.clear(); |
152 super_keycodes_.clear(); | 121 super_keycodes_.clear(); |
153 hyper_keycodes_.clear(); | 122 hyper_keycodes_.clear(); |
154 | 123 |
(...skipping 147 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
302 return false; | 271 return false; |
303 } | 272 } |
304 | 273 |
305 // GtkIMContext event handlers. | 274 // GtkIMContext event handlers. |
306 | 275 |
307 void X11InputMethodContextImplGtk2::OnCommit(GtkIMContext* context, | 276 void X11InputMethodContextImplGtk2::OnCommit(GtkIMContext* context, |
308 gchar* text) { | 277 gchar* text) { |
309 if (context != gtk_context_) | 278 if (context != gtk_context_) |
310 return; | 279 return; |
311 | 280 |
312 const base::string16& text_in_utf16 = base::UTF8ToUTF16(text); | 281 delegate_->OnCommit(base::UTF8ToUTF16(text)); |
313 // If an underlying IME is emitting the "commit" signal to insert a character | |
314 // for a direct input key event, ignores the insertion of the character at | |
315 // this point, because we have to call DispatchKeyEventPostIME() for direct | |
316 // input key events. DispatchKeyEvent() takes care of the trapped character | |
317 // and calls DispatchKeyEventPostIME(). | |
318 if (commit_signal_trap_.Trap(text_in_utf16)) | |
319 return; | |
320 | |
321 delegate_->OnCommit(text_in_utf16); | |
322 } | 282 } |
323 | 283 |
324 void X11InputMethodContextImplGtk2::OnPreeditChanged(GtkIMContext* context) { | 284 void X11InputMethodContextImplGtk2::OnPreeditChanged(GtkIMContext* context) { |
325 if (context != gtk_context_) | 285 if (context != gtk_context_) |
326 return; | 286 return; |
327 | 287 |
328 gchar* str = NULL; | 288 gchar* str = NULL; |
329 PangoAttrList* attrs = NULL; | 289 PangoAttrList* attrs = NULL; |
330 gint cursor_pos = 0; | 290 gint cursor_pos = 0; |
331 gtk_im_context_get_preedit_string(context, &str, &attrs, &cursor_pos); | 291 gtk_im_context_get_preedit_string(context, &str, &attrs, &cursor_pos); |
(...skipping 13 matching lines...) Expand all Loading... |
345 delegate_->OnPreeditEnd(); | 305 delegate_->OnPreeditEnd(); |
346 } | 306 } |
347 | 307 |
348 void X11InputMethodContextImplGtk2::OnPreeditStart(GtkIMContext* context) { | 308 void X11InputMethodContextImplGtk2::OnPreeditStart(GtkIMContext* context) { |
349 if (context != gtk_context_) | 309 if (context != gtk_context_) |
350 return; | 310 return; |
351 | 311 |
352 delegate_->OnPreeditStart(); | 312 delegate_->OnPreeditStart(); |
353 } | 313 } |
354 | 314 |
355 // GtkCommitSignalTrap | |
356 | |
357 X11InputMethodContextImplGtk2::GtkCommitSignalTrap::GtkCommitSignalTrap() | |
358 : is_trap_enabled_(false), | |
359 #if GTK_CHECK_VERSION (2,22,0) | |
360 gdk_event_key_keyval_(GDK_KEY_VoidSymbol), | |
361 #else | |
362 gdk_event_key_keyval_(GDK_VoidSymbol), | |
363 #endif | |
364 is_signal_caught_(false) {} | |
365 | |
366 void X11InputMethodContextImplGtk2::GtkCommitSignalTrap::StartTrap( | |
367 guint keyval) { | |
368 is_signal_caught_ = false; | |
369 gdk_event_key_keyval_ = keyval; | |
370 is_trap_enabled_ = true; | |
371 } | |
372 | |
373 void X11InputMethodContextImplGtk2::GtkCommitSignalTrap::StopTrap() { | |
374 is_trap_enabled_ = false; | |
375 } | |
376 | |
377 bool X11InputMethodContextImplGtk2::GtkCommitSignalTrap::Trap( | |
378 const base::string16& text) { | |
379 DCHECK(!is_signal_caught_); | |
380 if (is_trap_enabled_ && | |
381 text.length() == 1 && | |
382 text[0] == gdk_keyval_to_unicode(gdk_event_key_keyval_)) { | |
383 is_signal_caught_ = true; | |
384 return true; | |
385 } else { | |
386 return false; | |
387 } | |
388 } | |
389 | |
390 } // namespace libgtk2ui | 315 } // namespace libgtk2ui |
OLD | NEW |