This source file includes following definitions.
- last_key_filtered_no_result_
- ProcessKeyEvent
- UpdateInputMethodState
- UpdateCaretBounds
- OnFocusIn
- OnFocusOut
- BuildInputMethodsGtkMenu
- CancelComposition
- NeedCommitByForwardingCharEvent
- HasInputMethodResult
- ProcessFilteredKeyPressEvent
- ProcessUnfilteredKeyPressEvent
- ProcessInputMethodResult
- ConfirmComposition
- HandleCommit
- HandlePreeditStart
- HandlePreeditChanged
- HandlePreeditEnd
- HandleRetrieveSurrounding
- HandleHostViewRealize
- HandleHostViewUnrealize
- SendFakeCompositionKeyEvent
- HandleCommitThunk
- HandlePreeditStartThunk
- HandlePreeditChangedThunk
- HandlePreeditEndThunk
- HandleRetrieveSurroundingThunk
- HandleHostViewRealizeThunk
- HandleHostViewUnrealizeThunk
#include "content/browser/renderer_host/gtk_im_context_wrapper.h"
#include <gdk/gdk.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>
#include <algorithm>
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_view_gtk.h"
#include "content/public/browser/native_web_keyboard_event.h"
#include "third_party/WebKit/public/web/WebCompositionUnderline.h"
#include "ui/base/ime/composition_text_util_pango.h"
#include "ui/gfx/gtk_util.h"
#include "ui/gfx/rect.h"
namespace content {
namespace {
const int kCompositionEventKeyCode = 229;
}
COMPILE_ASSERT(sizeof(ui::CompositionUnderline) ==
sizeof(blink::WebCompositionUnderline),
ui_CompositionUnderline__WebKit_WebCompositionUnderline_diff);
GtkIMContextWrapper::GtkIMContextWrapper(RenderWidgetHostViewGtk* host_view)
: host_view_(host_view),
context_(gtk_im_multicontext_new()),
context_simple_(gtk_im_context_simple_new()),
is_focused_(false),
is_composing_text_(false),
is_enabled_(false),
is_in_key_event_handler_(false),
is_composition_changed_(false),
suppress_next_commit_(false),
last_key_code_(0),
last_key_was_up_(false),
last_key_filtered_no_result_(false) {
DCHECK(context_);
DCHECK(context_simple_);
g_signal_connect(context_, "preedit-start",
G_CALLBACK(HandlePreeditStartThunk), this);
g_signal_connect(context_, "preedit-end",
G_CALLBACK(HandlePreeditEndThunk), this);
g_signal_connect(context_, "preedit-changed",
G_CALLBACK(HandlePreeditChangedThunk), this);
g_signal_connect(context_, "commit",
G_CALLBACK(HandleCommitThunk), this);
g_signal_connect(context_, "retrieve-surrounding",
G_CALLBACK(HandleRetrieveSurroundingThunk), this);
g_signal_connect(context_simple_, "preedit-start",
G_CALLBACK(HandlePreeditStartThunk), this);
g_signal_connect(context_simple_, "preedit-end",
G_CALLBACK(HandlePreeditEndThunk), this);
g_signal_connect(context_simple_, "preedit-changed",
G_CALLBACK(HandlePreeditChangedThunk), this);
g_signal_connect(context_simple_, "commit",
G_CALLBACK(HandleCommitThunk), this);
g_signal_connect(context_simple_, "retrieve-surrounding",
G_CALLBACK(HandleRetrieveSurroundingThunk), this);
GtkWidget* widget = host_view->GetNativeView();
DCHECK(widget);
g_signal_connect(widget, "realize",
G_CALLBACK(HandleHostViewRealizeThunk), this);
g_signal_connect(widget, "unrealize",
G_CALLBACK(HandleHostViewUnrealizeThunk), this);
HandleHostViewRealize(widget);
}
GtkIMContextWrapper::~GtkIMContextWrapper() {
if (context_)
g_object_unref(context_);
if (context_simple_)
g_object_unref(context_simple_);
}
void GtkIMContextWrapper::ProcessKeyEvent(GdkEventKey* event) {
suppress_next_commit_ = false;
is_in_key_event_handler_ = true;
is_composition_changed_ = false;
commit_text_.clear();
gboolean filtered = false;
if (is_enabled_) {
filtered = gtk_im_context_filter_keypress(context_, event);
} else {
filtered = gtk_im_context_filter_keypress(context_simple_, event);
}
is_in_key_event_handler_ = false;
NativeWebKeyboardEvent wke(reinterpret_cast<GdkEvent*>(event));
if (filtered && event->type == GDK_KEY_PRESS)
wke.skip_in_browser = true;
const int key_code = wke.windowsKeyCode;
const bool has_result = HasInputMethodResult();
if (event->type == GDK_KEY_PRESS && filtered && has_result)
ProcessFilteredKeyPressEvent(&wke);
if (has_result)
ProcessInputMethodResult(event, filtered);
if (event->type == GDK_KEY_PRESS && !filtered) {
ProcessUnfilteredKeyPressEvent(&wke);
} else if (event->type == GDK_KEY_RELEASE) {
const bool suppress = (last_key_code_ == key_code) &&
(last_key_was_up_ || last_key_filtered_no_result_);
if (!suppress)
host_view_->ForwardKeyboardEvent(wke);
}
last_key_code_ = key_code;
last_key_was_up_ = (event->type == GDK_KEY_RELEASE);
last_key_filtered_no_result_ = (filtered && !has_result);
}
void GtkIMContextWrapper::UpdateInputMethodState(
ui::TextInputType type,
bool can_compose_inline) {
suppress_next_commit_ = false;
if (!context_)
return;
DCHECK(!is_in_key_event_handler_);
bool is_enabled = (type != ui::TEXT_INPUT_TYPE_NONE &&
type != ui::TEXT_INPUT_TYPE_PASSWORD);
if (is_enabled_ != is_enabled) {
is_enabled_ = is_enabled;
if (is_enabled && is_focused_)
gtk_im_context_focus_in(context_);
else
gtk_im_context_focus_out(context_);
}
if (is_enabled) {
gtk_im_context_set_use_preedit(context_, can_compose_inline);
}
}
void GtkIMContextWrapper::UpdateCaretBounds(
const gfx::Rect& caret_bounds) {
if (is_enabled_) {
GdkRectangle cursor_rect(caret_bounds.ToGdkRectangle());
gtk_im_context_set_cursor_location(context_, &cursor_rect);
}
}
void GtkIMContextWrapper::OnFocusIn() {
if (is_focused_)
return;
is_focused_ = true;
last_key_code_ = 0;
last_key_was_up_ = false;
last_key_filtered_no_result_ = false;
if (is_enabled_)
gtk_im_context_focus_in(context_);
gtk_im_context_focus_in(context_simple_);
if (host_view_->GetRenderWidgetHost()) {
RenderWidgetHostImpl::From(
host_view_->GetRenderWidgetHost())->SetInputMethodActive(true);
}
}
void GtkIMContextWrapper::OnFocusOut() {
if (!is_focused_)
return;
is_focused_ = false;
if (is_enabled_) {
ConfirmComposition();
gtk_im_context_focus_out(context_);
}
gtk_im_context_reset(context_simple_);
gtk_im_context_focus_out(context_simple_);
is_composing_text_ = false;
if (host_view_->GetRenderWidgetHost()) {
RenderWidgetHostImpl::From(
host_view_->GetRenderWidgetHost())->SetInputMethodActive(false);
}
}
GtkWidget* GtkIMContextWrapper::BuildInputMethodsGtkMenu() {
GtkWidget* submenu = gtk_menu_new();
gtk_im_multicontext_append_menuitems(GTK_IM_MULTICONTEXT(context_),
GTK_MENU_SHELL(submenu));
return submenu;
}
void GtkIMContextWrapper::CancelComposition() {
if (!is_enabled_)
return;
DCHECK(!is_in_key_event_handler_);
is_in_key_event_handler_ = true;
suppress_next_commit_ = true;
gtk_im_context_reset(context_);
gtk_im_context_reset(context_simple_);
if (is_focused_) {
gtk_im_context_focus_out(context_);
gtk_im_context_focus_in(context_);
}
is_composing_text_ = false;
composition_.Clear();
commit_text_.clear();
is_in_key_event_handler_ = false;
}
bool GtkIMContextWrapper::NeedCommitByForwardingCharEvent() const {
return !is_composing_text_ && commit_text_.length() == 1;
}
bool GtkIMContextWrapper::HasInputMethodResult() const {
return commit_text_.length() || is_composition_changed_;
}
void GtkIMContextWrapper::ProcessFilteredKeyPressEvent(
NativeWebKeyboardEvent* wke) {
if (!NeedCommitByForwardingCharEvent()) {
wke->windowsKeyCode = kCompositionEventKeyCode;
wke->setKeyIdentifierFromWindowsKeyCode();
}
host_view_->ForwardKeyboardEvent(*wke);
}
void GtkIMContextWrapper::ProcessUnfilteredKeyPressEvent(
NativeWebKeyboardEvent* wke) {
host_view_->ForwardKeyboardEvent(*wke);
if (wke->text[0]) {
wke->type = blink::WebInputEvent::Char;
wke->skip_in_browser = true;
host_view_->ForwardKeyboardEvent(*wke);
}
}
void GtkIMContextWrapper::ProcessInputMethodResult(const GdkEventKey* event,
bool filtered) {
RenderWidgetHostImpl* host = RenderWidgetHostImpl::From(
host_view_->GetRenderWidgetHost());
if (!host)
return;
bool committed = false;
if (commit_text_.length()) {
if (filtered && NeedCommitByForwardingCharEvent()) {
NativeWebKeyboardEvent char_event(commit_text_[0],
event->state,
base::Time::Now().ToDoubleT());
char_event.skip_in_browser = true;
host_view_->ForwardKeyboardEvent(char_event);
} else {
committed = true;
host->ImeConfirmComposition(
commit_text_,gfx::Range::InvalidRange(),false);
is_composing_text_ = false;
}
}
if (is_composition_changed_) {
if (composition_.text.length()) {
is_composing_text_ = true;
const std::vector<blink::WebCompositionUnderline>& underlines =
reinterpret_cast<const std::vector<blink::WebCompositionUnderline>&>(
composition_.underlines);
host->ImeSetComposition(composition_.text, underlines,
composition_.selection.start(),
composition_.selection.end());
} else if (!committed) {
host->ImeCancelComposition();
}
}
}
void GtkIMContextWrapper::ConfirmComposition() {
if (!is_enabled_)
return;
DCHECK(!is_in_key_event_handler_);
if (is_composing_text_) {
if (host_view_->GetRenderWidgetHost()) {
RenderWidgetHostImpl::From(
host_view_->GetRenderWidgetHost())->ImeConfirmComposition(
base::string16(), gfx::Range::InvalidRange(), false);
}
CancelComposition();
}
}
void GtkIMContextWrapper::HandleCommit(const base::string16& text) {
if (suppress_next_commit_)
return;
commit_text_.append(text);
if (!is_in_key_event_handler_ && host_view_->GetRenderWidgetHost()) {
SendFakeCompositionKeyEvent(blink::WebInputEvent::RawKeyDown);
RenderWidgetHostImpl::From(
host_view_->GetRenderWidgetHost())->ImeConfirmComposition(
text, gfx::Range::InvalidRange(), false);
SendFakeCompositionKeyEvent(blink::WebInputEvent::KeyUp);
}
}
void GtkIMContextWrapper::HandlePreeditStart() {
if (suppress_next_commit_)
return;
is_composing_text_ = true;
}
void GtkIMContextWrapper::HandlePreeditChanged(const gchar* text,
PangoAttrList* attrs,
int cursor_position) {
if (suppress_next_commit_)
return;
is_composition_changed_ = true;
composition_.Clear();
ui::ExtractCompositionTextFromGtkPreedit(text, attrs, cursor_position,
&composition_);
composition_.selection = gfx::Range(cursor_position);
if (composition_.text.length())
is_composing_text_ = true;
if (!is_in_key_event_handler_ && is_composing_text_ &&
host_view_->GetRenderWidgetHost()) {
SendFakeCompositionKeyEvent(blink::WebInputEvent::RawKeyDown);
const std::vector<blink::WebCompositionUnderline>& underlines =
reinterpret_cast<const std::vector<blink::WebCompositionUnderline>&>(
composition_.underlines);
RenderWidgetHostImpl::From(
host_view_->GetRenderWidgetHost())->ImeSetComposition(
composition_.text, underlines, composition_.selection.start(),
composition_.selection.end());
SendFakeCompositionKeyEvent(blink::WebInputEvent::KeyUp);
}
}
void GtkIMContextWrapper::HandlePreeditEnd() {
if (composition_.text.length()) {
composition_.Clear();
is_composition_changed_ = true;
if (!is_in_key_event_handler_ && host_view_->GetRenderWidgetHost()) {
RenderWidgetHostImpl::From(
host_view_->GetRenderWidgetHost())->ImeCancelComposition();
}
}
}
gboolean GtkIMContextWrapper::HandleRetrieveSurrounding(GtkIMContext* context) {
if (!is_enabled_)
return TRUE;
std::string text;
size_t cursor_index = 0;
if (!is_enabled_ || !host_view_->RetrieveSurrounding(&text, &cursor_index)) {
gtk_im_context_set_surrounding(context, "", 0, 0);
return TRUE;
}
gtk_im_context_set_surrounding(context, text.c_str(), text.length(),
cursor_index);
return TRUE;
}
void GtkIMContextWrapper::HandleHostViewRealize(GtkWidget* widget) {
GdkWindow* gdk_window = gtk_widget_get_window(widget);
if (gdk_window) {
gtk_im_context_set_client_window(context_, gdk_window);
gtk_im_context_set_client_window(context_simple_, gdk_window);
}
}
void GtkIMContextWrapper::HandleHostViewUnrealize() {
gtk_im_context_set_client_window(context_, NULL);
gtk_im_context_set_client_window(context_simple_, NULL);
}
void GtkIMContextWrapper::SendFakeCompositionKeyEvent(
blink::WebInputEvent::Type type) {
NativeWebKeyboardEvent fake_event;
fake_event.windowsKeyCode = kCompositionEventKeyCode;
fake_event.skip_in_browser = true;
fake_event.type = type;
host_view_->ForwardKeyboardEvent(fake_event);
}
void GtkIMContextWrapper::HandleCommitThunk(
GtkIMContext* context, gchar* text, GtkIMContextWrapper* self) {
self->HandleCommit(base::UTF8ToUTF16(text));
}
void GtkIMContextWrapper::HandlePreeditStartThunk(
GtkIMContext* context, GtkIMContextWrapper* self) {
self->HandlePreeditStart();
}
void GtkIMContextWrapper::HandlePreeditChangedThunk(
GtkIMContext* context, GtkIMContextWrapper* self) {
gchar* text = NULL;
PangoAttrList* attrs = NULL;
gint cursor_position = 0;
gtk_im_context_get_preedit_string(context, &text, &attrs, &cursor_position);
self->HandlePreeditChanged(text, attrs, cursor_position);
g_free(text);
pango_attr_list_unref(attrs);
}
void GtkIMContextWrapper::HandlePreeditEndThunk(
GtkIMContext* context, GtkIMContextWrapper* self) {
self->HandlePreeditEnd();
}
gboolean GtkIMContextWrapper::HandleRetrieveSurroundingThunk(
GtkIMContext* context, GtkIMContextWrapper* self) {
return self->HandleRetrieveSurrounding(context);
}
void GtkIMContextWrapper::HandleHostViewRealizeThunk(
GtkWidget* widget, GtkIMContextWrapper* self) {
self->HandleHostViewRealize(widget);
}
void GtkIMContextWrapper::HandleHostViewUnrealizeThunk(
GtkWidget* widget, GtkIMContextWrapper* self) {
self->HandleHostViewUnrealize();
}
}