| 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, bool is_simple) |
| 29 : delegate_(delegate), | 29 : delegate_(delegate), gtk_context_(NULL), |
| 30 gtk_context_simple_(NULL), | |
| 31 gtk_multicontext_(NULL), | |
| 32 gtk_context_(NULL), | |
| 33 gdk_last_set_client_window_(NULL) { | 30 gdk_last_set_client_window_(NULL) { |
| 34 CHECK(delegate_); | 31 CHECK(delegate_); |
| 35 | 32 |
| 36 ResetXModifierKeycodesCache(); | 33 ResetXModifierKeycodesCache(); |
| 37 | 34 |
| 38 gtk_context_simple_ = gtk_im_context_simple_new(); | 35 if (is_simple) |
| 39 gtk_multicontext_ = gtk_im_multicontext_new(); | 36 gtk_context_ = gtk_im_context_simple_new(); |
| 37 else |
| 38 gtk_context_ = gtk_im_multicontext_new(); |
| 40 | 39 |
| 41 GtkIMContext* contexts[] = {gtk_context_simple_, gtk_multicontext_}; | 40 g_signal_connect(gtk_context_, "commit", |
| 42 for (size_t i = 0; i < arraysize(contexts); ++i) { | 41 G_CALLBACK(OnCommitThunk), this); |
| 43 g_signal_connect(contexts[i], "commit", | 42 g_signal_connect(gtk_context_, "preedit-changed", |
| 44 G_CALLBACK(OnCommitThunk), this); | 43 G_CALLBACK(OnPreeditChangedThunk), this); |
| 45 g_signal_connect(contexts[i], "preedit-changed", | 44 g_signal_connect(gtk_context_, "preedit-end", |
| 46 G_CALLBACK(OnPreeditChangedThunk), this); | 45 G_CALLBACK(OnPreeditEndThunk), this); |
| 47 g_signal_connect(contexts[i], "preedit-end", | 46 g_signal_connect(gtk_context_, "preedit-start", |
| 48 G_CALLBACK(OnPreeditEndThunk), this); | 47 G_CALLBACK(OnPreeditStartThunk), this); |
| 49 g_signal_connect(contexts[i], "preedit-start", | 48 // TODO(yukishiino): Handle operations on surrounding text. |
| 50 G_CALLBACK(OnPreeditStartThunk), this); | 49 // "delete-surrounding" and "retrieve-surrounding" signals should be |
| 51 // TODO(yukishiino): Handle operations on surrounding text. | 50 // handled. |
| 52 // "delete-surrounding" and "retrieve-surrounding" signals should be | 51 } |
| 53 // handled. | 52 |
| 53 X11InputMethodContextImplGtk2::~X11InputMethodContextImplGtk2() { |
| 54 if (gtk_context_) { |
| 55 g_object_unref(gtk_context_); |
| 56 gtk_context_ = NULL; |
| 54 } | 57 } |
| 55 } | 58 } |
| 56 | 59 |
| 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 | 60 // Overriden from ui::LinuxInputMethodContext |
| 70 | 61 |
| 71 bool X11InputMethodContextImplGtk2::DispatchKeyEvent( | 62 bool X11InputMethodContextImplGtk2::DispatchKeyEvent( |
| 72 const ui::KeyEvent& key_event) { | 63 const ui::KeyEvent& key_event) { |
| 73 if (!key_event.HasNativeEvent()) | 64 if (!key_event.HasNativeEvent() || !gtk_context_) |
| 74 return false; | |
| 75 | |
| 76 // The caller must call Focus() first. | |
| 77 if (!gtk_context_) | |
| 78 return false; | 65 return false; |
| 79 | 66 |
| 80 // Translate a XKeyEvent to a GdkEventKey. | 67 // Translate a XKeyEvent to a GdkEventKey. |
| 81 GdkEvent* event = GdkEventFromNativeEvent(key_event.native_event()); | 68 GdkEvent* event = GdkEventFromNativeEvent(key_event.native_event()); |
| 82 if (!event) { | 69 if (!event) { |
| 83 LOG(ERROR) << "Cannot translate a XKeyEvent to a GdkEvent."; | 70 LOG(ERROR) << "Cannot translate a XKeyEvent to a GdkEvent."; |
| 84 return false; | 71 return false; |
| 85 } | 72 } |
| 86 | 73 |
| 87 // Set the client window and cursor location. | |
| 88 if (event->key.window != gdk_last_set_client_window_) { | 74 if (event->key.window != gdk_last_set_client_window_) { |
| 89 gtk_im_context_set_client_window(gtk_context_, event->key.window); | 75 gtk_im_context_set_client_window(gtk_context_, event->key.window); |
| 90 gdk_last_set_client_window_ = event->key.window; | 76 gdk_last_set_client_window_ = event->key.window; |
| 91 } | 77 } |
| 78 |
| 92 // Convert the last known caret bounds relative to the screen coordinates | 79 // Convert the last known caret bounds relative to the screen coordinates |
| 93 // to a GdkRectangle relative to the client window. | 80 // to a GdkRectangle relative to the client window. |
| 94 gint x = 0; | 81 gint x = 0; |
| 95 gint y = 0; | 82 gint y = 0; |
| 96 gdk_window_get_origin(event->key.window, &x, &y); | 83 gdk_window_get_origin(event->key.window, &x, &y); |
| 97 GdkRectangle rect = {last_caret_bounds_.x() - x, | 84 GdkRectangle rect = {last_caret_bounds_.x() - x, |
| 98 last_caret_bounds_.y() - y, | 85 last_caret_bounds_.y() - y, |
| 99 last_caret_bounds_.width(), | 86 last_caret_bounds_.width(), |
| 100 last_caret_bounds_.height()}; | 87 last_caret_bounds_.height()}; |
| 101 gtk_im_context_set_cursor_location(gtk_context_, &rect); | 88 gtk_im_context_set_cursor_location(gtk_context_, &rect); |
| 102 | 89 |
| 103 // Let an IME handle the key event. | 90 const bool handled = gtk_im_context_filter_keypress(gtk_context_, |
| 104 commit_signal_trap_.StartTrap(event->key.keyval); | 91 &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); | 92 gdk_event_free(event); |
| 109 | 93 return handled; |
| 110 return handled && !commit_signal_trap_.IsSignalCaught(); | |
| 111 } | 94 } |
| 112 | 95 |
| 113 void X11InputMethodContextImplGtk2::Reset() { | 96 void X11InputMethodContextImplGtk2::Reset() { |
| 114 // Reset all the states of the context, not only preedit, caret but also | 97 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 } | 98 } |
| 123 | 99 |
| 124 void X11InputMethodContextImplGtk2::OnTextInputTypeChanged( | 100 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_); | 101 gtk_im_context_focus_in(gtk_context_); |
| 135 } | 102 } |
| 136 | 103 |
| 137 void X11InputMethodContextImplGtk2::OnCaretBoundsChanged( | 104 void X11InputMethodContextImplGtk2::Blur() { |
| 138 const gfx::Rect& caret_bounds) { | 105 gtk_im_context_focus_out(gtk_context_); |
| 106 } |
| 107 |
| 108 void X11InputMethodContextImplGtk2::SetCursorLocation(const gfx::Rect& rect) { |
| 139 // Remember the caret bounds so that we can set the cursor location later. | 109 // 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 | 110 // 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 | 111 // client window, which is unknown at this point. So we'll call |
| 142 // gtk_im_context_set_cursor_location() later in ProcessKeyEvent() where | 112 // gtk_im_context_set_cursor_location() later in ProcessKeyEvent() where |
| 143 // (and only where) we know the client window. | 113 // (and only where) we know the client window. |
| 144 last_caret_bounds_ = caret_bounds; | 114 last_caret_bounds_ = rect; |
| 145 } | 115 } |
| 146 | 116 |
| 147 // private: | 117 // private: |
| 148 | 118 |
| 149 void X11InputMethodContextImplGtk2::ResetXModifierKeycodesCache() { | 119 void X11InputMethodContextImplGtk2::ResetXModifierKeycodesCache() { |
| 150 modifier_keycodes_.clear(); | 120 modifier_keycodes_.clear(); |
| 151 meta_keycodes_.clear(); | 121 meta_keycodes_.clear(); |
| 152 super_keycodes_.clear(); | 122 super_keycodes_.clear(); |
| 153 hyper_keycodes_.clear(); | 123 hyper_keycodes_.clear(); |
| 154 | 124 |
| (...skipping 147 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 302 return false; | 272 return false; |
| 303 } | 273 } |
| 304 | 274 |
| 305 // GtkIMContext event handlers. | 275 // GtkIMContext event handlers. |
| 306 | 276 |
| 307 void X11InputMethodContextImplGtk2::OnCommit(GtkIMContext* context, | 277 void X11InputMethodContextImplGtk2::OnCommit(GtkIMContext* context, |
| 308 gchar* text) { | 278 gchar* text) { |
| 309 if (context != gtk_context_) | 279 if (context != gtk_context_) |
| 310 return; | 280 return; |
| 311 | 281 |
| 312 const base::string16& text_in_utf16 = base::UTF8ToUTF16(text); | 282 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 } | 283 } |
| 323 | 284 |
| 324 void X11InputMethodContextImplGtk2::OnPreeditChanged(GtkIMContext* context) { | 285 void X11InputMethodContextImplGtk2::OnPreeditChanged(GtkIMContext* context) { |
| 325 if (context != gtk_context_) | 286 if (context != gtk_context_) |
| 326 return; | 287 return; |
| 327 | 288 |
| 328 gchar* str = NULL; | 289 gchar* str = NULL; |
| 329 PangoAttrList* attrs = NULL; | 290 PangoAttrList* attrs = NULL; |
| 330 gint cursor_pos = 0; | 291 gint cursor_pos = 0; |
| 331 gtk_im_context_get_preedit_string(context, &str, &attrs, &cursor_pos); | 292 gtk_im_context_get_preedit_string(context, &str, &attrs, &cursor_pos); |
| (...skipping 13 matching lines...) Expand all Loading... |
| 345 delegate_->OnPreeditEnd(); | 306 delegate_->OnPreeditEnd(); |
| 346 } | 307 } |
| 347 | 308 |
| 348 void X11InputMethodContextImplGtk2::OnPreeditStart(GtkIMContext* context) { | 309 void X11InputMethodContextImplGtk2::OnPreeditStart(GtkIMContext* context) { |
| 349 if (context != gtk_context_) | 310 if (context != gtk_context_) |
| 350 return; | 311 return; |
| 351 | 312 |
| 352 delegate_->OnPreeditStart(); | 313 delegate_->OnPreeditStart(); |
| 353 } | 314 } |
| 354 | 315 |
| 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 | 316 } // namespace libgtk2ui |
| OLD | NEW |