Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(144)

Unified Diff: views/ime/input_method_gtk.cc

Issue 6688049: New InputMethod api for Views. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Update. Created 9 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: views/ime/input_method_gtk.cc
diff --git a/views/ime/input_method_gtk.cc b/views/ime/input_method_gtk.cc
new file mode 100644
index 0000000000000000000000000000000000000000..6e6d6f14edb74621b48631a5467812d66991f434
--- /dev/null
+++ b/views/ime/input_method_gtk.cc
@@ -0,0 +1,503 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/ime/input_method_gtk.h"
+
+#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+
+#include <algorithm>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "base/third_party/icu/icu_utf.h"
+#include "base/utf_string_conversions.h"
+#include "ui/base/gtk/event_synthesis_gtk.h"
+#include "ui/base/gtk/gtk_im_context_util.h"
+#include "ui/base/keycodes/keyboard_codes.h"
+#include "ui/base/keycodes/keyboard_code_conversion_gtk.h"
+#include "views/events/event.h"
+#include "views/widget/widget.h"
+
+namespace views {
+
+InputMethodGtk::InputMethodGtk(internal::InputMethodDelegate* delegate)
+ : delegate_(delegate),
+ widget_(NULL),
+ focused_view_(NULL),
+ context_(NULL),
+ context_simple_(NULL),
+ widget_realize_id_(0),
+ widget_unrealize_id_(0),
+ widget_focus_in_id_(0),
+ widget_focus_out_id_(0),
+ widget_focused_(false),
+ context_focused_(false),
+ handling_key_event_(false),
+ composing_text_(false),
+ composition_changed_(false),
+ suppress_next_result_(false) {
+ DCHECK(delegate_);
+}
+
+InputMethodGtk::~InputMethodGtk() {
+ if (widget_) {
+ widget_->GetFocusManager()->RemoveFocusChangeListener(this);
+ GtkWidget* native_view = widget_->GetNativeView();
+ if (native_view) {
+ g_signal_handler_disconnect(native_view, widget_realize_id_);
+ g_signal_handler_disconnect(native_view, widget_unrealize_id_);
+ g_signal_handler_disconnect(native_view, widget_focus_in_id_);
+ g_signal_handler_disconnect(native_view, widget_focus_out_id_);
+ }
+ widget_ = NULL;
+ }
+ if (context_) {
+ g_object_unref(context_);
+ context_ = NULL;
+ }
+ if (context_simple_) {
+ g_object_unref(context_simple_);
+ context_simple_ = NULL;
+ }
+}
+
+void InputMethodGtk::set_delegate(internal::InputMethodDelegate* delegate) {
+ DCHECK(delegate_);
+ delegate_ = delegate;
+}
+
+void InputMethodGtk::Init(Widget* widget) {
+ DCHECK(widget);
+ DCHECK(GTK_IS_WIDGET(widget->GetNativeView()));
+ DCHECK(widget->GetFocusManager());
+
+ if (widget_) {
+ NOTREACHED() << "The input method is already initialized.";
+ return;
+ }
+
+ widget_ = widget;
+ widget->GetFocusManager()->AddFocusChangeListener(this);
+
+ widget_realize_id_ =
+ g_signal_connect(widget->GetNativeView(), "realize",
+ G_CALLBACK(OnWidgetRealizeThunk), this);
+ widget_unrealize_id_ =
+ g_signal_connect(widget->GetNativeView(), "unrealize",
+ G_CALLBACK(OnWidgetUnrealizeThunk), this);
+ widget_focus_in_id_ =
+ g_signal_connect(widget->GetNativeView(), "focus-in-event",
+ G_CALLBACK(OnWidgetFocusInThunk), this);
+ widget_focus_out_id_ =
+ g_signal_connect(widget->GetNativeView(), "focus-out-event",
+ G_CALLBACK(OnWidgetFocusOutThunk), this);
+
+ context_ = gtk_im_multicontext_new();
+ context_simple_ = gtk_im_context_simple_new();
+
+ // context_ and context_simple_ share the same callback handlers.
+ // All data come from them are treated equally.
+ // context_ is for full input method support.
+ // context_simple_ is for supporting dead/compose keys when input method is
+ // disabled, eg. in password input box.
+ g_signal_connect(context_, "commit",
+ G_CALLBACK(OnCommitThunk), this);
+ g_signal_connect(context_, "preedit_start",
+ G_CALLBACK(OnPreeditStartThunk), this);
+ g_signal_connect(context_, "preedit_end",
+ G_CALLBACK(OnPreeditEndThunk), this);
+ g_signal_connect(context_, "preedit_changed",
+ G_CALLBACK(OnPreeditChangedThunk), this);
+
+ g_signal_connect(context_simple_, "commit",
+ G_CALLBACK(OnCommitThunk), this);
+ g_signal_connect(context_simple_, "preedit_start",
+ G_CALLBACK(OnPreeditStartThunk), this);
+ g_signal_connect(context_simple_, "preedit_end",
+ G_CALLBACK(OnPreeditEndThunk), this);
+ g_signal_connect(context_simple_, "preedit_changed",
+ G_CALLBACK(OnPreeditChangedThunk), this);
+
+ // Set client window if the widget is already realized.
+ OnWidgetRealize(widget->GetNativeView());
+}
+
+void InputMethodGtk::DispatchKeyEvent(const KeyEvent& key) {
+ suppress_next_result_ = false;
+
+ if (!context_focused_) {
+ DispatchKeyEventPostIME(key);
+ return;
+ }
+
+ handling_key_event_ = true;
+ composition_changed_ = false;
+ result_text_.clear();
+
+ GdkEvent* event = key.native_event();
+ DCHECK(!event || event->type == GDK_KEY_PRESS ||
+ event->type == GDK_KEY_RELEASE);
+
+ // If it's a fake key event, then we need to synthesis a GdkEventKey.
+ bool need_free_event = false;
+ if (!event) {
+ event = SynthesizeGdkEventKey(key);
+ need_free_event = true;
+ }
+
+ gboolean filtered = false;
+ if (GetTextInputType() == ui::TEXT_INPUT_TYPE_TEXT)
+ filtered = gtk_im_context_filter_keypress(context_, &event->key);
+ else
+ filtered = gtk_im_context_filter_keypress(context_simple_, &event->key);
+
+ handling_key_event_ = false;
+
+ View* old_focused_view = focused_view_;
+ if (key.type() == ui::ET_KEY_PRESSED && filtered)
+ ProcessFilteredKeyPressEvent(key);
+
+ if (old_focused_view != focused_view_)
+ return;
+
+ if (HasInputMethodResult())
+ ProcessInputMethodResult(key, filtered);
+
+ if (old_focused_view != focused_view_)
+ return;
+
+ if (key.type() == ui::ET_KEY_PRESSED && !filtered)
+ ProcessUnfilteredKeyPressEvent(key);
+ else if (key.type() == ui::ET_KEY_RELEASED)
+ DispatchKeyEventPostIME(key);
+
+ if (need_free_event)
+ gdk_event_free(event);
+}
+
+void InputMethodGtk::OnTextInputTypeChanged(View* view) {
+ if (IsViewFocused(view))
+ UpdateContextFocusState();
+}
+
+void InputMethodGtk::OnCaretBoundsChanged(View* view) {
+ if (!IsViewFocused(view))
+ return;
+
+ TextInputClient* client = view->GetTextInputClient();
+ if (!client)
+ return;
+
+ gfx::Rect rect = client->GetCaretBounds();
+ gfx::Point origin = rect.origin();
+ gfx::Point end = gfx::Point(rect.right(), rect.bottom());
+
+ View::ConvertPointToWidget(view, &origin);
+ View::ConvertPointToWidget(view, &end);
+
+ GdkRectangle gdk_rect =
+ { origin.x(), origin.y(), end.x() - origin.x(), end.y() - origin.y() };
+
+ // We need to translate the coordinates to the toplevel widget if the view is
+ // inside a child widget.
+ if (view->GetWidget() != widget_) {
+ gboolean result = gtk_widget_translate_coordinates(
+ view->GetWidget()->GetNativeView(), widget_->GetNativeView(),
+ gdk_rect.x, gdk_rect.y, &gdk_rect.x, &gdk_rect.y);
+ DCHECK(result);
+ }
+
+ gtk_im_context_set_cursor_location(context_, &gdk_rect);
+}
+
+void InputMethodGtk::CancelComposition(View* view) {
+ if (IsViewFocused(view))
+ ResetContext();
+}
+
+std::string InputMethodGtk::GetInputLocale() {
+ // Not supported.
+ return std::string("");
+}
+
+base::i18n::TextDirection InputMethodGtk::GetInputTextDirection() {
+ // Not supported.
+ return base::i18n::UNKNOWN_DIRECTION;
+}
+
+bool InputMethodGtk::IsActive() {
+ // We always need to send keyboard events to either |context_| or
+ // |context_simple_|, so just return true here.
+ return true;
+}
+
+void InputMethodGtk::FocusWillChange(View* focused_before, View* focused) {
+ DCHECK_EQ(focused_view_, focused_before);
+ ConfirmComposition();
+ focused_view_ = focused;
+ UpdateContextFocusState();
+}
+
+void InputMethodGtk::ConfirmComposition() {
+ TextInputClient* client = GetTextInputClient();
+ if (client && client->HasComposition())
+ client->ConfirmComposition();
+
+ ResetContext();
+}
+
+void InputMethodGtk::ResetContext() {
+ if (!context_focused_)
+ return;
+
+ DCHECK(widget_focused_);
+ DCHECK(focused_view_);
+ DCHECK(!handling_key_event_);
+
+ // To prevent any text from being committed when resetting the |context_|;
+ handling_key_event_ = true;
+ suppress_next_result_ = true;
+
+ gtk_im_context_reset(context_);
+ gtk_im_context_reset(context_simple_);
+
+ // Some input methods may not honour the reset call. Focusing out/in the
+ // |context_| to make sure it gets reset correctly.
+ gtk_im_context_focus_out(context_);
+ gtk_im_context_focus_in(context_);
+
+ composition_.Clear();
+ result_text_.clear();
+ handling_key_event_ = false;
+ composing_text_ = false;
+ composition_changed_ = false;
+}
+
+void InputMethodGtk::UpdateContextFocusState() {
+ bool old_context_focused = context_focused_;
+ context_focused_ = (GetTextInputClient() != NULL);
+
+ if (old_context_focused && !context_focused_) {
+ gtk_im_context_focus_out(context_);
+ gtk_im_context_focus_out(context_simple_);
+ } else if (!old_context_focused && context_focused_) {
+ gtk_im_context_focus_in(context_);
+ gtk_im_context_focus_in(context_simple_);
+ }
+}
+
+void InputMethodGtk::ProcessFilteredKeyPressEvent(const KeyEvent& key) {
+ if (NeedInsertChar()) {
+ DispatchKeyEventPostIME(key);
+ } else {
+ KeyEvent key(ui::ET_KEY_PRESSED, ui::VKEY_PROCESSKEY, key.flags());
+ DispatchKeyEventPostIME(key);
+ }
+}
+
+void InputMethodGtk::ProcessUnfilteredKeyPressEvent(const KeyEvent& key) {
+ DispatchKeyEventPostIME(key);
+
+ // If a key event was not filtered by |context_| or |context_simple_|, then
+ // it means the key event didn't generate any result text. For some cases,
+ // the key event may still generate a valid character, eg. a control-key
+ // event (ctrl-a, return, tab, etc.). We need to send the character to the
+ // focused text input client by calling TextInputClient::InsertChar().
+ char16 ch = key.GetCharacter();
+ TextInputClient* client = GetTextInputClient();
+ if (ch && client)
+ client->InsertChar(ch, key.flags());
+}
+
+void InputMethodGtk::ProcessInputMethodResult(const KeyEvent& key,
+ bool filtered) {
+ TextInputClient* client = GetTextInputClient();
+ DCHECK(client);
+
+ if (result_text_.length()) {
+ if (filtered && NeedInsertChar()) {
+ for (string16::const_iterator i = result_text_.begin();
+ i != result_text_.end(); ++i) {
+ client->InsertChar(*i, key.flags());
+ }
+ } else {
+ client->InsertText(result_text_);
+ composing_text_ = false;
+ }
+ }
+
+ if (composition_changed_ && GetTextInputType() != ui::TEXT_INPUT_TYPE_NONE) {
+ if (composition_.text.length()) {
+ composing_text_ = true;
+ client->SetComposition(composition_);
+ } else if (result_text_.empty()) {
+ client->ClearComposition();
+ }
+ }
+}
+
+TextInputClient* InputMethodGtk::GetTextInputClient() const {
+ return (widget_focused_ && focused_view_) ?
+ focused_view_->GetTextInputClient() : NULL;
+}
+
+ui::TextInputType InputMethodGtk::GetTextInputType() const {
+ TextInputClient* client = GetTextInputClient();
+ return client ? client->GetTextInputType() : ui::TEXT_INPUT_TYPE_NONE;
+}
+
+bool InputMethodGtk::IsViewFocused(View* view) const {
+ return widget_focused_ && view && focused_view_ == view;
+}
+
+bool InputMethodGtk::NeedInsertChar() const {
+ return GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE ||
+ (!composing_text_ && result_text_.length() == 1);
+}
+
+bool InputMethodGtk::HasInputMethodResult() const {
+ return result_text_.length() || composition_changed_;
+}
+
+void InputMethodGtk::SendFakeProcessKeyEvent(bool pressed) const {
+ KeyEvent key(pressed ? ui::ET_KEY_PRESSED : ui::ET_KEY_RELEASED,
+ ui::VKEY_PROCESSKEY, 0);
+ DispatchKeyEventPostIME(key);
+}
+
+GdkEvent* InputMethodGtk::SynthesizeGdkEventKey(const KeyEvent& key) const {
+ guint keyval =
+ ui::GdkKeyCodeForWindowsKeyCode(key.key_code(), key.IsShiftDown());
+ guint state = 0;
+ if (key.IsShiftDown())
+ state |= GDK_SHIFT_MASK;
+ if (key.IsControlDown())
+ state |= GDK_CONTROL_MASK;
+ if (key.IsAltDown())
+ state |= GDK_MOD1_MASK;
+ if (key.IsCapsLockDown())
+ state |= GDK_LOCK_MASK;
+
+ DCHECK(widget_->GetNativeView()->window);
+ return ui::SynthesizeKeyEvent(widget_->GetNativeView()->window,
+ key.type() == ui::ET_KEY_PRESSED,
+ keyval, state);
+}
+
+void InputMethodGtk::DispatchKeyEventPostIME(const KeyEvent& key) const {
+ if (delegate_)
+ delegate_->DispatchKeyEventPostIME(key);
+}
+
+void InputMethodGtk::OnCommit(GtkIMContext* context, gchar* text) {
+ if (suppress_next_result_) {
+ suppress_next_result_ = false;
+ return;
+ }
+
+ if (!context_focused_)
+ return;
+
+ string16 utf16_text(UTF8ToUTF16(text));
+
+ // Append the text to the buffer, because commit signal might be fired
+ // multiple times when processing a key event.
+ result_text_.append(utf16_text);
+ if (!handling_key_event_) {
+ TextInputClient* client = GetTextInputClient();
+ if (client && client->GetTextInputType() != ui::TEXT_INPUT_TYPE_NONE) {
+ SendFakeProcessKeyEvent(true);
+ client->InsertText(utf16_text);
+ SendFakeProcessKeyEvent(false);
+ }
+ }
+}
+
+void InputMethodGtk::OnPreeditStart(GtkIMContext* context) {
+ if (suppress_next_result_ || !context_focused_)
+ return;
+
+ composing_text_ = true;
+}
+
+void InputMethodGtk::OnPreeditChanged(GtkIMContext* context) {
+ if (suppress_next_result_ || !context_focused_)
+ return;
+
+ gchar* text = NULL;
+ PangoAttrList* attrs = NULL;
+ gint cursor_position = 0;
+ gtk_im_context_get_preedit_string(context, &text, &attrs, &cursor_position);
+
+ ui::ExtractCompositionInfoFromGtkPreedit(text, attrs, cursor_position,
+ &composition_);
+ composition_changed_ = true;
+
+ g_free(text);
+ pango_attr_list_unref(attrs);
+
+ if (composition_.text.length())
+ composing_text_ = true;
+
+ if (!handling_key_event_) {
+ TextInputClient* client = GetTextInputClient();
+ if (client && client->GetTextInputType() != ui::TEXT_INPUT_TYPE_NONE) {
+ SendFakeProcessKeyEvent(true);
+ client->SetComposition(composition_);
+ SendFakeProcessKeyEvent(false);
+ }
+ }
+}
+
+void InputMethodGtk::OnPreeditEnd(GtkIMContext* context) {
+ if (!context_focused_ || composition_.text.empty())
+ return;
+
+ composition_changed_ = true;
+ composition_.Clear();
+
+ if (!handling_key_event_) {
+ TextInputClient* client = GetTextInputClient();
+ if (client && client->HasComposition())
+ client->ClearComposition();
+ }
+}
+
+void InputMethodGtk::OnWidgetRealize(GtkWidget* widget) {
+ // We should only set im context's client window once, because when setting
+ // client window, im context may destroy and recreate its internal states and
+ // objects.
+ if (widget->window) {
+ gtk_im_context_set_client_window(context_, widget->window);
+ gtk_im_context_set_client_window(context_simple_, widget->window);
+ }
+}
+
+void InputMethodGtk::OnWidgetUnrealize(GtkWidget* widget) {
+ gtk_im_context_set_client_window(context_, NULL);
+ gtk_im_context_set_client_window(context_simple_, NULL);
+}
+
+gboolean InputMethodGtk::OnWidgetFocusIn(GtkWidget* widget,
+ GdkEventFocus* event) {
+ if (!widget_focused_) {
+ widget_focused_ = true;
+ UpdateContextFocusState();
+ }
+ return false;
+}
+
+gboolean InputMethodGtk::OnWidgetFocusOut(GtkWidget* widget,
+ GdkEventFocus* event) {
+ if (widget_focused_) {
+ ConfirmComposition();
+ widget_focused_ = false;
+ UpdateContextFocusState();
+ }
+ return false;
+}
+
+} // namespace views

Powered by Google App Engine
This is Rietveld 408576698