Home | History | Annotate | Download | only in renderer_host
      1 // Copyright (c) 2012 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 "content/browser/renderer_host/gtk_im_context_wrapper.h"
      6 
      7 #include <gdk/gdk.h>
      8 #include <gdk/gdkkeysyms.h>
      9 #include <gtk/gtk.h>
     10 
     11 #include <algorithm>
     12 
     13 #include "base/logging.h"
     14 #include "base/strings/string_util.h"
     15 #include "base/strings/utf_string_conversions.h"
     16 #include "content/browser/renderer_host/render_widget_host_impl.h"
     17 #include "content/browser/renderer_host/render_widget_host_view_gtk.h"
     18 #include "content/public/browser/native_web_keyboard_event.h"
     19 #include "third_party/WebKit/public/web/WebCompositionUnderline.h"
     20 #include "ui/base/gtk/gtk_im_context_util.h"
     21 #include "ui/gfx/gtk_util.h"
     22 #include "ui/gfx/rect.h"
     23 
     24 namespace content {
     25 namespace {
     26 
     27 // Copied from third_party/WebKit/Source/WebCore/page/EventHandler.cpp
     28 //
     29 // Match key code of composition keydown event on windows.
     30 // IE sends VK_PROCESSKEY which has value 229;
     31 //
     32 // Please refer to following documents for detals:
     33 // - Virtual-Key Codes
     34 //   http://msdn.microsoft.com/en-us/library/ms645540(VS.85).aspx
     35 // - How the IME System Works
     36 //   http://msdn.microsoft.com/en-us/library/cc194848.aspx
     37 // - ImmGetVirtualKey Function
     38 //   http://msdn.microsoft.com/en-us/library/dd318570(VS.85).aspx
     39 const int kCompositionEventKeyCode = 229;
     40 
     41 }  // namespace
     42 
     43 // ui::CompositionUnderline should be identical to
     44 // WebKit::WebCompositionUnderline, so that we can do reinterpret_cast safely.
     45 // TODO(suzhe): remove it after migrating all code in chrome to use
     46 // ui::CompositionUnderline.
     47 COMPILE_ASSERT(sizeof(ui::CompositionUnderline) ==
     48                sizeof(WebKit::WebCompositionUnderline),
     49                ui_CompositionUnderline__WebKit_WebCompositionUnderline_diff);
     50 
     51 GtkIMContextWrapper::GtkIMContextWrapper(RenderWidgetHostViewGtk* host_view)
     52     : host_view_(host_view),
     53       context_(gtk_im_multicontext_new()),
     54       context_simple_(gtk_im_context_simple_new()),
     55       is_focused_(false),
     56       is_composing_text_(false),
     57       is_enabled_(false),
     58       is_in_key_event_handler_(false),
     59       is_composition_changed_(false),
     60       suppress_next_commit_(false),
     61       last_key_code_(0),
     62       last_key_was_up_(false),
     63       last_key_filtered_no_result_(false) {
     64   DCHECK(context_);
     65   DCHECK(context_simple_);
     66 
     67   // context_ and context_simple_ share the same callback handlers.
     68   // All data come from them are treated equally.
     69   // context_ is for full input method support.
     70   // context_simple_ is for supporting dead/compose keys when input method is
     71   // disabled by webkit, eg. in password input box.
     72   g_signal_connect(context_, "preedit-start",
     73                    G_CALLBACK(HandlePreeditStartThunk), this);
     74   g_signal_connect(context_, "preedit-end",
     75                    G_CALLBACK(HandlePreeditEndThunk), this);
     76   g_signal_connect(context_, "preedit-changed",
     77                    G_CALLBACK(HandlePreeditChangedThunk), this);
     78   g_signal_connect(context_, "commit",
     79                    G_CALLBACK(HandleCommitThunk), this);
     80   g_signal_connect(context_, "retrieve-surrounding",
     81                    G_CALLBACK(HandleRetrieveSurroundingThunk), this);
     82 
     83   g_signal_connect(context_simple_, "preedit-start",
     84                    G_CALLBACK(HandlePreeditStartThunk), this);
     85   g_signal_connect(context_simple_, "preedit-end",
     86                    G_CALLBACK(HandlePreeditEndThunk), this);
     87   g_signal_connect(context_simple_, "preedit-changed",
     88                    G_CALLBACK(HandlePreeditChangedThunk), this);
     89   g_signal_connect(context_simple_, "commit",
     90                    G_CALLBACK(HandleCommitThunk), this);
     91   g_signal_connect(context_simple_, "retrieve-surrounding",
     92                    G_CALLBACK(HandleRetrieveSurroundingThunk), this);
     93 
     94   GtkWidget* widget = host_view->GetNativeView();
     95   DCHECK(widget);
     96 
     97   g_signal_connect(widget, "realize",
     98                    G_CALLBACK(HandleHostViewRealizeThunk), this);
     99   g_signal_connect(widget, "unrealize",
    100                    G_CALLBACK(HandleHostViewUnrealizeThunk), this);
    101 
    102   // Set client window if the widget is already realized.
    103   HandleHostViewRealize(widget);
    104 }
    105 
    106 GtkIMContextWrapper::~GtkIMContextWrapper() {
    107   if (context_)
    108     g_object_unref(context_);
    109   if (context_simple_)
    110     g_object_unref(context_simple_);
    111 }
    112 
    113 void GtkIMContextWrapper::ProcessKeyEvent(GdkEventKey* event) {
    114   suppress_next_commit_ = false;
    115 
    116   // Indicates preedit-changed and commit signal handlers that we are
    117   // processing a key event.
    118   is_in_key_event_handler_ = true;
    119   // Reset this flag so that we can know if preedit is changed after
    120   // processing this key event.
    121   is_composition_changed_ = false;
    122   // Clear it so that we can know if something needs committing after
    123   // processing this key event.
    124   commit_text_.clear();
    125 
    126   // According to Document Object Model (DOM) Level 3 Events Specification
    127   // Appendix A: Keyboard events and key identifiers
    128   // http://www.w3.org/TR/DOM-Level-3-Events/keyset.html:
    129   // The event sequence would be:
    130   // 1. keydown
    131   // 2. textInput
    132   // 3. keyup
    133   //
    134   // So keydown must be sent to webkit before sending input method result,
    135   // while keyup must be sent afterwards.
    136   // However on Windows, if a keydown event has been processed by IME, its
    137   // virtual keycode will be changed to VK_PROCESSKEY(0xE5) before being sent
    138   // to application.
    139   // To emulate the windows behavior as much as possible, we need to send the
    140   // key event to the GtkIMContext object first, and decide whether or not to
    141   // send the original key event to webkit according to the result from IME.
    142   //
    143   // If IME is enabled by WebKit, this event will be dispatched to context_
    144   // to get full IME support. Otherwise it'll be dispatched to
    145   // context_simple_, so that dead/compose keys can still work.
    146   //
    147   // It sends a "commit" signal when it has a character to be inserted
    148   // even when we use a US keyboard so that we can send a Char event
    149   // (or an IME event) to the renderer in our "commit"-signal handler.
    150   // We should send a KeyDown (or a KeyUp) event before dispatching this
    151   // event to the GtkIMContext object (and send a Char event) so that WebKit
    152   // can dispatch the JavaScript events in the following order: onkeydown(),
    153   // onkeypress(), and onkeyup(). (Many JavaScript pages assume this.)
    154   gboolean filtered = false;
    155   if (is_enabled_) {
    156     filtered = gtk_im_context_filter_keypress(context_, event);
    157   } else {
    158     filtered = gtk_im_context_filter_keypress(context_simple_, event);
    159   }
    160 
    161   // Reset this flag here, as it's only used in input method callbacks.
    162   is_in_key_event_handler_ = false;
    163 
    164   NativeWebKeyboardEvent wke(reinterpret_cast<GdkEvent*>(event));
    165 
    166   // If the key event was handled by the input method, then we need to prevent
    167   // RenderView::UnhandledKeyboardEvent() from processing it.
    168   // Otherwise unexpected result may occur. For example if it's a
    169   // Backspace key event, the browser may go back to previous page.
    170   // We just send all keyup events to the browser to avoid breaking the
    171   // browser's MENU key function, which is actually the only keyup event
    172   // handled in the browser.
    173   if (filtered && event->type == GDK_KEY_PRESS)
    174     wke.skip_in_browser = true;
    175 
    176   const int key_code = wke.windowsKeyCode;
    177   const bool has_result = HasInputMethodResult();
    178 
    179   // Send filtered keydown event before sending IME result.
    180   // In order to workaround http://crosbug.com/6582, we only send a filtered
    181   // keydown event if it generated any input method result.
    182   if (event->type == GDK_KEY_PRESS && filtered && has_result)
    183     ProcessFilteredKeyPressEvent(&wke);
    184 
    185   // Send IME results. In most cases, it's only available if the key event
    186   // is filtered by IME. But in rare cases, an unfiltered key event may also
    187   // generate IME results.
    188   // Any IME results generated by a unfiltered key down event must be sent
    189   // before the key down event, to avoid some tricky issues. For example,
    190   // when using latin-post input method, pressing 'a' then Backspace, may
    191   // generate following events in sequence:
    192   //  1. keydown 'a' (filtered)
    193   //  2. preedit changed to "a"
    194   //  3. keyup 'a' (unfiltered)
    195   //  4. keydown Backspace (unfiltered)
    196   //  5. commit "a"
    197   //  6. preedit end
    198   //  7. keyup Backspace (unfiltered)
    199   //
    200   // In this case, the input box will be in a strange state if keydown
    201   // Backspace is sent to webkit before commit "a" and preedit end.
    202   if (has_result)
    203     ProcessInputMethodResult(event, filtered);
    204 
    205   // Send unfiltered keydown and keyup events after sending IME result.
    206   if (event->type == GDK_KEY_PRESS && !filtered) {
    207     ProcessUnfilteredKeyPressEvent(&wke);
    208   } else if (event->type == GDK_KEY_RELEASE) {
    209     // In order to workaround http://crosbug.com/6582, we need to suppress
    210     // the keyup event if corresponding keydown event was suppressed, or
    211     // the last key event was a keyup event with the same keycode.
    212     const bool suppress = (last_key_code_ == key_code) &&
    213         (last_key_was_up_ || last_key_filtered_no_result_);
    214 
    215     if (!suppress)
    216       host_view_->ForwardKeyboardEvent(wke);
    217   }
    218 
    219   last_key_code_ = key_code;
    220   last_key_was_up_ = (event->type == GDK_KEY_RELEASE);
    221   last_key_filtered_no_result_ = (filtered && !has_result);
    222 }
    223 
    224 void GtkIMContextWrapper::UpdateInputMethodState(
    225     ui::TextInputType type,
    226     bool can_compose_inline) {
    227   suppress_next_commit_ = false;
    228 
    229   // The renderer has updated its IME status.
    230   // Control the GtkIMContext object according to this status.
    231   if (!context_)
    232     return;
    233 
    234   DCHECK(!is_in_key_event_handler_);
    235 
    236   bool is_enabled = (type != ui::TEXT_INPUT_TYPE_NONE &&
    237       type != ui::TEXT_INPUT_TYPE_PASSWORD);
    238   if (is_enabled_ != is_enabled) {
    239     is_enabled_ = is_enabled;
    240     if (is_enabled && is_focused_)
    241       gtk_im_context_focus_in(context_);
    242     else
    243       gtk_im_context_focus_out(context_);
    244   }
    245 
    246   if (is_enabled) {
    247     // If the focused element supports inline rendering of composition text,
    248     // we receive and send related events to it. Otherwise, the events related
    249     // to the updates of composition text are directed to the candidate window.
    250     gtk_im_context_set_use_preedit(context_, can_compose_inline);
    251   }
    252 }
    253 
    254 void GtkIMContextWrapper::UpdateCaretBounds(
    255     const gfx::Rect& caret_bounds) {
    256   if (is_enabled_) {
    257     // Updates the position of the IME candidate window.
    258     // The position sent from the renderer is a relative one, so we need to
    259     // attach the GtkIMContext object to this window before changing the
    260     // position.
    261     GdkRectangle cursor_rect(caret_bounds.ToGdkRectangle());
    262     gtk_im_context_set_cursor_location(context_, &cursor_rect);
    263   }
    264 }
    265 
    266 void GtkIMContextWrapper::OnFocusIn() {
    267   if (is_focused_)
    268     return;
    269 
    270   // Tracks the focused state so that we can give focus to the
    271   // GtkIMContext object correctly later when IME is enabled by WebKit.
    272   is_focused_ = true;
    273 
    274   last_key_code_ = 0;
    275   last_key_was_up_ = false;
    276   last_key_filtered_no_result_ = false;
    277 
    278   // Notify the GtkIMContext object of this focus-in event only if IME is
    279   // enabled by WebKit.
    280   if (is_enabled_)
    281     gtk_im_context_focus_in(context_);
    282 
    283   // context_simple_ is always enabled.
    284   // Actually it doesn't care focus state at all.
    285   gtk_im_context_focus_in(context_simple_);
    286 
    287   // Enables RenderWidget's IME related events, so that we can be notified
    288   // when WebKit wants to enable or disable IME.
    289   if (host_view_->GetRenderWidgetHost()) {
    290     RenderWidgetHostImpl::From(
    291         host_view_->GetRenderWidgetHost())->SetInputMethodActive(true);
    292   }
    293 }
    294 
    295 void GtkIMContextWrapper::OnFocusOut() {
    296   if (!is_focused_)
    297     return;
    298 
    299   // Tracks the focused state so that we won't give focus to the
    300   // GtkIMContext object unexpectly.
    301   is_focused_ = false;
    302 
    303   // Notify the GtkIMContext object of this focus-out event only if IME is
    304   // enabled by WebKit.
    305   if (is_enabled_) {
    306     // To reset the GtkIMContext object and prevent data loss.
    307     ConfirmComposition();
    308     gtk_im_context_focus_out(context_);
    309   }
    310 
    311   // To make sure it'll be in correct state when focused in again.
    312   gtk_im_context_reset(context_simple_);
    313   gtk_im_context_focus_out(context_simple_);
    314 
    315   is_composing_text_ = false;
    316 
    317   // Disable RenderWidget's IME related events to save bandwidth.
    318   if (host_view_->GetRenderWidgetHost()) {
    319     RenderWidgetHostImpl::From(
    320         host_view_->GetRenderWidgetHost())->SetInputMethodActive(false);
    321   }
    322 }
    323 
    324 GtkWidget* GtkIMContextWrapper::BuildInputMethodsGtkMenu() {
    325   GtkWidget* submenu = gtk_menu_new();
    326   gtk_im_multicontext_append_menuitems(GTK_IM_MULTICONTEXT(context_),
    327                                        GTK_MENU_SHELL(submenu));
    328   return submenu;
    329 }
    330 
    331 void GtkIMContextWrapper::CancelComposition() {
    332   if (!is_enabled_)
    333     return;
    334 
    335   DCHECK(!is_in_key_event_handler_);
    336 
    337   // To prevent any text from being committed when resetting the |context_|;
    338   is_in_key_event_handler_ = true;
    339   suppress_next_commit_ = true;
    340 
    341   gtk_im_context_reset(context_);
    342   gtk_im_context_reset(context_simple_);
    343 
    344   if (is_focused_) {
    345     // Some input methods may not honour the reset call. Focusing out/in the
    346     // |context_| to make sure it gets reset correctly.
    347     gtk_im_context_focus_out(context_);
    348     gtk_im_context_focus_in(context_);
    349   }
    350 
    351   is_composing_text_ = false;
    352   composition_.Clear();
    353   commit_text_.clear();
    354 
    355   is_in_key_event_handler_ = false;
    356 }
    357 
    358 bool GtkIMContextWrapper::NeedCommitByForwardingCharEvent() const {
    359   // If there is no composition text and has only one character to be
    360   // committed, then the character will be send to webkit as a Char event
    361   // instead of a confirmed composition text.
    362   // It should be fine to handle BMP character only, as non-BMP characters
    363   // can always be committed as confirmed composition text.
    364   return !is_composing_text_ && commit_text_.length() == 1;
    365 }
    366 
    367 bool GtkIMContextWrapper::HasInputMethodResult() const {
    368   return commit_text_.length() || is_composition_changed_;
    369 }
    370 
    371 void GtkIMContextWrapper::ProcessFilteredKeyPressEvent(
    372     NativeWebKeyboardEvent* wke) {
    373   // If IME has filtered this event, then replace virtual key code with
    374   // VK_PROCESSKEY. See comment in ProcessKeyEvent() for details.
    375   // It's only required for keydown events.
    376   // To emulate windows behavior, when input method is enabled, if the commit
    377   // text can be emulated by a Char event, then don't do this replacement.
    378   if (!NeedCommitByForwardingCharEvent()) {
    379     wke->windowsKeyCode = kCompositionEventKeyCode;
    380     // keyidentifier must be updated accordingly, otherwise this key event may
    381     // still be processed by webkit.
    382     wke->setKeyIdentifierFromWindowsKeyCode();
    383   }
    384   host_view_->ForwardKeyboardEvent(*wke);
    385 }
    386 
    387 void GtkIMContextWrapper::ProcessUnfilteredKeyPressEvent(
    388     NativeWebKeyboardEvent* wke) {
    389   // Send keydown event as it, because it's not filtered by IME.
    390   host_view_->ForwardKeyboardEvent(*wke);
    391 
    392   // IME is disabled by WebKit or the GtkIMContext object cannot handle
    393   // this key event.
    394   // This case is caused by two reasons:
    395   // 1. The given key event is a control-key event, (e.g. return, page up,
    396   //    page down, tab, arrows, etc.) or;
    397   // 2. The given key event is not a control-key event but printable
    398   //    characters aren't assigned to the event, (e.g. alt+d, etc.)
    399   // Create a Char event manually from this key event and send it to the
    400   // renderer when this Char event contains a printable character which
    401   // should be processed by WebKit.
    402   // isSystemKey will be set to true if this key event has Alt modifier,
    403   // see WebInputEventFactory::keyboardEvent() for details.
    404   if (wke->text[0]) {
    405     wke->type = WebKit::WebInputEvent::Char;
    406     wke->skip_in_browser = true;
    407     host_view_->ForwardKeyboardEvent(*wke);
    408   }
    409 }
    410 
    411 void GtkIMContextWrapper::ProcessInputMethodResult(const GdkEventKey* event,
    412                                                    bool filtered) {
    413   RenderWidgetHostImpl* host = RenderWidgetHostImpl::From(
    414       host_view_->GetRenderWidgetHost());
    415   if (!host)
    416     return;
    417 
    418   bool committed = false;
    419   // We do commit before preedit change, so that we can optimize some
    420   // unnecessary preedit changes.
    421   if (commit_text_.length()) {
    422     if (filtered && NeedCommitByForwardingCharEvent()) {
    423       // Send a Char event when we input a composed character without IMEs
    424       // so that this event is to be dispatched to onkeypress() handlers,
    425       // autofill, etc.
    426       // Only commit text generated by a filtered key down event can be sent
    427       // as a Char event, because a unfiltered key down event will probably
    428       // generate another Char event.
    429       // TODO(james.su (at) gmail.com): Is it necessary to support non BMP chars
    430       // here?
    431       NativeWebKeyboardEvent char_event(commit_text_[0],
    432                                         event->state,
    433                                         base::Time::Now().ToDoubleT());
    434       char_event.skip_in_browser = true;
    435       host_view_->ForwardKeyboardEvent(char_event);
    436     } else {
    437       committed = true;
    438       // Send an IME event.
    439       // Unlike a Char event, an IME event is NOT dispatched to onkeypress()
    440       // handlers or autofill.
    441       host->ImeConfirmComposition(
    442           commit_text_,ui::Range::InvalidRange(),false);
    443       // Set this flag to false, as this composition session has been
    444       // finished.
    445       is_composing_text_ = false;
    446     }
    447   }
    448 
    449   // Send preedit text only if it's changed.
    450   // If a text has been committed, then we don't need to send the empty
    451   // preedit text again.
    452   if (is_composition_changed_) {
    453     if (composition_.text.length()) {
    454       // Another composition session has been started.
    455       is_composing_text_ = true;
    456       // TODO(suzhe): convert both renderer_host and renderer to use
    457       // ui::CompositionText.
    458       const std::vector<WebKit::WebCompositionUnderline>& underlines =
    459           reinterpret_cast<const std::vector<WebKit::WebCompositionUnderline>&>(
    460               composition_.underlines);
    461       host->ImeSetComposition(composition_.text, underlines,
    462                               composition_.selection.start(),
    463                               composition_.selection.end());
    464     } else if (!committed) {
    465       host->ImeCancelComposition();
    466     }
    467   }
    468 }
    469 
    470 void GtkIMContextWrapper::ConfirmComposition() {
    471   if (!is_enabled_)
    472     return;
    473 
    474   DCHECK(!is_in_key_event_handler_);
    475 
    476   if (is_composing_text_) {
    477     if (host_view_->GetRenderWidgetHost()) {
    478       RenderWidgetHostImpl::From(
    479           host_view_->GetRenderWidgetHost())->ImeConfirmComposition(
    480               string16(), ui::Range::InvalidRange(), false);
    481     }
    482 
    483     // Reset the input method.
    484     CancelComposition();
    485   }
    486 }
    487 
    488 void GtkIMContextWrapper::HandleCommit(const string16& text) {
    489   if (suppress_next_commit_)
    490     return;
    491 
    492   // Append the text to the buffer, because commit signal might be fired
    493   // multiple times when processing a key event.
    494   commit_text_.append(text);
    495   // Nothing needs to do, if it's currently in ProcessKeyEvent()
    496   // handler, which will send commit text to webkit later. Otherwise,
    497   // we need send it here.
    498   // It's possible that commit signal is fired without a key event, for
    499   // example when user input via a voice or handwriting recognition software.
    500   // In this case, the text must be committed directly.
    501   if (!is_in_key_event_handler_ && host_view_->GetRenderWidgetHost()) {
    502     // Workaround http://crbug.com/45478 by sending fake key down/up events.
    503     SendFakeCompositionKeyEvent(WebKit::WebInputEvent::RawKeyDown);
    504     RenderWidgetHostImpl::From(
    505         host_view_->GetRenderWidgetHost())->ImeConfirmComposition(
    506             text, ui::Range::InvalidRange(), false);
    507     SendFakeCompositionKeyEvent(WebKit::WebInputEvent::KeyUp);
    508   }
    509 }
    510 
    511 void GtkIMContextWrapper::HandlePreeditStart() {
    512   // Ignore preedit related signals triggered by CancelComposition() method.
    513   if (suppress_next_commit_)
    514     return;
    515   is_composing_text_ = true;
    516 }
    517 
    518 void GtkIMContextWrapper::HandlePreeditChanged(const gchar* text,
    519                                                PangoAttrList* attrs,
    520                                                int cursor_position) {
    521   // Ignore preedit related signals triggered by CancelComposition() method.
    522   if (suppress_next_commit_)
    523     return;
    524 
    525   // Don't set is_composition_changed_ to false if there is no change, because
    526   // this handler might be called multiple times with the same data.
    527   is_composition_changed_ = true;
    528   composition_.Clear();
    529 
    530   ui::ExtractCompositionTextFromGtkPreedit(text, attrs, cursor_position,
    531                                            &composition_);
    532 
    533   // TODO(suzhe): due to a bug of webkit, we currently can't use selection range
    534   // with composition string. See: https://bugs.webkit.org/show_bug.cgi?id=40805
    535   composition_.selection = ui::Range(cursor_position);
    536 
    537   // In case we are using a buggy input method which doesn't fire
    538   // "preedit_start" signal.
    539   if (composition_.text.length())
    540     is_composing_text_ = true;
    541 
    542   // Nothing needs to do, if it's currently in ProcessKeyEvent()
    543   // handler, which will send preedit text to webkit later.
    544   // Otherwise, we need send it here if it's been changed.
    545   if (!is_in_key_event_handler_ && is_composing_text_ &&
    546       host_view_->GetRenderWidgetHost()) {
    547     // Workaround http://crbug.com/45478 by sending fake key down/up events.
    548     SendFakeCompositionKeyEvent(WebKit::WebInputEvent::RawKeyDown);
    549     // TODO(suzhe): convert both renderer_host and renderer to use
    550     // ui::CompositionText.
    551     const std::vector<WebKit::WebCompositionUnderline>& underlines =
    552         reinterpret_cast<const std::vector<WebKit::WebCompositionUnderline>&>(
    553             composition_.underlines);
    554     RenderWidgetHostImpl::From(
    555         host_view_->GetRenderWidgetHost())->ImeSetComposition(
    556             composition_.text, underlines, composition_.selection.start(),
    557             composition_.selection.end());
    558     SendFakeCompositionKeyEvent(WebKit::WebInputEvent::KeyUp);
    559   }
    560 }
    561 
    562 void GtkIMContextWrapper::HandlePreeditEnd() {
    563   if (composition_.text.length()) {
    564     // The composition session has been finished.
    565     composition_.Clear();
    566     is_composition_changed_ = true;
    567 
    568     // If there is still a preedit text when firing "preedit-end" signal,
    569     // we need inform webkit to clear it.
    570     // It's only necessary when it's not in ProcessKeyEvent ().
    571     if (!is_in_key_event_handler_ && host_view_->GetRenderWidgetHost()) {
    572       RenderWidgetHostImpl::From(
    573           host_view_->GetRenderWidgetHost())->ImeCancelComposition();
    574     }
    575   }
    576 
    577   // Don't set is_composing_text_ to false here, because "preedit_end"
    578   // signal may be fired before "commit" signal.
    579 }
    580 
    581 gboolean GtkIMContextWrapper::HandleRetrieveSurrounding(GtkIMContext* context) {
    582   if (!is_enabled_)
    583     return TRUE;
    584 
    585   std::string text;
    586   size_t cursor_index = 0;
    587 
    588   if (!is_enabled_ || !host_view_->RetrieveSurrounding(&text, &cursor_index)) {
    589     gtk_im_context_set_surrounding(context, "", 0, 0);
    590     return TRUE;
    591   }
    592 
    593   gtk_im_context_set_surrounding(context, text.c_str(), text.length(),
    594       cursor_index);
    595 
    596   return TRUE;
    597 }
    598 
    599 void GtkIMContextWrapper::HandleHostViewRealize(GtkWidget* widget) {
    600   // We should only set im context's client window once, because when setting
    601   // client window.im context may destroy and recreate its internal states and
    602   // objects.
    603   GdkWindow* gdk_window = gtk_widget_get_window(widget);
    604   if (gdk_window) {
    605     gtk_im_context_set_client_window(context_, gdk_window);
    606     gtk_im_context_set_client_window(context_simple_, gdk_window);
    607   }
    608 }
    609 
    610 void GtkIMContextWrapper::HandleHostViewUnrealize() {
    611   gtk_im_context_set_client_window(context_, NULL);
    612   gtk_im_context_set_client_window(context_simple_, NULL);
    613 }
    614 
    615 void GtkIMContextWrapper::SendFakeCompositionKeyEvent(
    616     WebKit::WebInputEvent::Type type) {
    617   NativeWebKeyboardEvent fake_event;
    618   fake_event.windowsKeyCode = kCompositionEventKeyCode;
    619   fake_event.skip_in_browser = true;
    620   fake_event.type = type;
    621   host_view_->ForwardKeyboardEvent(fake_event);
    622 }
    623 
    624 void GtkIMContextWrapper::HandleCommitThunk(
    625     GtkIMContext* context, gchar* text, GtkIMContextWrapper* self) {
    626   self->HandleCommit(UTF8ToUTF16(text));
    627 }
    628 
    629 void GtkIMContextWrapper::HandlePreeditStartThunk(
    630     GtkIMContext* context, GtkIMContextWrapper* self) {
    631   self->HandlePreeditStart();
    632 }
    633 
    634 void GtkIMContextWrapper::HandlePreeditChangedThunk(
    635     GtkIMContext* context, GtkIMContextWrapper* self) {
    636   gchar* text = NULL;
    637   PangoAttrList* attrs = NULL;
    638   gint cursor_position = 0;
    639   gtk_im_context_get_preedit_string(context, &text, &attrs, &cursor_position);
    640   self->HandlePreeditChanged(text, attrs, cursor_position);
    641   g_free(text);
    642   pango_attr_list_unref(attrs);
    643 }
    644 
    645 void GtkIMContextWrapper::HandlePreeditEndThunk(
    646     GtkIMContext* context, GtkIMContextWrapper* self) {
    647   self->HandlePreeditEnd();
    648 }
    649 
    650 gboolean GtkIMContextWrapper::HandleRetrieveSurroundingThunk(
    651     GtkIMContext* context, GtkIMContextWrapper* self) {
    652   return self->HandleRetrieveSurrounding(context);
    653 }
    654 
    655 void GtkIMContextWrapper::HandleHostViewRealizeThunk(
    656     GtkWidget* widget, GtkIMContextWrapper* self) {
    657   self->HandleHostViewRealize(widget);
    658 }
    659 
    660 void GtkIMContextWrapper::HandleHostViewUnrealizeThunk(
    661     GtkWidget* widget, GtkIMContextWrapper* self) {
    662   self->HandleHostViewUnrealize();
    663 }
    664 
    665 }  // namespace content
    666