summaryrefslogtreecommitdiffstats
path: root/ui
diff options
context:
space:
mode:
authortfarina@chromium.org <tfarina@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-11-17 00:46:42 +0000
committertfarina@chromium.org <tfarina@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-11-17 00:46:42 +0000
commitd9646d2805844990e1c9d79d69c06fd502fb5783 (patch)
tree5086f93061d3a3dc0dbe1472410b767ba38249b9 /ui
parent97c29575ab1cc3b1bcb53449c3d9627ee1382d04 (diff)
downloadchromium_src-d9646d2805844990e1c9d79d69c06fd502fb5783.zip
chromium_src-d9646d2805844990e1c9d79d69c06fd502fb5783.tar.gz
chromium_src-d9646d2805844990e1c9d79d69c06fd502fb5783.tar.bz2
views: Move ime and test directories to ui/views.
BUG=104039 R=ben@chromium.org Review URL: http://codereview.chromium.org/8581003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@110399 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ui')
-rw-r--r--ui/views/examples/examples_main.cc2
-rw-r--r--ui/views/examples/native_widget_views_example.cc2
-rw-r--r--ui/views/ime/OWNERS2
-rw-r--r--ui/views/ime/input_method.h116
-rw-r--r--ui/views/ime/input_method_base.cc131
-rw-r--r--ui/views/ime/input_method_base.h104
-rw-r--r--ui/views/ime/input_method_delegate.h31
-rw-r--r--ui/views/ime/input_method_gtk.cc451
-rw-r--r--ui/views/ime/input_method_gtk.h123
-rw-r--r--ui/views/ime/input_method_ibus.cc961
-rw-r--r--ui/views/ime/input_method_ibus.h199
-rw-r--r--ui/views/ime/input_method_wayland.cc76
-rw-r--r--ui/views/ime/input_method_wayland.h36
-rw-r--r--ui/views/ime/input_method_win.cc431
-rw-r--r--ui/views/ime/input_method_win.h105
-rw-r--r--ui/views/ime/mock_input_method.cc173
-rw-r--r--ui/views/ime/mock_input_method.h88
-rw-r--r--ui/views/ime/text_input_type_tracker.cc37
-rw-r--r--ui/views/ime/text_input_type_tracker.h60
-rw-r--r--ui/views/test/test_views_delegate.cc51
-rw-r--r--ui/views/test/test_views_delegate.h72
-rw-r--r--ui/views/test/views_test_base.cc64
-rw-r--r--ui/views/test/views_test_base.h49
-rw-r--r--ui/views/touchui/touch_selection_controller_impl_unittest.cc2
24 files changed, 3363 insertions, 3 deletions
diff --git a/ui/views/examples/examples_main.cc b/ui/views/examples/examples_main.cc
index 84a0b14..6718d78 100644
--- a/ui/views/examples/examples_main.cc
+++ b/ui/views/examples/examples_main.cc
@@ -32,12 +32,12 @@
#include "ui/views/examples/textfield_example.h"
#include "ui/views/examples/throbber_example.h"
#include "ui/views/examples/widget_example.h"
+#include "ui/views/test/test_views_delegate.h"
#include "views/controls/button/text_button.h"
#include "views/controls/label.h"
#include "views/controls/tabbed_pane/tabbed_pane.h"
#include "views/focus/accelerator_handler.h"
#include "views/layout/grid_layout.h"
-#include "views/test/test_views_delegate.h"
#include "views/widget/widget.h"
#if defined(OS_WIN)
diff --git a/ui/views/examples/native_widget_views_example.cc b/ui/views/examples/native_widget_views_example.cc
index 0e00035..72cdab7 100644
--- a/ui/views/examples/native_widget_views_example.cc
+++ b/ui/views/examples/native_widget_views_example.cc
@@ -7,8 +7,8 @@
#include "base/utf_string_conversions.h"
#include "ui/gfx/canvas.h"
#include "ui/views/examples/example_base.h"
+#include "ui/views/test/test_views_delegate.h"
#include "views/controls/button/text_button.h"
-#include "views/test/test_views_delegate.h"
#include "views/view.h"
#include "views/widget/native_widget_views.h"
#include "views/widget/widget.h"
diff --git a/ui/views/ime/OWNERS b/ui/views/ime/OWNERS
new file mode 100644
index 0000000..d76ee7e
--- /dev/null
+++ b/ui/views/ime/OWNERS
@@ -0,0 +1,2 @@
+yusukes@chromium.org
+penghuang@chromium.org
diff --git a/ui/views/ime/input_method.h b/ui/views/ime/input_method.h
new file mode 100644
index 0000000..178b6db
--- /dev/null
+++ b/ui/views/ime/input_method.h
@@ -0,0 +1,116 @@
+// 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.
+
+#ifndef UI_VIEWS_IME_INPUT_METHOD_H_
+#define UI_VIEWS_IME_INPUT_METHOD_H_
+#pragma once
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/i18n/rtl.h"
+#include "ui/base/ime/text_input_type.h"
+#include "views/views_export.h"
+
+namespace ui {
+class TextInputClient;
+} // namespace ui
+
+namespace views {
+
+namespace internal {
+class InputMethodDelegate;
+} // namespace internal
+
+class KeyEvent;
+class View;
+class Widget;
+
+// An interface implemented by an object that encapsulates a native input method
+// service provided by the underlying operation system.
+// Because on most systems, the system input method service is bound to
+// individual native window. On Windows, its HWND, on Linux/Gtk, its GdkWindow.
+// And in Views control system, only the top-level NativeWidget has a native
+// window that can get keyboard focus. So this API is designed to be bound to
+// the top-level NativeWidget.
+class VIEWS_EXPORT InputMethod {
+ public:
+ virtual ~InputMethod() {}
+
+ // Sets the delegate used by this InputMethod instance. It should only be
+ // called by the internal NativeWidget or testing code.
+ virtual void set_delegate(internal::InputMethodDelegate* delegate) = 0;
+
+ // Initialize the InputMethod object and attach it to the given |widget|.
+ // The |widget| must already be initialized.
+ virtual void Init(Widget* widget) = 0;
+
+ // Called when the top-level NativeWidget gets keyboard focus. It should only
+ // be called by the top-level NativeWidget which owns this InputMethod
+ // instance.
+ virtual void OnFocus() = 0;
+
+ // Called when the top-level NativeWidget loses keyboard focus. It should only
+ // be called by the top-level NativeWidget which owns this InputMethod
+ // instance.
+ virtual void OnBlur() = 0;
+
+ // Dispatch a key event to the input method. The key event will be dispatched
+ // back to the caller via InputMethodDelegate::DispatchKeyEventPostIME(), once
+ // it's processed by the input method. It should only be called by the
+ // top-level NativeWidget which owns this InputMethod instance, or other
+ // related platform dependent code, such as a message dispatcher.
+ virtual void DispatchKeyEvent(const KeyEvent& key) = 0;
+
+ // Called by the focused |view| whenever its text input type is changed.
+ // Before calling this method, the focused |view| must confirm or clear
+ // existing composition text and call InputMethod::CancelComposition() when
+ // necessary. Otherwise unexpected behavior may happen. This method has no
+ // effect if the |view| is not focused.
+ virtual void OnTextInputTypeChanged(View* view) = 0;
+
+ // Called by the focused |view| whenever its caret bounds is changed.
+ // This method has no effect if the |view| is not focused.
+ virtual void OnCaretBoundsChanged(View* view) = 0;
+
+ // Called by the focused |view| to ask the input method cancel the ongoing
+ // composition session. This method has no effect if the |view| is not
+ // focused.
+ virtual void CancelComposition(View* view) = 0;
+
+ // Returns the locale of current keyboard layout or input method, as a BCP-47
+ // tag, or an empty string if the input method cannot provide it.
+ virtual std::string GetInputLocale() = 0;
+
+ // Returns the text direction of current keyboard layout or input method, or
+ // base::i18n::UNKNOWN_DIRECTION if the input method cannot provide it.
+ virtual base::i18n::TextDirection GetInputTextDirection() = 0;
+
+ // Checks if the input method is active, i.e. if it's ready for processing
+ // keyboard event and generate composition or text result.
+ // If the input method is inactive, then it's not necessary to inform it the
+ // changes of caret bounds and text input type.
+ // Note: character results may still be generated and sent to the text input
+ // client by calling TextInputClient::InsertChar(), even if the input method
+ // is not active.
+ virtual bool IsActive() = 0;
+
+ // Gets the focused text input client. Returns NULL if the Widget is not
+ // focused, or there is no focused View or the focused View doesn't support
+ // text input.
+ virtual ui::TextInputClient* GetTextInputClient() const = 0;
+
+ // Gets the text input type of the focused text input client. Returns
+ // ui::TEXT_INPUT_TYPE_NONE if there is no focused text input client.
+ virtual ui::TextInputType GetTextInputType() const = 0;
+
+ // Returns true if the input method is a mock and not real.
+ virtual bool IsMock() const = 0;
+
+ // TODO(suzhe): Support mouse/touch event.
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_IME_INPUT_METHOD_H_
diff --git a/ui/views/ime/input_method_base.cc b/ui/views/ime/input_method_base.cc
new file mode 100644
index 0000000..4b53ff5
--- /dev/null
+++ b/ui/views/ime/input_method_base.cc
@@ -0,0 +1,131 @@
+// 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 "ui/base/ime/text_input_client.h"
+#include "ui/views/ime/input_method_base.h"
+#include "ui/views/ime/text_input_type_tracker.h"
+#include "views/view.h"
+#include "views/widget/widget.h"
+
+#include "base/logging.h"
+
+namespace views {
+
+InputMethodBase::InputMethodBase()
+ : delegate_(NULL),
+ widget_(NULL),
+ widget_focused_(false) {
+}
+
+InputMethodBase::~InputMethodBase() {
+ if (widget_) {
+ widget_->GetFocusManager()->RemoveFocusChangeListener(this);
+ widget_ = NULL;
+ }
+}
+
+void InputMethodBase::set_delegate(internal::InputMethodDelegate* delegate) {
+ DCHECK(delegate);
+ delegate_ = delegate;
+}
+
+void InputMethodBase::Init(Widget* widget) {
+ DCHECK(widget);
+ DCHECK(widget->GetFocusManager());
+
+ if (widget_) {
+ NOTREACHED() << "The input method is already initialized.";
+ return;
+ }
+
+ widget_ = widget;
+ // The InputMethod is lazily created, so we need to tell it the currently
+ // focused view.
+ View* focused = widget->GetFocusManager()->GetFocusedView();
+ if (focused)
+ OnWillChangeFocus(NULL, focused);
+ widget->GetFocusManager()->AddFocusChangeListener(this);
+}
+
+void InputMethodBase::OnFocus() {
+ widget_focused_ = true;
+ TextInputTypeTracker::GetInstance()->OnTextInputTypeChanged(
+ GetTextInputType(), widget_);
+}
+
+void InputMethodBase::OnBlur() {
+ widget_focused_ = false;
+ TextInputTypeTracker::GetInstance()->OnTextInputTypeChanged(
+ GetTextInputType(), widget_);
+}
+
+views::View* InputMethodBase::GetFocusedView() const {
+ return widget_->GetFocusManager()->GetFocusedView();
+}
+
+void InputMethodBase::OnTextInputTypeChanged(View* view) {
+ if (IsViewFocused(view)) {
+ TextInputTypeTracker::GetInstance()->OnTextInputTypeChanged(
+ GetTextInputType(), widget_);
+ }
+}
+
+ui::TextInputClient* InputMethodBase::GetTextInputClient() const {
+ return (widget_focused_ && GetFocusedView()) ?
+ GetFocusedView()->GetTextInputClient() : NULL;
+}
+
+ui::TextInputType InputMethodBase::GetTextInputType() const {
+ ui::TextInputClient* client = GetTextInputClient();
+ return client ? client->GetTextInputType() : ui::TEXT_INPUT_TYPE_NONE;
+}
+
+bool InputMethodBase::IsMock() const {
+ return false;
+}
+
+void InputMethodBase::OnWillChangeFocus(View* focused_before, View* focused) {
+}
+
+void InputMethodBase::OnDidChangeFocus(View* focused_before, View* focused) {
+ if (widget_focused_) {
+ TextInputTypeTracker::GetInstance()->OnTextInputTypeChanged(
+ GetTextInputType(), widget_);
+ }
+}
+
+bool InputMethodBase::IsViewFocused(View* view) const {
+ return widget_focused_ && view && GetFocusedView() == view;
+}
+
+bool InputMethodBase::IsTextInputTypeNone() const {
+ return GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE;
+}
+
+void InputMethodBase::OnInputMethodChanged() const {
+ ui::TextInputClient* client = GetTextInputClient();
+ if (client && client->GetTextInputType() != ui::TEXT_INPUT_TYPE_NONE)
+ client->OnInputMethodChanged();
+}
+
+void InputMethodBase::DispatchKeyEventPostIME(const KeyEvent& key) const {
+ if (delegate_)
+ delegate_->DispatchKeyEventPostIME(key);
+}
+
+bool InputMethodBase::GetCaretBoundsInWidget(gfx::Rect* rect) const {
+ DCHECK(rect);
+ ui::TextInputClient* client = GetTextInputClient();
+ if (!client || client->GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE)
+ return false;
+
+ *rect = GetFocusedView()->ConvertRectToWidget(client->GetCaretBounds());
+
+ // We need to do coordinate conversion if the focused view is inside a child
+ // Widget.
+ if (GetFocusedView()->GetWidget() != widget_)
+ return Widget::ConvertRect(GetFocusedView()->GetWidget(), widget_, rect);
+ return true;
+}
+} // namespace views
diff --git a/ui/views/ime/input_method_base.h b/ui/views/ime/input_method_base.h
new file mode 100644
index 0000000..cdecbda
--- /dev/null
+++ b/ui/views/ime/input_method_base.h
@@ -0,0 +1,104 @@
+// 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.
+
+#ifndef UI_VIEWS_IME_INPUT_METHOD_BASE_H_
+#define UI_VIEWS_IME_INPUT_METHOD_BASE_H_
+#pragma once
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ui/views/ime/input_method.h"
+#include "ui/views/ime/input_method_delegate.h"
+#include "views/focus/focus_manager.h"
+
+namespace gfx {
+class Rect;
+}
+
+namespace ui {
+class TextInputClient;
+}
+
+namespace views {
+
+class View;
+
+// A helper class providing functionalities shared among InputMethod
+// implementations.
+class VIEWS_EXPORT InputMethodBase : public InputMethod,
+ public FocusChangeListener {
+ public:
+ InputMethodBase();
+ virtual ~InputMethodBase();
+
+ // Overriden from InputMethod.
+ virtual void set_delegate(internal::InputMethodDelegate* delegate) OVERRIDE;
+
+ // If a derived class overrides this method, it should call parent's
+ // implementation first.
+ virtual void Init(Widget* widget) OVERRIDE;
+
+ // If a derived class overrides this method, it should call parent's
+ // implementation first, to make sure |widget_focused_| flag can be updated
+ // correctly.
+ virtual void OnFocus() OVERRIDE;
+
+ // If a derived class overrides this method, it should call parent's
+ // implementation first, to make sure |widget_focused_| flag can be updated
+ // correctly.
+ virtual void OnBlur() OVERRIDE;
+
+ // If a derived class overrides this method, it should call parent's
+ // implementation.
+ virtual void OnTextInputTypeChanged(View* view) OVERRIDE;
+
+ virtual ui::TextInputClient* GetTextInputClient() const OVERRIDE;
+ virtual ui::TextInputType GetTextInputType() const OVERRIDE;
+ virtual bool IsMock() const OVERRIDE;
+
+ // Overridden from FocusChangeListener.
+ virtual void OnWillChangeFocus(View* focused_before, View* focused) OVERRIDE;
+ virtual void OnDidChangeFocus(View* focused_before, View* focused) OVERRIDE;
+
+ protected:
+ // Getters and setters of properties.
+ internal::InputMethodDelegate* delegate() const { return delegate_; }
+ Widget* widget() const { return widget_; }
+ bool widget_focused() const { return widget_focused_; }
+ View* GetFocusedView() const;
+
+ // Checks if the given View is focused. Returns true only if the View and
+ // the Widget are both focused.
+ bool IsViewFocused(View* view) const;
+
+ // Checks if the focused text input client's text input type is
+ // ui::TEXT_INPUT_TYPE_NONE. Also returns true if there is no focused text
+ // input client.
+ bool IsTextInputTypeNone() const;
+
+ // Convenience method to call the focused text input client's
+ // OnInputMethodChanged() method. It'll only take effect if the current text
+ // input type is not ui::TEXT_INPUT_TYPE_NONE.
+ void OnInputMethodChanged() const;
+
+ // Convenience method to call delegate_->DispatchKeyEventPostIME().
+ void DispatchKeyEventPostIME(const KeyEvent& key) const;
+
+ // Gets the current text input client's caret bounds in Widget's coordinates.
+ // Returns false if the current text input client doesn't support text input.
+ bool GetCaretBoundsInWidget(gfx::Rect* rect) const;
+
+ private:
+ internal::InputMethodDelegate* delegate_;
+ Widget* widget_;
+
+ // Indicates if the top-level widget is focused or not.
+ bool widget_focused_;
+
+ DISALLOW_COPY_AND_ASSIGN(InputMethodBase);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_IME_INPUT_METHOD_BASE_H_
diff --git a/ui/views/ime/input_method_delegate.h b/ui/views/ime/input_method_delegate.h
new file mode 100644
index 0000000..369cc8b
--- /dev/null
+++ b/ui/views/ime/input_method_delegate.h
@@ -0,0 +1,31 @@
+// 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.
+
+#ifndef UI_VIEWS_IME_INPUT_METHOD_DELEGATE_H_
+#define UI_VIEWS_IME_INPUT_METHOD_DELEGATE_H_
+#pragma once
+
+#include "views/views_export.h"
+
+namespace views {
+
+class KeyEvent;
+
+namespace internal {
+
+// An interface implemented by the object that handles events sent back from an
+// InputMethod implementation.
+class VIEWS_EXPORT InputMethodDelegate {
+ public:
+ virtual ~InputMethodDelegate() {}
+
+ // Dispatch a key event already processed by the input method.
+ virtual void DispatchKeyEventPostIME(const KeyEvent& key) = 0;
+
+};
+
+} // namespace internal
+} // namespace views
+
+#endif // UI_VIEWS_IME_INPUT_METHOD_DELEGATE_H_
diff --git a/ui/views/ime/input_method_gtk.cc b/ui/views/ime/input_method_gtk.cc
new file mode 100644
index 0000000..edaa20e
--- /dev/null
+++ b/ui/views/ime/input_method_gtk.cc
@@ -0,0 +1,451 @@
+// 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 "ui/views/ime/input_method_gtk.h"
+
+#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+
+#include <algorithm>
+#include <string>
+
+#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_code_conversion_gtk.h"
+#include "ui/base/keycodes/keyboard_codes.h"
+#include "views/events/event.h"
+#include "views/widget/widget.h"
+
+namespace views {
+
+InputMethodGtk::InputMethodGtk(internal::InputMethodDelegate* delegate)
+ : context_(NULL),
+ context_simple_(NULL),
+ widget_realize_id_(0),
+ widget_unrealize_id_(0),
+ context_focused_(false),
+ handling_key_event_(false),
+ composing_text_(false),
+ composition_changed_(false),
+ suppress_next_result_(false) {
+ set_delegate(delegate);
+}
+
+InputMethodGtk::~InputMethodGtk() {
+ if (widget()) {
+ 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_);
+ }
+ }
+ if (context_) {
+ g_object_unref(context_);
+ context_ = NULL;
+ }
+ if (context_simple_) {
+ g_object_unref(context_simple_);
+ context_simple_ = NULL;
+ }
+}
+
+void InputMethodGtk::Init(Widget* widget) {
+ DCHECK(GTK_IS_WIDGET(widget->GetNativeView()));
+
+ 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);
+
+ 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());
+
+ InputMethodBase::Init(widget);
+}
+
+void InputMethodGtk::OnFocus() {
+ DCHECK(!widget_focused());
+ InputMethodBase::OnFocus();
+ UpdateContextFocusState();
+}
+
+void InputMethodGtk::OnBlur() {
+ DCHECK(widget_focused());
+ ConfirmCompositionText();
+ InputMethodBase::OnBlur();
+ UpdateContextFocusState();
+}
+
+void InputMethodGtk::DispatchKeyEvent(const KeyEvent& key) {
+ DCHECK(key.type() == ui::ET_KEY_PRESSED || key.type() == ui::ET_KEY_RELEASED);
+ suppress_next_result_ = false;
+
+ // We should bypass |context_| and |context_simple_| only if there is no
+ // text input client focused. Otherwise, always send the key event to either
+ // |context_| or |context_simple_| even if the text input type is
+ // ui::TEXT_INPUT_TYPE_NONE, to make sure we can get correct character result.
+ if (!GetTextInputClient()) {
+ DispatchKeyEventPostIME(key);
+ return;
+ }
+
+ handling_key_event_ = true;
+ composition_changed_ = false;
+ result_text_.clear();
+
+ // If it's a fake key event, then we need to synthesize a GdkEventKey.
+ GdkEvent* event = key.gdk_event() ? key.gdk_event() :
+ SynthesizeGdkEventKey(key);
+ gboolean filtered = gtk_im_context_filter_keypress(
+ context_focused_ ? context_ : context_simple_, &event->key);
+
+ handling_key_event_ = false;
+
+ const View* old_focused_view = GetFocusedView();
+ if (key.type() == ui::ET_KEY_PRESSED && filtered)
+ ProcessFilteredKeyPressEvent(key);
+
+ // Ensure no focus change from processing the key event.
+ if (old_focused_view == GetFocusedView()) {
+ if (HasInputMethodResult())
+ ProcessInputMethodResult(key, filtered);
+ // Ensure no focus change sending input method results to the focused View.
+ if (old_focused_view == GetFocusedView()) {
+ if (key.type() == ui::ET_KEY_PRESSED && !filtered)
+ ProcessUnfilteredKeyPressEvent(key);
+ else if (key.type() == ui::ET_KEY_RELEASED)
+ DispatchKeyEventPostIME(key);
+ }
+ }
+
+ // Free the synthesized event if there was no underlying native event.
+ if (event != key.gdk_event())
+ gdk_event_free(event);
+}
+
+void InputMethodGtk::OnTextInputTypeChanged(View* view) {
+ if (IsViewFocused(view)) {
+ DCHECK(!composing_text_);
+ UpdateContextFocusState();
+ }
+ InputMethodBase::OnTextInputTypeChanged(view);
+}
+
+void InputMethodGtk::OnCaretBoundsChanged(View* view) {
+ gfx::Rect rect;
+ if (!IsViewFocused(view) || !GetCaretBoundsInWidget(&rect))
+ return;
+
+ GdkRectangle gdk_rect = rect.ToGdkRectangle();
+ 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::OnWillChangeFocus(View* focused_before, View* focused) {
+ ConfirmCompositionText();
+}
+
+void InputMethodGtk::OnDidChangeFocus(View* focused_before, View* focused) {
+ UpdateContextFocusState();
+
+ // Force to update caret bounds, in case the View thinks that the caret
+ // bounds has not changed.
+ if (context_focused_)
+ OnCaretBoundsChanged(GetFocusedView());
+}
+
+void InputMethodGtk::ConfirmCompositionText() {
+ ui::TextInputClient* client = GetTextInputClient();
+ if (client && client->HasCompositionText())
+ client->ConfirmCompositionText();
+
+ ResetContext();
+}
+
+void InputMethodGtk::ResetContext() {
+ if (!GetTextInputClient())
+ return;
+
+ DCHECK(widget_focused());
+ DCHECK(GetFocusedView());
+ 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.
+ if (context_focused_) {
+ 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_;
+ // Use switch here in case we are going to add more text input types.
+ switch (GetTextInputType()) {
+ case ui::TEXT_INPUT_TYPE_NONE:
+ case ui::TEXT_INPUT_TYPE_PASSWORD:
+ context_focused_ = false;
+ break;
+ default:
+ context_focused_ = true;
+ break;
+ }
+
+ // We only focus in |context_| when the focus is in a normal textfield.
+ if (old_context_focused && !context_focused_)
+ gtk_im_context_focus_out(context_);
+ else if (!old_context_focused && context_focused_)
+ gtk_im_context_focus_in(context_);
+
+ // |context_simple_| can be used in any textfield, including password box, and
+ // even if the focused text input client's text input type is
+ // ui::TEXT_INPUT_TYPE_NONE.
+ if (GetTextInputClient())
+ gtk_im_context_focus_in(context_simple_);
+ else
+ gtk_im_context_focus_out(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) {
+ const View* old_focused_view = GetFocusedView();
+ DispatchKeyEventPostIME(key);
+
+ // We shouldn't dispatch the character anymore if the key event caused focus
+ // change.
+ if (old_focused_view != GetFocusedView())
+ return;
+
+ // 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();
+ ui::TextInputClient* client = GetTextInputClient();
+ if (ch && client)
+ client->InsertChar(ch, key.flags());
+}
+
+void InputMethodGtk::ProcessInputMethodResult(const KeyEvent& key,
+ bool filtered) {
+ ui::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_ && !IsTextInputTypeNone()) {
+ if (composition_.text.length()) {
+ composing_text_ = true;
+ client->SetCompositionText(composition_);
+ } else if (result_text_.empty()) {
+ client->ClearCompositionText();
+ }
+ }
+}
+
+bool InputMethodGtk::NeedInsertChar() const {
+ return IsTextInputTypeNone() ||
+ (!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;
+ state |= key.IsShiftDown() ? GDK_SHIFT_MASK : 0;
+ state |= key.IsControlDown() ? GDK_CONTROL_MASK : 0;
+ state |= key.IsAltDown() ? GDK_MOD1_MASK : 0;
+ state |= key.IsCapsLockDown() ? GDK_LOCK_MASK : 0;
+
+ DCHECK(widget()->GetNativeView()->window);
+ return ui::SynthesizeKeyEvent(widget()->GetNativeView()->window,
+ key.type() == ui::ET_KEY_PRESSED,
+ keyval, state);
+}
+
+void InputMethodGtk::OnCommit(GtkIMContext* context, gchar* text) {
+ if (suppress_next_result_) {
+ suppress_next_result_ = false;
+ return;
+ }
+
+ // We need to receive input method result even if the text input type is
+ // ui::TEXT_INPUT_TYPE_NONE, to make sure we can always send correct
+ // character for each key event to the focused text input client.
+ if (!GetTextInputClient())
+ 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 we are not handling key event, do not bother sending text result if the
+ // focused text input client does not support text input.
+ if (!handling_key_event_ && !IsTextInputTypeNone()) {
+ SendFakeProcessKeyEvent(true);
+ GetTextInputClient()->InsertText(utf16_text);
+ SendFakeProcessKeyEvent(false);
+ }
+}
+
+void InputMethodGtk::OnPreeditStart(GtkIMContext* context) {
+ if (suppress_next_result_ || IsTextInputTypeNone())
+ return;
+
+ composing_text_ = true;
+}
+
+void InputMethodGtk::OnPreeditChanged(GtkIMContext* context) {
+ if (suppress_next_result_ || IsTextInputTypeNone())
+ return;
+
+ gchar* text = NULL;
+ PangoAttrList* attrs = NULL;
+ gint cursor_position = 0;
+ gtk_im_context_get_preedit_string(context, &text, &attrs, &cursor_position);
+
+ ui::ExtractCompositionTextFromGtkPreedit(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_ && !IsTextInputTypeNone()) {
+ SendFakeProcessKeyEvent(true);
+ GetTextInputClient()->SetCompositionText(composition_);
+ SendFakeProcessKeyEvent(false);
+ }
+}
+
+void InputMethodGtk::OnPreeditEnd(GtkIMContext* context) {
+ if (composition_.text.empty() || IsTextInputTypeNone())
+ return;
+
+ composition_changed_ = true;
+ composition_.Clear();
+
+ if (!handling_key_event_) {
+ ui::TextInputClient* client = GetTextInputClient();
+ if (client && client->HasCompositionText())
+ client->ClearCompositionText();
+ }
+}
+
+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);
+}
+
+} // namespace views
diff --git a/ui/views/ime/input_method_gtk.h b/ui/views/ime/input_method_gtk.h
new file mode 100644
index 0000000..670669f
--- /dev/null
+++ b/ui/views/ime/input_method_gtk.h
@@ -0,0 +1,123 @@
+// 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.
+
+#ifndef UI_VIEWS_IME_INPUT_METHOD_GTK_H_
+#define UI_VIEWS_IME_INPUT_METHOD_GTK_H_
+#pragma once
+
+#include <gtk/gtk.h>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ui/base/gtk/gtk_signal.h"
+#include "ui/base/ime/composition_text.h"
+#include "ui/base/ime/text_input_client.h"
+#include "ui/views/ime/input_method_base.h"
+#include "views/view.h"
+
+namespace views {
+
+// An InputMethod implementation based on GtkIMContext. Most code are copied
+// from chrome/browser/renderer_host/gtk_im_context_wrapper.*
+// It's intended for testing purpose.
+class InputMethodGtk : public InputMethodBase {
+ public:
+ explicit InputMethodGtk(internal::InputMethodDelegate* delegate);
+ virtual ~InputMethodGtk();
+
+ // Overridden from InputMethod:
+ virtual void Init(Widget* widget) OVERRIDE;
+ virtual void OnFocus() OVERRIDE;
+ virtual void OnBlur() OVERRIDE;
+ virtual void DispatchKeyEvent(const KeyEvent& key) OVERRIDE;
+ virtual void OnTextInputTypeChanged(View* view) OVERRIDE;
+ virtual void OnCaretBoundsChanged(View* view) OVERRIDE;
+ virtual void CancelComposition(View* view) OVERRIDE;
+ virtual std::string GetInputLocale() OVERRIDE;
+ virtual base::i18n::TextDirection GetInputTextDirection() OVERRIDE;
+ virtual bool IsActive() OVERRIDE;
+
+ private:
+ // Overridden from InputMethodBase:
+ virtual void OnWillChangeFocus(View* focused_before, View* focused) OVERRIDE;
+ virtual void OnDidChangeFocus(View* focused_before, View* focused) OVERRIDE;
+
+ // Asks the client to confirm current composition text.
+ void ConfirmCompositionText();
+
+ // Resets |context_| and |context_simple_|.
+ void ResetContext();
+
+ // Checks the availability of focused text input client and update focus state
+ // of |context_| and |context_simple_| accordingly.
+ void UpdateContextFocusState();
+
+ // Processes a key event that was already filtered by the input method.
+ // A VKEY_PROCESSKEY may be dispatched to the focused View.
+ void ProcessFilteredKeyPressEvent(const KeyEvent& key);
+
+ // Processes a key event that was not filtered by the input method.
+ void ProcessUnfilteredKeyPressEvent(const KeyEvent& key);
+
+ // Sends input method result caused by the given key event to the focused text
+ // input client.
+ void ProcessInputMethodResult(const KeyEvent& key, bool filtered);
+
+ // Checks if the pending input method result needs inserting into the focused
+ // text input client as a single character.
+ bool NeedInsertChar() const;
+
+ // Checks if there is pending input method result.
+ bool HasInputMethodResult() const;
+
+ // Fabricates a key event with VKEY_PROCESSKEY key code and dispatches it to
+ // the focused View.
+ void SendFakeProcessKeyEvent(bool pressed) const;
+
+ // Synthesize a GdkEventKey based on given key event. The returned GdkEventKey
+ // must be freed with gdk_event_free().
+ GdkEvent* SynthesizeGdkEventKey(const KeyEvent& key) const;
+
+ // Event handlers:
+ CHROMEG_CALLBACK_1(InputMethodGtk, void, OnCommit, GtkIMContext*, gchar*);
+ CHROMEG_CALLBACK_0(InputMethodGtk, void, OnPreeditStart, GtkIMContext*);
+ CHROMEG_CALLBACK_0(InputMethodGtk, void, OnPreeditChanged, GtkIMContext*);
+ CHROMEG_CALLBACK_0(InputMethodGtk, void, OnPreeditEnd, GtkIMContext*);
+
+ CHROMEGTK_CALLBACK_0(InputMethodGtk, void, OnWidgetRealize);
+ CHROMEGTK_CALLBACK_0(InputMethodGtk, void, OnWidgetUnrealize);
+
+ GtkIMContext* context_;
+ GtkIMContext* context_simple_;
+
+ ui::CompositionText composition_;
+
+ string16 result_text_;
+
+ // Signal ids for corresponding gtk signals.
+ gulong widget_realize_id_;
+ gulong widget_unrealize_id_;
+
+ // Indicates if |context_| and |context_simple_| are focused or not.
+ bool context_focused_;
+
+ // Indicates if we are handling a key event.
+ bool handling_key_event_;
+
+ // Indicates if there is an ongoing composition text.
+ bool composing_text_;
+
+ // Indicates if the composition text is changed or deleted.
+ bool composition_changed_;
+
+ // If it's true then all input method result received before the next key
+ // event will be discarded.
+ bool suppress_next_result_;
+
+ DISALLOW_COPY_AND_ASSIGN(InputMethodGtk);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_IME_INPUT_METHOD_GTK_H_
diff --git a/ui/views/ime/input_method_ibus.cc b/ui/views/ime/input_method_ibus.cc
new file mode 100644
index 0000000..9fa9525
--- /dev/null
+++ b/ui/views/ime/input_method_ibus.cc
@@ -0,0 +1,961 @@
+// 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 "ui/views/ime/input_method_ibus.h"
+
+#include <ibus.h>
+#if defined(TOUCH_UI)
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#endif
+
+#include <algorithm>
+#include <cstring>
+#include <set>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/command_line.h"
+#include "base/i18n/char_iterator.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/keycodes/keyboard_codes.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/rect.h"
+#include "views/events/event.h"
+#include "views/widget/widget.h"
+
+#if defined(USE_AURA) || defined(TOUCH_UI)
+#include "ui/base/keycodes/keyboard_code_conversion_x.h"
+#elif defined(TOOLKIT_USES_GTK)
+#include "ui/base/keycodes/keyboard_code_conversion_gtk.h"
+#endif
+
+namespace {
+
+// A global flag to switch the InputMethod implementation to InputMethodIBus
+bool inputmethod_ibus_enabled = false;
+
+// Converts ibus key state flags to event flags.
+int EventFlagsFromIBusState(guint32 state) {
+ return (state & IBUS_LOCK_MASK ? ui::EF_CAPS_LOCK_DOWN : 0) |
+ (state & IBUS_CONTROL_MASK ? ui::EF_CONTROL_DOWN : 0) |
+ (state & IBUS_SHIFT_MASK ? ui::EF_SHIFT_DOWN : 0) |
+ (state & IBUS_MOD1_MASK ? ui::EF_ALT_DOWN : 0) |
+ (state & IBUS_BUTTON1_MASK ? ui::EF_LEFT_BUTTON_DOWN : 0) |
+ (state & IBUS_BUTTON2_MASK ? ui::EF_MIDDLE_BUTTON_DOWN : 0) |
+ (state & IBUS_BUTTON3_MASK ? ui::EF_RIGHT_BUTTON_DOWN : 0);
+}
+
+// Converts event flags to ibus key state flags.
+guint32 IBusStateFromEventFlags(int flags) {
+ return (flags & ui::EF_CAPS_LOCK_DOWN ? IBUS_LOCK_MASK : 0) |
+ (flags & ui::EF_CONTROL_DOWN ? IBUS_CONTROL_MASK : 0) |
+ (flags & ui::EF_SHIFT_DOWN ? IBUS_SHIFT_MASK : 0) |
+ (flags & ui::EF_ALT_DOWN ? IBUS_MOD1_MASK : 0) |
+ (flags & ui::EF_LEFT_BUTTON_DOWN ? IBUS_BUTTON1_MASK : 0) |
+ (flags & ui::EF_MIDDLE_BUTTON_DOWN ? IBUS_BUTTON2_MASK : 0) |
+ (flags & ui::EF_RIGHT_BUTTON_DOWN ? IBUS_BUTTON3_MASK : 0);
+}
+
+void IBusKeyEventFromViewsKeyEvent(const views::KeyEvent& key,
+ guint32* ibus_keyval,
+ guint32* ibus_keycode,
+ guint32* ibus_state) {
+#if defined(USE_AURA)
+ // TODO(yusukes): Handle native_event()?
+ *ibus_keyval = ui::XKeysymForWindowsKeyCode(
+ key.key_code(), key.IsShiftDown() ^ key.IsCapsLockDown());
+ *ibus_keycode = 0;
+#elif defined(TOUCH_UI)
+ if (key.native_event()) {
+ XKeyEvent* x_key = reinterpret_cast<XKeyEvent*>(key.native_event());
+ // Yes, ibus uses X11 keysym. We cannot use XLookupKeysym(), which doesn't
+ // translate Shift and CapsLock states.
+ KeySym keysym = NoSymbol;
+ ::XLookupString(x_key, NULL, 0, &keysym, NULL);
+ *ibus_keyval = keysym;
+ *ibus_keycode = x_key->keycode;
+ } else {
+ *ibus_keyval = ui::XKeysymForWindowsKeyCode(
+ key.key_code(), key.IsShiftDown() ^ key.IsCapsLockDown());
+ *ibus_keycode = 0;
+ }
+#elif defined(TOOLKIT_USES_GTK)
+ if (key.gdk_event()) {
+ GdkEventKey* gdk_key = reinterpret_cast<GdkEventKey*>(key.gdk_event());
+ *ibus_keyval = gdk_key->keyval;
+ *ibus_keycode = gdk_key->hardware_keycode;
+ } else {
+ *ibus_keyval = ui::GdkKeyCodeForWindowsKeyCode(
+ key.key_code(), key.IsShiftDown() ^ key.IsCapsLockDown());
+ *ibus_keycode = 0;
+ }
+#endif
+
+ *ibus_state = IBusStateFromEventFlags(key.flags());
+ if (key.type() == ui::ET_KEY_RELEASED)
+ *ibus_state |= IBUS_RELEASE_MASK;
+}
+
+void ExtractCompositionTextFromIBusPreedit(IBusText* text,
+ guint cursor_position,
+ ui::CompositionText* composition) {
+ composition->Clear();
+ composition->text = UTF8ToUTF16(text->text);
+
+ if (composition->text.empty())
+ return;
+
+ // ibus uses character index for cursor position and attribute range, but we
+ // use char16 offset for them. So we need to do conversion here.
+ std::vector<size_t> char16_offsets;
+ size_t length = composition->text.length();
+ base::i18n::UTF16CharIterator char_iterator(&composition->text);
+ do {
+ char16_offsets.push_back(char_iterator.array_pos());
+ } while (char_iterator.Advance());
+
+ // The text length in Unicode characters.
+ guint char_length = static_cast<guint>(char16_offsets.size());
+ // Make sure we can convert the value of |char_length| as well.
+ char16_offsets.push_back(length);
+
+ size_t cursor_offset =
+ char16_offsets[std::min(char_length, cursor_position)];
+
+ composition->selection = ui::Range(cursor_offset);
+ if (text->attrs) {
+ guint i = 0;
+ while (true) {
+ IBusAttribute* attr = ibus_attr_list_get(text->attrs, i++);
+ if (!attr)
+ break;
+ if (attr->type != IBUS_ATTR_TYPE_UNDERLINE &&
+ attr->type != IBUS_ATTR_TYPE_BACKGROUND) {
+ continue;
+ }
+ guint start = std::min(char_length, attr->start_index);
+ guint end = std::min(char_length, attr->end_index);
+ if (start >= end)
+ continue;
+ ui::CompositionUnderline underline(
+ char16_offsets[start], char16_offsets[end],
+ SK_ColorBLACK, false /* thick */);
+ if (attr->type == IBUS_ATTR_TYPE_BACKGROUND) {
+ underline.thick = true;
+ // If the cursor is at start or end of this underline, then we treat
+ // it as the selection range as well, but make sure to set the cursor
+ // position to the selection end.
+ if (underline.start_offset == cursor_offset) {
+ composition->selection.set_start(underline.end_offset);
+ composition->selection.set_end(cursor_offset);
+ } else if (underline.end_offset == cursor_offset) {
+ composition->selection.set_start(underline.start_offset);
+ composition->selection.set_end(cursor_offset);
+ }
+ } else if (attr->type == IBUS_ATTR_TYPE_UNDERLINE) {
+ if (attr->value == IBUS_ATTR_UNDERLINE_DOUBLE)
+ underline.thick = true;
+ else if (attr->value == IBUS_ATTR_UNDERLINE_ERROR)
+ underline.color = SK_ColorRED;
+ }
+ composition->underlines.push_back(underline);
+ }
+ }
+
+ // Use a black thin underline by default.
+ if (composition->underlines.empty()) {
+ composition->underlines.push_back(ui::CompositionUnderline(
+ 0, length, SK_ColorBLACK, false /* thick */));
+ }
+}
+
+// A switch to enable InputMethodIBus
+const char kEnableInputMethodIBusSwitch[] = "enable-inputmethod-ibus";
+
+} // namespace
+
+namespace views {
+
+// InputMethodIBus::PendingKeyEvent implementation ----------------------------
+class InputMethodIBus::PendingKeyEvent {
+ public:
+ PendingKeyEvent(InputMethodIBus* input_method, const KeyEvent& key,
+ guint32 ibus_keyval);
+ ~PendingKeyEvent();
+
+ // Abandon this pending key event. Its result will just be discarded.
+ void abandon() { input_method_ = NULL; }
+
+ InputMethodIBus* input_method() const { return input_method_; }
+
+ // Process this pending key event after we receive its result from the input
+ // method. It just call through InputMethodIBus::ProcessKeyEventPostIME().
+ void ProcessPostIME(bool handled);
+
+ private:
+ InputMethodIBus* input_method_;
+
+ // Complete information of a views::KeyEvent. Sadly, views::KeyEvent doesn't
+ // support copy.
+ ui::EventType type_;
+ int flags_;
+ ui::KeyboardCode key_code_;
+ uint16 character_;
+ uint16 unmodified_character_;
+
+ guint32 ibus_keyval_;
+
+#if defined(TOUCH_UI)
+ // corresponding XEvent data of a views::KeyEvent. It's a plain struct so we
+ // can do bitwise copy.
+ XKeyEvent x_event_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(PendingKeyEvent);
+};
+
+InputMethodIBus::PendingKeyEvent::PendingKeyEvent(InputMethodIBus* input_method,
+ const KeyEvent& key,
+ guint32 ibus_keyval)
+ : input_method_(input_method),
+ type_(key.type()),
+ flags_(key.flags()),
+ key_code_(key.key_code()),
+ character_(key.GetCharacter()),
+ unmodified_character_(key.GetUnmodifiedCharacter()),
+ ibus_keyval_(ibus_keyval) {
+ DCHECK(input_method_);
+
+#if defined(TOUCH_UI)
+ if (key.native_event())
+ x_event_ = *reinterpret_cast<XKeyEvent*>(key.native_event());
+ else
+ memset(&x_event_, 0, sizeof(x_event_));
+#endif
+}
+
+InputMethodIBus::PendingKeyEvent::~PendingKeyEvent() {
+ if (input_method_)
+ input_method_->FinishPendingKeyEvent(this);
+}
+
+void InputMethodIBus::PendingKeyEvent::ProcessPostIME(bool handled) {
+ if (!input_method_)
+ return;
+
+#if defined(TOUCH_UI)
+ if (x_event_.type == KeyPress || x_event_.type == KeyRelease) {
+ KeyEvent key(reinterpret_cast<XEvent*>(&x_event_));
+ input_method_->ProcessKeyEventPostIME(key, ibus_keyval_, handled);
+ return;
+ }
+#endif
+ KeyEvent key(type_, key_code_, flags_);
+ if (key_code_ == ui::VKEY_UNKNOWN) {
+ key.set_character(character_);
+ key.set_unmodified_character(unmodified_character_);
+ }
+ input_method_->ProcessKeyEventPostIME(key, ibus_keyval_, handled);
+}
+
+// InputMethodIBus::PendingCreateICRequest implementation ---------------------
+class InputMethodIBus::PendingCreateICRequest {
+ public:
+ PendingCreateICRequest(InputMethodIBus* input_method,
+ PendingCreateICRequest** request_ptr);
+ ~PendingCreateICRequest();
+
+ // Abandon this pending key event. Its result will just be discarded.
+ void abandon() {
+ input_method_ = NULL;
+ request_ptr_ = NULL;
+ }
+
+ // Stores the result input context to |input_method_|, or abandon it if
+ // |input_method_| is NULL.
+ void StoreOrAbandonInputContext(IBusInputContext* ic);
+
+ private:
+ InputMethodIBus* input_method_;
+ PendingCreateICRequest** request_ptr_;
+};
+
+InputMethodIBus::PendingCreateICRequest::PendingCreateICRequest(
+ InputMethodIBus* input_method,
+ PendingCreateICRequest** request_ptr)
+ : input_method_(input_method),
+ request_ptr_(request_ptr) {
+}
+
+InputMethodIBus::PendingCreateICRequest::~PendingCreateICRequest() {
+ if (request_ptr_) {
+ DCHECK_EQ(*request_ptr_, this);
+ *request_ptr_ = NULL;
+ }
+}
+
+void InputMethodIBus::PendingCreateICRequest::StoreOrAbandonInputContext(
+ IBusInputContext* ic) {
+ if (input_method_) {
+ input_method_->SetContext(ic);
+ } else {
+ // ibus_proxy_destroy() will not really release the object, we still need
+ // to call g_object_unref() explicitly.
+ ibus_proxy_destroy(reinterpret_cast<IBusProxy *>(ic));
+ g_object_unref(ic);
+ }
+}
+
+// InputMethodIBus implementation ---------------------------------------------
+InputMethodIBus::InputMethodIBus(internal::InputMethodDelegate* delegate)
+ : context_(NULL),
+ pending_create_ic_request_(NULL),
+ context_focused_(false),
+ composing_text_(false),
+ composition_changed_(false),
+ suppress_next_result_(false) {
+ set_delegate(delegate);
+}
+
+InputMethodIBus::~InputMethodIBus() {
+ AbandonAllPendingKeyEvents();
+ DestroyContext();
+
+ // Disconnect bus signals
+ g_signal_handlers_disconnect_by_func(
+ GetIBus(), reinterpret_cast<gpointer>(OnIBusConnectedThunk), this);
+ g_signal_handlers_disconnect_by_func(
+ GetIBus(), reinterpret_cast<gpointer>(OnIBusDisconnectedThunk), this);
+}
+
+void InputMethodIBus::OnFocus() {
+ DCHECK(!widget_focused());
+ InputMethodBase::OnFocus();
+ UpdateContextFocusState();
+}
+
+void InputMethodIBus::OnBlur() {
+ DCHECK(widget_focused());
+ ConfirmCompositionText();
+ InputMethodBase::OnBlur();
+ UpdateContextFocusState();
+}
+
+void InputMethodIBus::Init(Widget* widget) {
+ // Initializes the connection to ibus daemon. It may happen asynchronously,
+ // and as soon as the connection is established, the |context_| will be
+ // created automatically.
+ IBusBus* bus = GetIBus();
+
+ // connect bus signals
+ g_signal_connect(bus, "connected",
+ G_CALLBACK(OnIBusConnectedThunk), this);
+ g_signal_connect(bus, "disconnected",
+ G_CALLBACK(OnIBusDisconnectedThunk), this);
+
+ // Creates the |context_| if the connection is already established. In such
+ // case, we will not get "connected" signal.
+ if (ibus_bus_is_connected(bus))
+ CreateContext();
+
+ InputMethodBase::Init(widget);
+}
+
+void InputMethodIBus::DispatchKeyEvent(const KeyEvent& key) {
+ DCHECK(key.type() == ui::ET_KEY_PRESSED || key.type() == ui::ET_KEY_RELEASED);
+ DCHECK(widget_focused());
+
+ guint32 ibus_keyval = 0;
+ guint32 ibus_keycode = 0;
+ guint32 ibus_state = 0;
+ IBusKeyEventFromViewsKeyEvent(key, &ibus_keyval, &ibus_keycode, &ibus_state);
+
+ // If |context_| is not usable, then we can only dispatch the key event as is.
+ // We also dispatch the key event directly if the current text input type is
+ // ui::TEXT_INPUT_TYPE_PASSWORD, to bypass the input method.
+ // Note: We need to send the key event to ibus even if the |context_| is not
+ // enabled, so that ibus can have a chance to enable the |context_|.
+ if (!context_focused_ ||
+ GetTextInputType() == ui::TEXT_INPUT_TYPE_PASSWORD) {
+ if (key.type() == ui::ET_KEY_PRESSED)
+ ProcessUnfilteredKeyPressEvent(key, ibus_keyval);
+ else
+ DispatchKeyEventPostIME(key);
+ return;
+ }
+
+ PendingKeyEvent* pending_key = new PendingKeyEvent(this, key, ibus_keyval);
+ pending_key_events_.insert(pending_key);
+
+ // Note:
+ // 1. We currently set timeout to -1, because ibus doesn't have a mechanism to
+ // associate input method results to corresponding key event, thus there is
+ // actually no way to abandon results generated by a specific key event. So we
+ // actually cannot abandon a specific key event and its result but accept
+ // following key events and their results. So a timeout to abandon a key event
+ // will not work.
+ // 2. We set GCancellable to NULL, because the operation of cancelling a async
+ // request also happens asynchronously, thus it's actually useless to us.
+ //
+ // The fundemental problem of ibus' async API is: it uses Glib's GIO API to
+ // realize async communication, but in fact, GIO API is specially designed for
+ // concurrent tasks, though it supports async communication as well, the API
+ // is much more complicated than an ordinary message based async communication
+ // API (such as Chrome's IPC).
+ // Thus it's very complicated, if not impossible, to implement a client that
+ // fully utilize asynchronous communication without potential problem.
+ ibus_input_context_process_key_event_async(
+ context_,
+ ibus_keyval, ibus_keycode, ibus_state, -1, NULL,
+ reinterpret_cast<GAsyncReadyCallback>(ProcessKeyEventDone),
+ pending_key);
+
+ // We don't want to suppress the result generated by this key event, but it
+ // may cause problem. See comment in ResetContext() method.
+ suppress_next_result_ = false;
+}
+
+void InputMethodIBus::OnTextInputTypeChanged(View* view) {
+ if (context_ && IsViewFocused(view)) {
+ ResetContext();
+ UpdateContextFocusState();
+ }
+ InputMethodBase::OnTextInputTypeChanged(view);
+}
+
+void InputMethodIBus::OnCaretBoundsChanged(View* view) {
+ if (!context_focused_ || !IsViewFocused(view))
+ return;
+
+ // The current text input type should not be NONE if |context_| is focused.
+ DCHECK(!IsTextInputTypeNone());
+
+ gfx::Rect rect = GetTextInputClient()->GetCaretBounds();
+ gfx::Point origin = rect.origin();
+ gfx::Point end = gfx::Point(rect.right(), rect.bottom());
+
+ // We need to convert the origin and end points separately, in case the View
+ // is scaled.
+ View::ConvertPointToScreen(view, &origin);
+ View::ConvertPointToScreen(view, &end);
+
+ // This function runs asynchronously.
+ ibus_input_context_set_cursor_location(
+ context_, origin.x(), origin.y(),
+ end.x() - origin.x(), end.y() - origin.y());
+}
+
+void InputMethodIBus::CancelComposition(View* view) {
+ if (context_focused_ && IsViewFocused(view))
+ ResetContext();
+}
+
+std::string InputMethodIBus::GetInputLocale() {
+ // Not supported.
+ return std::string("");
+}
+
+base::i18n::TextDirection InputMethodIBus::GetInputTextDirection() {
+ // Not supported.
+ return base::i18n::UNKNOWN_DIRECTION;
+}
+
+bool InputMethodIBus::IsActive() {
+ return true;
+}
+
+// static
+bool InputMethodIBus::IsInputMethodIBusEnabled() {
+#if defined(TOUCH_UI)
+ return true;
+#else
+ return inputmethod_ibus_enabled ||
+ CommandLine::ForCurrentProcess()->HasSwitch(
+ kEnableInputMethodIBusSwitch);
+#endif
+}
+
+// static
+void InputMethodIBus::SetEnableInputMethodIBus(bool enabled) {
+ inputmethod_ibus_enabled = enabled;
+}
+
+void InputMethodIBus::OnWillChangeFocus(View* focused_before, View* focused) {
+ ConfirmCompositionText();
+}
+
+void InputMethodIBus::OnDidChangeFocus(View* focused_before, View* focused) {
+ UpdateContextFocusState();
+
+ // Force to update caret bounds, in case the View thinks that the caret
+ // bounds has not changed.
+ if (context_focused_)
+ OnCaretBoundsChanged(GetFocusedView());
+}
+
+void InputMethodIBus::CreateContext() {
+ DCHECK(!context_);
+ DCHECK(GetIBus());
+ DCHECK(ibus_bus_is_connected(GetIBus()));
+ DCHECK(!pending_create_ic_request_);
+
+ // Creates the input context asynchronously.
+ pending_create_ic_request_ = new PendingCreateICRequest(
+ this, &pending_create_ic_request_);
+ ibus_bus_create_input_context_async(
+ GetIBus(), "chrome", -1, NULL,
+ reinterpret_cast<GAsyncReadyCallback>(CreateInputContextDone),
+ pending_create_ic_request_);
+}
+
+void InputMethodIBus::SetContext(IBusInputContext* ic) {
+ DCHECK(ic);
+ DCHECK(!context_);
+ context_ = ic;
+
+ // connect input context signals
+ g_signal_connect(ic, "commit-text",
+ G_CALLBACK(OnCommitTextThunk), this);
+ g_signal_connect(ic, "forward-key-event",
+ G_CALLBACK(OnForwardKeyEventThunk), this);
+ g_signal_connect(ic, "update-preedit-text",
+ G_CALLBACK(OnUpdatePreeditTextThunk), this);
+ g_signal_connect(ic, "show-preedit-text",
+ G_CALLBACK(OnShowPreeditTextThunk), this);
+ g_signal_connect(ic, "hide-preedit-text",
+ G_CALLBACK(OnHidePreeditTextThunk), this);
+ g_signal_connect(ic, "destroy",
+ G_CALLBACK(OnDestroyThunk), this);
+
+ // TODO(suzhe): support surrounding text.
+ guint32 caps = IBUS_CAP_PREEDIT_TEXT | IBUS_CAP_FOCUS;
+ ibus_input_context_set_capabilities(ic, caps);
+
+ UpdateContextFocusState();
+ OnInputMethodChanged();
+}
+
+void InputMethodIBus::DestroyContext() {
+ if (pending_create_ic_request_) {
+ DCHECK(!context_);
+ // |pending_create_ic_request_| will be deleted in CreateInputContextDone().
+ pending_create_ic_request_->abandon();
+ pending_create_ic_request_ = NULL;
+ } else if (context_) {
+ // ibus_proxy_destroy() will not really release the resource of |context_|
+ // object. We still need to handle "destroy" signal and call
+ // g_object_unref() there.
+ ibus_proxy_destroy(reinterpret_cast<IBusProxy *>(context_));
+ DCHECK(!context_);
+ }
+}
+
+void InputMethodIBus::ConfirmCompositionText() {
+ ui::TextInputClient* client = GetTextInputClient();
+ if (client && client->HasCompositionText())
+ client->ConfirmCompositionText();
+
+ ResetContext();
+}
+
+void InputMethodIBus::ResetContext() {
+ if (!context_focused_ || !GetTextInputClient())
+ return;
+
+ DCHECK(widget_focused());
+ DCHECK(GetFocusedView());
+
+ // Because ibus runs in asynchronous mode, the input method may still send us
+ // results after sending out the reset request, so we use a flag to discard
+ // all results generated by previous key events. But because ibus does not
+ // have a mechanism to identify each key event and corresponding results, this
+ // approach will not work for some corner cases. For example if the user types
+ // very fast, then the next key event may come in before the |context_| is
+ // really reset. Then we actually cannot know whether or not the next
+ // result should be discard.
+ suppress_next_result_ = true;
+
+ composition_.Clear();
+ result_text_.clear();
+ composing_text_ = false;
+ composition_changed_ = false;
+
+ // We need to abandon all pending key events, but as above comment says, there
+ // is no reliable way to abandon all results generated by these abandoned key
+ // events.
+ AbandonAllPendingKeyEvents();
+
+ // This function runs asynchronously.
+ // Note: some input method engines may not support reset method, such as
+ // ibus-anthy. But as we control all input method engines by ourselves, we can
+ // make sure that all of the engines we are using support it correctly.
+ ibus_input_context_reset(context_);
+
+ character_composer_.Reset();
+}
+
+void InputMethodIBus::UpdateContextFocusState() {
+ if (!context_) {
+ context_focused_ = false;
+ return;
+ }
+
+ const bool old_context_focused = context_focused_;
+ // Use switch here in case we are going to add more text input types.
+ switch (GetTextInputType()) {
+ case ui::TEXT_INPUT_TYPE_NONE:
+ case ui::TEXT_INPUT_TYPE_PASSWORD:
+ context_focused_ = false;
+ break;
+ default:
+ context_focused_ = true;
+ break;
+ }
+
+ // We only focus in |context_| when the focus is in a normal textfield.
+ // ibus_input_context_focus_{in|out}() run asynchronously.
+ if (old_context_focused && !context_focused_)
+ ibus_input_context_focus_out(context_);
+ else if (!old_context_focused && context_focused_)
+ ibus_input_context_focus_in(context_);
+}
+
+void InputMethodIBus::ProcessKeyEventPostIME(const KeyEvent& key,
+ guint32 ibus_keyval,
+ bool handled) {
+ // If we get here without a focused text input client, then it means the key
+ // event is sent to the global ibus input context.
+ if (!GetTextInputClient()) {
+ DispatchKeyEventPostIME(key);
+ return;
+ }
+
+ const View* old_focused_view = GetFocusedView();
+
+ // Same reason as above DCHECK.
+ DCHECK(old_focused_view);
+
+ if (key.type() == ui::ET_KEY_PRESSED && handled)
+ ProcessFilteredKeyPressEvent(key);
+
+ // In case the focus was changed by the key event. The |context_| should have
+ // been reset when the focused view changed.
+ if (old_focused_view != GetFocusedView())
+ return;
+
+ if (HasInputMethodResult())
+ ProcessInputMethodResult(key, handled);
+
+ // In case the focus was changed when sending input method results to the
+ // focused View.
+ if (old_focused_view != GetFocusedView())
+ return;
+
+ if (key.type() == ui::ET_KEY_PRESSED && !handled)
+ ProcessUnfilteredKeyPressEvent(key, ibus_keyval);
+ else if (key.type() == ui::ET_KEY_RELEASED)
+ DispatchKeyEventPostIME(key);
+}
+
+void InputMethodIBus::ProcessFilteredKeyPressEvent(const KeyEvent& key) {
+ if (NeedInsertChar()) {
+ DispatchKeyEventPostIME(key);
+ } else {
+ KeyEvent key(ui::ET_KEY_PRESSED, ui::VKEY_PROCESSKEY, key.flags());
+ DispatchKeyEventPostIME(key);
+ }
+}
+
+void InputMethodIBus::ProcessUnfilteredKeyPressEvent(const KeyEvent& key,
+ guint32 ibus_keyval) {
+ const View* old_focused_view = GetFocusedView();
+ DispatchKeyEventPostIME(key);
+
+ // We shouldn't dispatch the character anymore if the key event caused focus
+ // change.
+ if (old_focused_view != GetFocusedView())
+ return;
+
+ // Process compose and dead keys
+ if (character_composer_.FilterKeyPress(ibus_keyval)) {
+ string16 composed = character_composer_.composed_character();
+ if (!composed.empty()) {
+ ui::TextInputClient* client = GetTextInputClient();
+ if (client)
+ client->InsertText(composed);
+ }
+ return;
+ }
+ // If a key event was not filtered by |context_| and |character_composer_|,
+ // then it means the key event didn't generate any result text. So we need
+ // to send corresponding character to the focused text input client.
+
+ ui::TextInputClient* client = GetTextInputClient();
+ char16 ch = key.GetCharacter();
+ if (ch && client)
+ client->InsertChar(ch, key.flags());
+}
+
+void InputMethodIBus::ProcessInputMethodResult(const KeyEvent& key,
+ bool handled) {
+ ui::TextInputClient* client = GetTextInputClient();
+ DCHECK(client);
+
+ if (result_text_.length()) {
+ if (handled && 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_ && !IsTextInputTypeNone()) {
+ if (composition_.text.length()) {
+ composing_text_ = true;
+ client->SetCompositionText(composition_);
+ } else if (result_text_.empty()) {
+ client->ClearCompositionText();
+ }
+ }
+
+ // We should not clear composition text here, as it may belong to the next
+ // composition session.
+ result_text_.clear();
+ composition_changed_ = false;
+}
+
+bool InputMethodIBus::NeedInsertChar() const {
+ return GetTextInputClient() &&
+ (IsTextInputTypeNone() ||
+ (!composing_text_ && result_text_.length() == 1));
+}
+
+bool InputMethodIBus::HasInputMethodResult() const {
+ return result_text_.length() || composition_changed_;
+}
+
+void InputMethodIBus::SendFakeProcessKeyEvent(bool pressed) const {
+ KeyEvent key(pressed ? ui::ET_KEY_PRESSED : ui::ET_KEY_RELEASED,
+ ui::VKEY_PROCESSKEY, 0);
+ DispatchKeyEventPostIME(key);
+}
+
+void InputMethodIBus::FinishPendingKeyEvent(PendingKeyEvent* pending_key) {
+ DCHECK(pending_key_events_.count(pending_key));
+
+ // |pending_key| will be deleted in ProcessKeyEventDone().
+ pending_key_events_.erase(pending_key);
+}
+
+void InputMethodIBus::AbandonAllPendingKeyEvents() {
+ for (std::set<PendingKeyEvent*>::iterator i = pending_key_events_.begin();
+ i != pending_key_events_.end(); ++i) {
+ // The object will be deleted in ProcessKeyEventDone().
+ (*i)->abandon();
+ }
+ pending_key_events_.clear();
+}
+
+void InputMethodIBus::OnCommitText(IBusInputContext* context, IBusText* text) {
+ DCHECK_EQ(context_, context);
+ if (suppress_next_result_ || !text || !text->text)
+ return;
+
+ // We need to receive input method result even if the text input type is
+ // ui::TEXT_INPUT_TYPE_NONE, to make sure we can always send correct
+ // character for each key event to the focused text input client.
+ if (!GetTextInputClient())
+ return;
+
+ string16 utf16_text(UTF8ToUTF16(text->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 we are not handling key event, do not bother sending text result if the
+ // focused text input client does not support text input.
+ if (pending_key_events_.empty() && !IsTextInputTypeNone()) {
+ SendFakeProcessKeyEvent(true);
+ GetTextInputClient()->InsertText(utf16_text);
+ SendFakeProcessKeyEvent(false);
+ result_text_.clear();
+ }
+}
+
+void InputMethodIBus::OnForwardKeyEvent(IBusInputContext* context,
+ guint keyval,
+ guint keycode,
+ guint state) {
+ DCHECK_EQ(context_, context);
+
+ ui::KeyboardCode key_code = ui::VKEY_UNKNOWN;
+#if defined(USE_AURA) || defined(TOUCH_UI)
+ key_code = ui::KeyboardCodeFromXKeysym(keyval);
+#elif defined(TOOLKIT_USES_GTK)
+ key_code = ui::WindowsKeyCodeForGdkKeyCode(keyval);
+#endif
+
+ if (!key_code)
+ return;
+
+ KeyEvent key(state & IBUS_RELEASE_MASK ?
+ ui::ET_KEY_RELEASED : ui::ET_KEY_PRESSED,
+ key_code, EventFlagsFromIBusState(state));
+
+ // It is not clear when the input method will forward us a fake key event.
+ // If there is a pending key event, then we may already received some input
+ // method results, so we dispatch this fake key event directly rather than
+ // calling ProcessKeyEventPostIME(), which will clear pending input method
+ // results.
+ if (key.type() == ui::ET_KEY_PRESSED)
+ ProcessUnfilteredKeyPressEvent(key, keyval);
+ else
+ DispatchKeyEventPostIME(key);
+}
+
+void InputMethodIBus::OnShowPreeditText(IBusInputContext* context) {
+ DCHECK_EQ(context_, context);
+ if (suppress_next_result_ || IsTextInputTypeNone())
+ return;
+
+ composing_text_ = true;
+}
+
+void InputMethodIBus::OnUpdatePreeditText(IBusInputContext* context,
+ IBusText* text,
+ guint cursor_pos,
+ gboolean visible) {
+ DCHECK_EQ(context_, context);
+ if (suppress_next_result_ || IsTextInputTypeNone())
+ return;
+
+ // |visible| argument is very confusing. For example, what's the correct
+ // behavior when:
+ // 1. OnUpdatePreeditText() is called with a text and visible == false, then
+ // 2. OnShowPreeditText() is called afterwards.
+ //
+ // If it's only for clearing the current preedit text, then why not just use
+ // OnHidePreeditText()?
+ if (!visible) {
+ OnHidePreeditText(context);
+ return;
+ }
+
+ ExtractCompositionTextFromIBusPreedit(text, cursor_pos, &composition_);
+
+ composition_changed_ = true;
+
+ // In case OnShowPreeditText() is not called.
+ if (composition_.text.length())
+ composing_text_ = true;
+
+ // If we receive a composition text without pending key event, then we need to
+ // send it to the focused text input client directly.
+ if (pending_key_events_.empty()) {
+ SendFakeProcessKeyEvent(true);
+ GetTextInputClient()->SetCompositionText(composition_);
+ SendFakeProcessKeyEvent(false);
+ composition_changed_ = false;
+ composition_.Clear();
+ }
+}
+
+void InputMethodIBus::OnHidePreeditText(IBusInputContext* context) {
+ DCHECK_EQ(context_, context);
+ if (composition_.text.empty() || IsTextInputTypeNone())
+ return;
+
+ // Intentionally leaves |composing_text_| unchanged.
+ composition_changed_ = true;
+ composition_.Clear();
+
+ if (pending_key_events_.empty()) {
+ ui::TextInputClient* client = GetTextInputClient();
+ if (client && client->HasCompositionText())
+ client->ClearCompositionText();
+ composition_changed_ = false;
+ }
+}
+
+void InputMethodIBus::OnDestroy(IBusInputContext* context) {
+ DCHECK_EQ(context_, context);
+ g_object_unref(context_);
+ context_ = NULL;
+ context_focused_ = false;
+
+ ConfirmCompositionText();
+
+ // We are dead, so we need to ask the client to stop relying on us.
+ // We cannot do it in DestroyContext(), because OnDestroy() may be called
+ // automatically.
+ OnInputMethodChanged();
+}
+
+void InputMethodIBus::OnIBusConnected(IBusBus* bus) {
+ DCHECK_EQ(GetIBus(), bus);
+ DCHECK(ibus_bus_is_connected(bus));
+
+ DestroyContext();
+ CreateContext();
+}
+
+void InputMethodIBus::OnIBusDisconnected(IBusBus* bus) {
+ DCHECK_EQ(GetIBus(), bus);
+
+ // TODO(suzhe): Make sure if we really do not need to handle this signal.
+ // And I'm actually wondering if ibus-daemon will release the resource of the
+ // |context_| correctly when the connection is lost.
+}
+
+// static
+IBusBus* InputMethodIBus::GetIBus() {
+ // Everything happens in UI thread, so we do not need to care about
+ // synchronization issue.
+ static IBusBus* ibus = NULL;
+
+ if (!ibus) {
+ ibus_init();
+ ibus = ibus_bus_new();
+ DCHECK(ibus);
+ }
+ return ibus;
+}
+
+// static
+void InputMethodIBus::ProcessKeyEventDone(IBusInputContext* context,
+ GAsyncResult* res,
+ PendingKeyEvent* data) {
+ DCHECK(data);
+ DCHECK(!data->input_method() ||
+ data->input_method()->context_ == context);
+
+ gboolean handled = ibus_input_context_process_key_event_async_finish(
+ context, res, NULL);
+ data->ProcessPostIME(handled);
+ delete data;
+}
+
+// static
+void InputMethodIBus::CreateInputContextDone(IBusBus* bus,
+ GAsyncResult* res,
+ PendingCreateICRequest* data) {
+ DCHECK_EQ(GetIBus(), bus);
+ DCHECK(data);
+ IBusInputContext* ic =
+ ibus_bus_create_input_context_async_finish(bus, res, NULL);
+ if (ic)
+ data->StoreOrAbandonInputContext(ic);
+ delete data;
+}
+
+} // namespace views
diff --git a/ui/views/ime/input_method_ibus.h b/ui/views/ime/input_method_ibus.h
new file mode 100644
index 0000000..8d7b591
--- /dev/null
+++ b/ui/views/ime/input_method_ibus.h
@@ -0,0 +1,199 @@
+// 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.
+
+#ifndef UI_VIEWS_IME_INPUT_METHOD_IBUS_H_
+#define UI_VIEWS_IME_INPUT_METHOD_IBUS_H_
+#pragma once
+
+#include <set>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/base/glib/glib_integers.h"
+#include "ui/base/glib/glib_signal.h"
+#include "ui/base/ime/character_composer.h"
+#include "ui/base/ime/composition_text.h"
+#include "ui/base/ime/text_input_client.h"
+#include "ui/views/ime/input_method_base.h"
+#include "views/events/event.h"
+#include "views/view.h"
+
+// Forward declarations, so that we don't need to include ibus.h in this file.
+typedef struct _GAsyncResult GAsyncResult;
+typedef struct _IBusBus IBusBus;
+typedef struct _IBusInputContext IBusInputContext;
+typedef struct _IBusText IBusText;
+
+namespace views {
+
+// An InputMethod implementation based on IBus.
+class InputMethodIBus : public InputMethodBase {
+ public:
+ explicit InputMethodIBus(internal::InputMethodDelegate* delegate);
+ virtual ~InputMethodIBus();
+
+ // Overridden from InputMethod:
+ virtual void Init(Widget* widget) OVERRIDE;
+ virtual void OnFocus() OVERRIDE;
+ virtual void OnBlur() OVERRIDE;
+ virtual void DispatchKeyEvent(const KeyEvent& key) OVERRIDE;
+ virtual void OnTextInputTypeChanged(View* view) OVERRIDE;
+ virtual void OnCaretBoundsChanged(View* view) OVERRIDE;
+ virtual void CancelComposition(View* view) OVERRIDE;
+ virtual std::string GetInputLocale() OVERRIDE;
+ virtual base::i18n::TextDirection GetInputTextDirection() OVERRIDE;
+ virtual bool IsActive() OVERRIDE;
+
+ // Returns true when
+ // 1) built with GYP_DEFINES="touchui=1" or,
+ // 2) enabled by SetEnabledInputMethodIBus or,
+ // 3) enabled by command line flag "--enable-inputmethod-ibus"
+ static bool IsInputMethodIBusEnabled();
+ // Enable/Disable InputMethodIBus
+ static void SetEnableInputMethodIBus(bool enabled);
+
+ private:
+ // A class to hold all data related to a key event being processed by the
+ // input method but still has no result back yet.
+ class PendingKeyEvent;
+
+ // A class to hold information of a pending request for creating an ibus input
+ // context.
+ class PendingCreateICRequest;
+
+ // Overridden from InputMethodBase:
+ virtual void OnWillChangeFocus(View* focused_before, View* focused) OVERRIDE;
+ virtual void OnDidChangeFocus(View* focused_before, View* focused) OVERRIDE;
+
+ // Creates |context_| instance asynchronously.
+ void CreateContext();
+
+ // Sets |context_| and hooks necessary signals.
+ void SetContext(IBusInputContext* ic);
+
+ // Destroys |context_| instance.
+ void DestroyContext();
+
+ // Asks the client to confirm current composition text.
+ void ConfirmCompositionText();
+
+ // Resets |context_| and abandon all pending results and key events.
+ void ResetContext();
+
+ // Checks the availability of focused text input client and update focus state
+ // of |context_| and |context_simple_| accordingly.
+ void UpdateContextFocusState();
+
+ // Process a key returned from the input method.
+ void ProcessKeyEventPostIME(const KeyEvent& key, guint32 ibus_keycode,
+ bool handled);
+
+ // Processes a key event that was already filtered by the input method.
+ // A VKEY_PROCESSKEY may be dispatched to the focused View.
+ void ProcessFilteredKeyPressEvent(const KeyEvent& key);
+
+ // Processes a key event that was not filtered by the input method.
+ void ProcessUnfilteredKeyPressEvent(const KeyEvent& key,
+ guint32 ibus_keycode);
+
+ // Sends input method result caused by the given key event to the focused text
+ // input client.
+ void ProcessInputMethodResult(const KeyEvent& key, bool filtered);
+
+ // Checks if the pending input method result needs inserting into the focused
+ // text input client as a single character.
+ bool NeedInsertChar() const;
+
+ // Checks if there is pending input method result.
+ bool HasInputMethodResult() const;
+
+ // Fabricates a key event with VKEY_PROCESSKEY key code and dispatches it to
+ // the focused View.
+ void SendFakeProcessKeyEvent(bool pressed) const;
+
+ // Called when a pending key event has finished. The event will be removed
+ // from |pending_key_events_|.
+ void FinishPendingKeyEvent(PendingKeyEvent* pending_key);
+
+ // Abandons all pending key events. It usually happends when we lose keyboard
+ // focus, the text input type is changed or we are destroyed.
+ void AbandonAllPendingKeyEvents();
+
+ // Event handlers for IBusInputContext:
+ CHROMEG_CALLBACK_1(InputMethodIBus, void, OnCommitText,
+ IBusInputContext*, IBusText*);
+ CHROMEG_CALLBACK_3(InputMethodIBus, void, OnForwardKeyEvent,
+ IBusInputContext*, guint, guint, guint);
+ CHROMEG_CALLBACK_0(InputMethodIBus, void, OnShowPreeditText,
+ IBusInputContext*);
+ CHROMEG_CALLBACK_3(InputMethodIBus, void, OnUpdatePreeditText,
+ IBusInputContext*, IBusText*, guint, gboolean);
+ CHROMEG_CALLBACK_0(InputMethodIBus, void, OnHidePreeditText,
+ IBusInputContext*);
+ CHROMEG_CALLBACK_0(InputMethodIBus, void, OnDestroy, IBusInputContext*);
+
+ // Event handlers for IBusBus:
+ CHROMEG_CALLBACK_0(InputMethodIBus, void, OnIBusConnected, IBusBus*);
+ CHROMEG_CALLBACK_0(InputMethodIBus, void, OnIBusDisconnected, IBusBus*);
+
+ // Returns the global IBusBus instance.
+ static IBusBus* GetIBus();
+
+ // Callback function for ibus_input_context_process_key_event_async().
+ static void ProcessKeyEventDone(IBusInputContext* context,
+ GAsyncResult* res,
+ PendingKeyEvent* data);
+
+ // Callback function for ibus_bus_create_input_context_async().
+ static void CreateInputContextDone(IBusBus* bus,
+ GAsyncResult* res,
+ PendingCreateICRequest* data);
+
+ // The input context for actual text input.
+ IBusInputContext* context_;
+
+ // All pending key events. Note: we do not own these object, we just save
+ // pointers to these object so that we can abandon them when necessary.
+ // They will be deleted in ProcessKeyEventDone().
+ std::set<PendingKeyEvent*> pending_key_events_;
+
+ // The pending request for creating the |context_| instance. We need to keep
+ // this pointer so that we can receive or abandon the result.
+ PendingCreateICRequest* pending_create_ic_request_;
+
+ // Pending composition text generated by the current pending key event.
+ // It'll be sent to the focused text input client as soon as we receive the
+ // processing result of the pending key event.
+ ui::CompositionText composition_;
+
+ // Pending result text generated by the current pending key event.
+ // It'll be sent to the focused text input client as soon as we receive the
+ // processing result of the pending key event.
+ string16 result_text_;
+
+ // Indicates if |context_| and |context_simple_| are focused or not.
+ bool context_focused_;
+
+ // Indicates if there is an ongoing composition text.
+ bool composing_text_;
+
+ // Indicates if the composition text is changed or deleted.
+ bool composition_changed_;
+
+ // If it's true then all input method result received before the next key
+ // event will be discarded.
+ bool suppress_next_result_;
+
+ // An object to compose a character from a sequence of key presses
+ // including dead key etc.
+ ui::CharacterComposer character_composer_;
+
+ DISALLOW_COPY_AND_ASSIGN(InputMethodIBus);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_IME_INPUT_METHOD_IBUS_H_
diff --git a/ui/views/ime/input_method_wayland.cc b/ui/views/ime/input_method_wayland.cc
new file mode 100644
index 0000000..d35b1bd
--- /dev/null
+++ b/ui/views/ime/input_method_wayland.cc
@@ -0,0 +1,76 @@
+// 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 "ui/views/ime/input_method_wayland.h"
+
+#include "views/widget/widget.h"
+
+namespace views {
+
+InputMethodWayland::InputMethodWayland(
+ internal::InputMethodDelegate *delegate) {
+ set_delegate(delegate);
+}
+
+void InputMethodWayland::DispatchKeyEvent(const KeyEvent& key) {
+ if (!GetTextInputClient()) {
+ DispatchKeyEventPostIME(key);
+ return;
+ }
+
+ if (key.type() == ui::ET_KEY_PRESSED) {
+ ProcessKeyPressEvent(key);
+ } else if (key.type() == ui::ET_KEY_RELEASED) {
+ DispatchKeyEventPostIME(key);
+ }
+}
+
+void InputMethodWayland::OnTextInputTypeChanged(View* view) {
+ NOTIMPLEMENTED();
+}
+
+void InputMethodWayland::OnCaretBoundsChanged(View* view) {
+ NOTIMPLEMENTED();
+}
+
+void InputMethodWayland::CancelComposition(View* view) {
+ NOTIMPLEMENTED();
+}
+
+std::string InputMethodWayland::GetInputLocale() {
+ return std::string("");
+}
+
+base::i18n::TextDirection InputMethodWayland::GetInputTextDirection() {
+ return base::i18n::UNKNOWN_DIRECTION;
+}
+
+bool InputMethodWayland::IsActive() {
+ return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// InputMethodWayland private
+
+void InputMethodWayland::ProcessKeyPressEvent(const KeyEvent& key) {
+ const View* old_focused_view = focused_view();
+ DispatchKeyEventPostIME(key);
+
+ // We shouldn't dispatch the character anymore if the key event caused focus
+ // change.
+ if (old_focused_view != focused_view())
+ return;
+
+ // 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();
+ ui::TextInputClient* client = GetTextInputClient();
+ if (ch && client)
+ client->InsertChar(ch, key.flags());
+}
+
+} // namespace views
diff --git a/ui/views/ime/input_method_wayland.h b/ui/views/ime/input_method_wayland.h
new file mode 100644
index 0000000..7e937bf
--- /dev/null
+++ b/ui/views/ime/input_method_wayland.h
@@ -0,0 +1,36 @@
+// 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.
+
+#ifndef UI_VIEWS_IME_INPUT_METHOD_WAYLAND_H_
+#define UI_VIEWS_IME_INPUT_METHOD_WAYLAND_H_
+#pragma once
+
+#include <string>
+
+#include "ui/views/ime/input_method_base.h"
+
+namespace views {
+
+class InputMethodWayland : public InputMethodBase {
+ public:
+ explicit InputMethodWayland(internal::InputMethodDelegate *delegate);
+
+ virtual void DispatchKeyEvent(const KeyEvent& key) OVERRIDE;
+ virtual void OnTextInputTypeChanged(View* view) OVERRIDE;
+ virtual void OnCaretBoundsChanged(View* view) OVERRIDE;
+ virtual void CancelComposition(View* view) OVERRIDE;
+ virtual std::string GetInputLocale() OVERRIDE;
+ virtual base::i18n::TextDirection GetInputTextDirection() OVERRIDE;
+ virtual bool IsActive() OVERRIDE;
+
+ private:
+
+ void ProcessKeyPressEvent(const KeyEvent& key);
+
+ DISALLOW_COPY_AND_ASSIGN(InputMethodWayland);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_IME_INPUT_METHOD_WAYLAND_H_
diff --git a/ui/views/ime/input_method_win.cc b/ui/views/ime/input_method_win.cc
new file mode 100644
index 0000000..1bc56b1
--- /dev/null
+++ b/ui/views/ime/input_method_win.cc
@@ -0,0 +1,431 @@
+// 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 "ui/views/ime/input_method_win.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "ui/base/ime/composition_text.h"
+#include "ui/base/ime/text_input_client.h"
+#include "ui/base/keycodes/keyboard_codes.h"
+#include "views/events/event.h"
+
+// Extra number of chars before and after selection (or composition) range which
+// is returned to IME for improving conversion accuracy.
+static const size_t kExtraNumberOfChars = 20;
+
+namespace views {
+
+InputMethodWin::InputMethodWin(internal::InputMethodDelegate* delegate)
+ : active_(false),
+ direction_(base::i18n::UNKNOWN_DIRECTION),
+ pending_requested_direction_(base::i18n::UNKNOWN_DIRECTION) {
+ set_delegate(delegate);
+}
+
+InputMethodWin::~InputMethodWin() {
+ if (widget())
+ ime_input_.DisableIME(hwnd());
+}
+
+void InputMethodWin::Init(Widget* widget) {
+ // Gets the initial input locale and text direction information.
+ OnInputLangChange(0, 0);
+
+ InputMethodBase::Init(widget);
+}
+
+void InputMethodWin::OnFocus() {
+ DCHECK(!widget_focused());
+ InputMethodBase::OnFocus();
+ UpdateIMEState();
+}
+
+void InputMethodWin::OnBlur() {
+ DCHECK(widget_focused());
+ ConfirmCompositionText();
+ InputMethodBase::OnBlur();
+}
+
+void InputMethodWin::DispatchKeyEvent(const KeyEvent& key) {
+ // Handles ctrl-shift key to change text direction and layout alignment.
+ if (ui::ImeInput::IsRTLKeyboardLayoutInstalled() && !IsTextInputTypeNone()) {
+ ui::KeyboardCode code = key.key_code();
+ if (key.type() == ui::ET_KEY_PRESSED) {
+ if (code == ui::VKEY_SHIFT) {
+ base::i18n::TextDirection dir;
+ if (ui::ImeInput::IsCtrlShiftPressed(&dir))
+ pending_requested_direction_ = dir;
+ } else if (code != ui::VKEY_CONTROL) {
+ pending_requested_direction_ = base::i18n::UNKNOWN_DIRECTION;
+ }
+ } else if (key.type() == ui::ET_KEY_RELEASED &&
+ (code == ui::VKEY_SHIFT || code == ui::VKEY_CONTROL) &&
+ pending_requested_direction_ != base::i18n::UNKNOWN_DIRECTION) {
+ GetTextInputClient()->ChangeTextDirectionAndLayoutAlignment(
+ pending_requested_direction_);
+ pending_requested_direction_ = base::i18n::UNKNOWN_DIRECTION;
+ }
+ }
+
+ DispatchKeyEventPostIME(key);
+}
+
+void InputMethodWin::OnTextInputTypeChanged(View* view) {
+ if (IsViewFocused(view)) {
+ ime_input_.CancelIME(hwnd());
+ UpdateIMEState();
+ }
+ InputMethodBase::OnTextInputTypeChanged(view);
+}
+
+void InputMethodWin::OnCaretBoundsChanged(View* view) {
+ gfx::Rect rect;
+ if (!IsViewFocused(view) || !GetCaretBoundsInWidget(&rect))
+ return;
+ ime_input_.UpdateCaretRect(hwnd(), rect);
+}
+
+void InputMethodWin::CancelComposition(View* view) {
+ if (IsViewFocused(view))
+ ime_input_.CancelIME(hwnd());
+}
+
+std::string InputMethodWin::GetInputLocale() {
+ return locale_;
+}
+
+base::i18n::TextDirection InputMethodWin::GetInputTextDirection() {
+ return direction_;
+}
+
+bool InputMethodWin::IsActive() {
+ return active_;
+}
+
+LRESULT InputMethodWin::OnImeMessages(
+ UINT message, WPARAM w_param, LPARAM l_param, BOOL* handled) {
+ LRESULT result = 0;
+ switch (message) {
+ case WM_IME_SETCONTEXT:
+ result = OnImeSetContext(message, w_param, l_param, handled);
+ break;
+ case WM_IME_STARTCOMPOSITION:
+ result = OnImeStartComposition(message, w_param, l_param, handled);
+ break;
+ case WM_IME_COMPOSITION:
+ result = OnImeComposition(message, w_param, l_param, handled);
+ break;
+ case WM_IME_ENDCOMPOSITION:
+ result = OnImeEndComposition(message, w_param, l_param, handled);
+ break;
+ case WM_IME_REQUEST:
+ result = OnImeRequest(message, w_param, l_param, handled);
+ break;
+ case WM_CHAR:
+ case WM_SYSCHAR:
+ result = OnChar(message, w_param, l_param, handled);
+ break;
+ case WM_DEADCHAR:
+ case WM_SYSDEADCHAR:
+ result = OnDeadChar(message, w_param, l_param, handled);
+ break;
+ default:
+ NOTREACHED() << "Unknown IME message:" << message;
+ break;
+ }
+ return result;
+}
+
+void InputMethodWin::OnWillChangeFocus(View* focused_before, View* focused) {
+ ConfirmCompositionText();
+}
+
+void InputMethodWin::OnDidChangeFocus(View* focused_before, View* focused) {
+ UpdateIMEState();
+}
+
+void InputMethodWin::OnInputLangChange(DWORD character_set,
+ HKL input_language_id) {
+ active_ = ime_input_.SetInputLanguage();
+ locale_ = ime_input_.GetInputLanguageName();
+ direction_ = ime_input_.GetTextDirection();
+ OnInputMethodChanged();
+}
+
+LRESULT InputMethodWin::OnImeSetContext(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL* handled) {
+ active_ = (wparam == TRUE);
+ if (active_)
+ ime_input_.CreateImeWindow(hwnd());
+
+ OnInputMethodChanged();
+ return ime_input_.SetImeWindowStyle(hwnd(), message, wparam, lparam, handled);
+}
+
+LRESULT InputMethodWin::OnImeStartComposition(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL* handled) {
+ // We have to prevent WTL from calling ::DefWindowProc() because the function
+ // calls ::ImmSetCompositionWindow() and ::ImmSetCandidateWindow() to
+ // over-write the position of IME windows.
+ *handled = TRUE;
+
+ if (IsTextInputTypeNone())
+ return 0;
+
+ // Reset the composition status and create IME windows.
+ ime_input_.CreateImeWindow(hwnd());
+ ime_input_.ResetComposition(hwnd());
+ return 0;
+}
+
+LRESULT InputMethodWin::OnImeComposition(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL* handled) {
+ // We have to prevent WTL from calling ::DefWindowProc() because we do not
+ // want for the IMM (Input Method Manager) to send WM_IME_CHAR messages.
+ *handled = TRUE;
+
+ if (IsTextInputTypeNone())
+ return 0;
+
+ // At first, update the position of the IME window.
+ ime_input_.UpdateImeWindow(hwnd());
+
+ // Retrieve the result string and its attributes of the ongoing composition
+ // and send it to a renderer process.
+ ui::CompositionText composition;
+ if (ime_input_.GetResult(hwnd(), lparam, &composition.text)) {
+ GetTextInputClient()->InsertText(composition.text);
+ ime_input_.ResetComposition(hwnd());
+ // Fall though and try reading the composition string.
+ // Japanese IMEs send a message containing both GCS_RESULTSTR and
+ // GCS_COMPSTR, which means an ongoing composition has been finished
+ // by the start of another composition.
+ }
+ // Retrieve the composition string and its attributes of the ongoing
+ // composition and send it to a renderer process.
+ if (ime_input_.GetComposition(hwnd(), lparam, &composition))
+ GetTextInputClient()->SetCompositionText(composition);
+
+ return 0;
+}
+
+LRESULT InputMethodWin::OnImeEndComposition(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL* handled) {
+ // Let WTL call ::DefWindowProc() and release its resources.
+ *handled = FALSE;
+
+ if (IsTextInputTypeNone())
+ return 0;
+
+ if (GetTextInputClient()->HasCompositionText())
+ GetTextInputClient()->ClearCompositionText();
+
+ ime_input_.ResetComposition(hwnd());
+ ime_input_.DestroyImeWindow(hwnd());
+ return 0;
+}
+
+LRESULT InputMethodWin::OnImeRequest(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL* handled) {
+ *handled = FALSE;
+
+ // Should not receive WM_IME_REQUEST message, if IME is disabled.
+ const ui::TextInputType type = GetTextInputType();
+ if (type == ui::TEXT_INPUT_TYPE_NONE ||
+ type == ui::TEXT_INPUT_TYPE_PASSWORD) {
+ return 0;
+ }
+
+ switch (wparam) {
+ case IMR_RECONVERTSTRING:
+ *handled = TRUE;
+ return OnReconvertString(reinterpret_cast<RECONVERTSTRING*>(lparam));
+ case IMR_DOCUMENTFEED:
+ *handled = TRUE;
+ return OnDocumentFeed(reinterpret_cast<RECONVERTSTRING*>(lparam));
+ default:
+ return 0;
+ }
+}
+
+LRESULT InputMethodWin::OnChar(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL* handled) {
+ *handled = TRUE;
+
+ // We need to send character events to the focused text input client event if
+ // its text input type is ui::TEXT_INPUT_TYPE_NONE.
+ if (!GetTextInputClient())
+ return 0;
+
+ int flags = 0;
+ flags |= (::GetKeyState(VK_MENU) & 0x80)? ui::EF_ALT_DOWN : 0;
+ flags |= (::GetKeyState(VK_SHIFT) & 0x80)? ui::EF_SHIFT_DOWN : 0;
+ flags |= (::GetKeyState(VK_CONTROL) & 0x80)? ui::EF_CONTROL_DOWN : 0;
+ GetTextInputClient()->InsertChar(static_cast<char16>(wparam), flags);
+ return 0;
+}
+
+LRESULT InputMethodWin::OnDeadChar(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL* handled) {
+ *handled = TRUE;
+
+ if (IsTextInputTypeNone())
+ return 0;
+
+ // Shows the dead character as a composition text, so that the user can know
+ // what dead key was pressed.
+ ui::CompositionText composition;
+ composition.text.assign(1, static_cast<char16>(wparam));
+ composition.selection = ui::Range(0, 1);
+ composition.underlines.push_back(
+ ui::CompositionUnderline(0, 1, SK_ColorBLACK, false));
+ GetTextInputClient()->SetCompositionText(composition);
+ return 0;
+}
+
+LRESULT InputMethodWin::OnDocumentFeed(RECONVERTSTRING* reconv) {
+ ui::TextInputClient* client = GetTextInputClient();
+ if (!client)
+ return 0;
+
+ ui::Range text_range;
+ if (!client->GetTextRange(&text_range) || text_range.is_empty())
+ return 0;
+
+ bool result = false;
+ ui::Range target_range;
+ if (client->HasCompositionText())
+ result = client->GetCompositionTextRange(&target_range);
+
+ if (!result || target_range.is_empty()) {
+ if (!client->GetSelectionRange(&target_range) ||
+ !target_range.IsValid()) {
+ return 0;
+ }
+ }
+
+ if (!text_range.Contains(target_range))
+ return 0;
+
+ if (target_range.GetMin() - text_range.start() > kExtraNumberOfChars)
+ text_range.set_start(target_range.GetMin() - kExtraNumberOfChars);
+
+ if (text_range.end() - target_range.GetMax() > kExtraNumberOfChars)
+ text_range.set_end(target_range.GetMax() + kExtraNumberOfChars);
+
+ size_t len = text_range.length();
+ size_t need_size = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR);
+
+ if (!reconv)
+ return need_size;
+
+ if (reconv->dwSize < need_size)
+ return 0;
+
+ string16 text;
+ if (!GetTextInputClient()->GetTextFromRange(text_range, &text))
+ return 0;
+ DCHECK_EQ(text_range.length(), text.length());
+
+ reconv->dwVersion = 0;
+ reconv->dwStrLen = len;
+ reconv->dwStrOffset = sizeof(RECONVERTSTRING);
+ reconv->dwCompStrLen =
+ client->HasCompositionText() ? target_range.length() : 0;
+ reconv->dwCompStrOffset =
+ (target_range.GetMin() - text_range.start()) * sizeof(WCHAR);
+ reconv->dwTargetStrLen = target_range.length();
+ reconv->dwTargetStrOffset = reconv->dwCompStrOffset;
+
+ memcpy((char*)reconv + sizeof(RECONVERTSTRING),
+ text.c_str(), len * sizeof(WCHAR));
+
+ // According to Microsft API document, IMR_RECONVERTSTRING and
+ // IMR_DOCUMENTFEED should return reconv, but some applications return
+ // need_size.
+ return reinterpret_cast<LRESULT>(reconv);
+}
+
+LRESULT InputMethodWin::OnReconvertString(RECONVERTSTRING* reconv) {
+ ui::TextInputClient* client = GetTextInputClient();
+ if (!client)
+ return 0;
+
+ // If there is a composition string already, we don't allow reconversion.
+ if (client->HasCompositionText())
+ return 0;
+
+ ui::Range text_range;
+ if (!client->GetTextRange(&text_range) || text_range.is_empty())
+ return 0;
+
+ ui::Range selection_range;
+ if (!client->GetSelectionRange(&selection_range) ||
+ selection_range.is_empty()) {
+ return 0;
+ }
+
+ DCHECK(text_range.Contains(selection_range));
+
+ size_t len = selection_range.length();
+ size_t need_size = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR);
+
+ if (!reconv)
+ return need_size;
+
+ if (reconv->dwSize < need_size)
+ return 0;
+
+ // TODO(penghuang): Return some extra context to help improve IME's
+ // reconversion accuracy.
+ string16 text;
+ if (!GetTextInputClient()->GetTextFromRange(selection_range, &text))
+ return 0;
+ DCHECK_EQ(selection_range.length(), text.length());
+
+ reconv->dwVersion = 0;
+ reconv->dwStrLen = len;
+ reconv->dwStrOffset = sizeof(RECONVERTSTRING);
+ reconv->dwCompStrLen = len;
+ reconv->dwCompStrOffset = 0;
+ reconv->dwTargetStrLen = len;
+ reconv->dwTargetStrOffset = 0;
+
+ memcpy(reinterpret_cast<char*>(reconv) + sizeof(RECONVERTSTRING),
+ text.c_str(), len * sizeof(WCHAR));
+
+ // According to Microsft API document, IMR_RECONVERTSTRING and
+ // IMR_DOCUMENTFEED should return reconv, but some applications return
+ // need_size.
+ return reinterpret_cast<LRESULT>(reconv);
+}
+
+void InputMethodWin::ConfirmCompositionText() {
+ if (!IsTextInputTypeNone()) {
+ ime_input_.CleanupComposition(hwnd());
+ // Though above line should confirm the client's composition text by sending
+ // a result text to us, in case the input method and the client are in
+ // inconsistent states, we check the client's composition state again.
+ if (GetTextInputClient()->HasCompositionText())
+ GetTextInputClient()->ConfirmCompositionText();
+ }
+}
+
+void InputMethodWin::UpdateIMEState() {
+ // Use switch here in case we are going to add more text input types.
+ // We disable input method in password field.
+ switch (GetTextInputType()) {
+ case ui::TEXT_INPUT_TYPE_NONE:
+ case ui::TEXT_INPUT_TYPE_PASSWORD:
+ ime_input_.DisableIME(hwnd());
+ break;
+ default:
+ ime_input_.EnableIME(hwnd());
+ break;
+ }
+}
+
+} // namespace views
diff --git a/ui/views/ime/input_method_win.h b/ui/views/ime/input_method_win.h
new file mode 100644
index 0000000..6c4455f
--- /dev/null
+++ b/ui/views/ime/input_method_win.h
@@ -0,0 +1,105 @@
+// 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.
+
+#ifndef UI_VIEWS_IME_INPUT_METHOD_WIN_H_
+#define UI_VIEWS_IME_INPUT_METHOD_WIN_H_
+#pragma once
+
+#include <windows.h>
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ui/base/win/ime_input.h"
+#include "ui/views/ime/input_method_base.h"
+#include "views/view.h"
+#include "views/widget/widget.h"
+
+namespace views {
+
+// An InputMethod implementation based on Windows IMM32 API.
+class InputMethodWin : public InputMethodBase {
+ public:
+ explicit InputMethodWin(internal::InputMethodDelegate* delegate);
+ virtual ~InputMethodWin();
+
+ // Overridden from InputMethod:
+ virtual void Init(Widget* widget) OVERRIDE;
+ virtual void OnFocus() OVERRIDE;
+ virtual void OnBlur() OVERRIDE;
+ virtual void DispatchKeyEvent(const KeyEvent& key) OVERRIDE;
+ virtual void OnTextInputTypeChanged(View* view) OVERRIDE;
+ virtual void OnCaretBoundsChanged(View* view) OVERRIDE;
+ virtual void CancelComposition(View* view) OVERRIDE;
+ virtual std::string GetInputLocale() OVERRIDE;
+ virtual base::i18n::TextDirection GetInputTextDirection() OVERRIDE;
+ virtual bool IsActive() OVERRIDE;
+
+ // Handles IME messages.
+ LRESULT OnImeMessages(UINT message, WPARAM wparam, LPARAM lparam,
+ BOOL* handled);
+
+ // Message handlers. The native widget is responsible for forwarding following
+ // messages to the input method.
+ void OnInputLangChange(DWORD character_set, HKL input_language_id);
+
+ private:
+ LRESULT OnImeSetContext(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL* handled);
+ LRESULT OnImeStartComposition(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL* handled);
+ LRESULT OnImeComposition(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL* handled);
+ LRESULT OnImeEndComposition(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL* handled);
+ LRESULT OnImeRequest(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL* handled);
+ // For both WM_CHAR and WM_SYSCHAR
+ LRESULT OnChar(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL* handled);
+ // For both WM_DEADCHAR and WM_SYSDEADCHAR
+ LRESULT OnDeadChar(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL* handled);
+
+ LRESULT OnDocumentFeed(RECONVERTSTRING *reconv);
+ LRESULT OnReconvertString(RECONVERTSTRING *reconv);
+
+ // Overridden from InputMethodBase.
+ virtual void OnWillChangeFocus(View* focused_before, View* focused) OVERRIDE;
+ virtual void OnDidChangeFocus(View* focused_before, View* focused) OVERRIDE;
+
+ // A helper function to return the Widget's native window.
+ HWND hwnd() const { return widget()->GetNativeView(); }
+
+ // Asks the client to confirm current composition text.
+ void ConfirmCompositionText();
+
+ // Enables or disables the IME according to the current text input type.
+ void UpdateIMEState();
+
+ // Indicates if the current input locale has an IME.
+ bool active_;
+
+ // Name of the current input locale.
+ std::string locale_;
+
+ // The current input text direction.
+ base::i18n::TextDirection direction_;
+
+ // The new text direction and layout alignment requested by the user by
+ // pressing ctrl-shift. It'll be sent to the text input client when the key
+ // is released.
+ base::i18n::TextDirection pending_requested_direction_;
+
+ // Windows IMM32 wrapper.
+ // (See "ui/base/win/ime_input.h" for its details.)
+ ui::ImeInput ime_input_;
+
+ DISALLOW_COPY_AND_ASSIGN(InputMethodWin);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_IME_INPUT_METHOD_WIN_H_
diff --git a/ui/views/ime/mock_input_method.cc b/ui/views/ime/mock_input_method.cc
new file mode 100644
index 0000000..52a5912
--- /dev/null
+++ b/ui/views/ime/mock_input_method.cc
@@ -0,0 +1,173 @@
+// 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 "ui/views/ime/mock_input_method.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "ui/base/ime/text_input_client.h"
+#include "ui/base/keycodes/keyboard_codes.h"
+#include "views/events/event.h"
+#include "views/widget/widget.h"
+
+namespace views {
+
+MockInputMethod::MockInputMethod()
+ : composition_changed_(false),
+ focus_changed_(false),
+ text_input_type_changed_(false),
+ caret_bounds_changed_(false),
+ cancel_composition_called_(false),
+ locale_("en-US"),
+ direction_(base::i18n::LEFT_TO_RIGHT),
+ active_(true) {
+}
+
+MockInputMethod::MockInputMethod(internal::InputMethodDelegate* delegate)
+ : composition_changed_(false),
+ focus_changed_(false),
+ text_input_type_changed_(false),
+ caret_bounds_changed_(false),
+ cancel_composition_called_(false),
+ locale_("en-US"),
+ direction_(base::i18n::LEFT_TO_RIGHT),
+ active_(true) {
+ set_delegate(delegate);
+}
+
+MockInputMethod::~MockInputMethod() {
+}
+
+void MockInputMethod::Init(Widget* widget) {
+ InputMethodBase::Init(widget);
+}
+
+void MockInputMethod::DispatchKeyEvent(const KeyEvent& key) {
+ bool handled = (composition_changed_ || result_text_.length()) &&
+ !IsTextInputTypeNone();
+
+ ClearStates();
+ if (handled) {
+ KeyEvent mock_key(ui::ET_KEY_PRESSED, ui::VKEY_PROCESSKEY, key.flags());
+ DispatchKeyEventPostIME(mock_key);
+ } else {
+ DispatchKeyEventPostIME(key);
+ }
+
+ if (focus_changed_)
+ return;
+
+ ui::TextInputClient* client = GetTextInputClient();
+ if (client) {
+ if (handled) {
+ if (result_text_.length())
+ client->InsertText(result_text_);
+ if (composition_changed_) {
+ if (composition_.text.length())
+ client->SetCompositionText(composition_);
+ else
+ client->ClearCompositionText();
+ }
+ } else if (key.type() == ui::ET_KEY_PRESSED) {
+ char16 ch = key.GetCharacter();
+ client->InsertChar(ch, key.flags());
+ }
+ }
+
+ ClearResult();
+}
+
+void MockInputMethod::OnTextInputTypeChanged(View* view) {
+ if (IsViewFocused(view))
+ text_input_type_changed_ = true;
+ InputMethodBase::OnTextInputTypeChanged(view);
+}
+
+void MockInputMethod::OnCaretBoundsChanged(View* view) {
+ if (IsViewFocused(view))
+ caret_bounds_changed_ = true;
+}
+
+void MockInputMethod::CancelComposition(View* view) {
+ if (IsViewFocused(view)) {
+ cancel_composition_called_ = true;
+ ClearResult();
+ }
+}
+
+std::string MockInputMethod::GetInputLocale() {
+ return locale_;
+}
+
+base::i18n::TextDirection MockInputMethod::GetInputTextDirection() {
+ return direction_;
+}
+
+bool MockInputMethod::IsActive() {
+ return active_;
+}
+
+bool MockInputMethod::IsMock() const {
+ return true;
+}
+
+void MockInputMethod::OnWillChangeFocus(View* focused_before, View* focused) {
+ ui::TextInputClient* client = GetTextInputClient();
+ if (client && client->HasCompositionText())
+ client->ConfirmCompositionText();
+ focus_changed_ = true;
+ ClearResult();
+}
+
+void MockInputMethod::Clear() {
+ ClearStates();
+ ClearResult();
+}
+
+void MockInputMethod::SetCompositionTextForNextKey(
+ const ui::CompositionText& composition) {
+ composition_changed_ = true;
+ composition_ = composition;
+}
+
+void MockInputMethod::SetResultTextForNextKey(const string16& result) {
+ result_text_ = result;
+}
+
+void MockInputMethod::SetInputLocale(const std::string& locale) {
+ if (locale_ != locale) {
+ locale_ = locale;
+ OnInputMethodChanged();
+ }
+}
+
+void MockInputMethod::SetInputTextDirection(
+ base::i18n::TextDirection direction) {
+ if (direction_ != direction) {
+ direction_ = direction;
+ OnInputMethodChanged();
+ }
+}
+
+void MockInputMethod::SetActive(bool active) {
+ if (active_ != active) {
+ active_ = active;
+ OnInputMethodChanged();
+ }
+}
+
+void MockInputMethod::ClearStates() {
+ focus_changed_ = false;
+ text_input_type_changed_ = false;
+ caret_bounds_changed_ = false;
+ cancel_composition_called_ = false;
+}
+
+void MockInputMethod::ClearResult() {
+ composition_.Clear();
+ composition_changed_ = false;
+ result_text_.clear();
+}
+
+} // namespace views
diff --git a/ui/views/ime/mock_input_method.h b/ui/views/ime/mock_input_method.h
new file mode 100644
index 0000000..5384661
--- /dev/null
+++ b/ui/views/ime/mock_input_method.h
@@ -0,0 +1,88 @@
+// 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.
+
+#ifndef UI_VIEWS_IME_MOCK_INPUT_METHOD_H_
+#define UI_VIEWS_IME_MOCK_INPUT_METHOD_H_
+#pragma once
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ui/base/ime/composition_text.h"
+#include "ui/views/ime/input_method_base.h"
+#include "views/view.h"
+
+namespace views {
+
+// A mock InputMethod implementation for testing purpose.
+class VIEWS_EXPORT MockInputMethod : public InputMethodBase {
+ public:
+ MockInputMethod();
+ explicit MockInputMethod(internal::InputMethodDelegate* delegate);
+ virtual ~MockInputMethod();
+
+ // Overridden from InputMethod:
+ virtual void Init(Widget* widget) OVERRIDE;
+ virtual void DispatchKeyEvent(const KeyEvent& key) OVERRIDE;
+ virtual void OnTextInputTypeChanged(View* view) OVERRIDE;
+ virtual void OnCaretBoundsChanged(View* view) OVERRIDE;
+ virtual void CancelComposition(View* view) OVERRIDE;
+ virtual std::string GetInputLocale() OVERRIDE;
+ virtual base::i18n::TextDirection GetInputTextDirection() OVERRIDE;
+ virtual bool IsActive() OVERRIDE;
+ virtual bool IsMock() const OVERRIDE;
+
+ bool focus_changed() const { return focus_changed_; }
+ bool text_input_type_changed() const { return text_input_type_changed_; }
+ bool caret_bounds_changed() const { return caret_bounds_changed_; }
+ bool cancel_composition_called() const { return cancel_composition_called_; }
+
+ // Clears all internal states and result.
+ void Clear();
+
+ void SetCompositionTextForNextKey(const ui::CompositionText& composition);
+ void SetResultTextForNextKey(const string16& result);
+
+ void SetInputLocale(const std::string& locale);
+ void SetInputTextDirection(base::i18n::TextDirection direction);
+ void SetActive(bool active);
+
+ private:
+ // Overridden from InputMethodBase.
+ virtual void OnWillChangeFocus(View* focused_before, View* focused) OVERRIDE;
+
+ // Clears boolean states defined below.
+ void ClearStates();
+
+ // Clears only composition information and result text.
+ void ClearResult();
+
+ // Composition information for the next key event. It'll be cleared
+ // automatically after dispatching the next key event.
+ ui::CompositionText composition_;
+ bool composition_changed_;
+
+ // Result text for the next key event. It'll be cleared automatically after
+ // dispatching the next key event.
+ string16 result_text_;
+
+ // Record call state of corresponding methods. They will be set to false
+ // automatically before dispatching a key event.
+ bool focus_changed_;
+ bool text_input_type_changed_;
+ bool caret_bounds_changed_;
+ bool cancel_composition_called_;
+
+ // To mock corresponding input method prooperties.
+ std::string locale_;
+ base::i18n::TextDirection direction_;
+ bool active_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockInputMethod);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_IME_MOCK_INPUT_METHOD_H_
diff --git a/ui/views/ime/text_input_type_tracker.cc b/ui/views/ime/text_input_type_tracker.cc
new file mode 100644
index 0000000..1e54909
--- /dev/null
+++ b/ui/views/ime/text_input_type_tracker.cc
@@ -0,0 +1,37 @@
+// 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 "ui/views/ime/text_input_type_tracker.h"
+
+namespace views {
+
+void TextInputTypeTracker::AddTextInputTypeObserver(
+ TextInputTypeObserver* observer) {
+ text_input_type_observers_.AddObserver(observer);
+}
+
+void TextInputTypeTracker::RemoveTextInputTypeObserver(
+ TextInputTypeObserver* observer) {
+ text_input_type_observers_.RemoveObserver(observer);
+}
+
+void TextInputTypeTracker::OnTextInputTypeChanged(
+ ui::TextInputType type, Widget* widget) {
+ FOR_EACH_OBSERVER(TextInputTypeObserver,
+ text_input_type_observers_,
+ TextInputTypeChanged(type, widget));
+}
+
+TextInputTypeTracker::TextInputTypeTracker() {
+}
+
+TextInputTypeTracker::~TextInputTypeTracker() {
+}
+
+// static
+TextInputTypeTracker* TextInputTypeTracker::GetInstance() {
+ return Singleton<TextInputTypeTracker>::get();
+}
+
+} // namespace views
diff --git a/ui/views/ime/text_input_type_tracker.h b/ui/views/ime/text_input_type_tracker.h
new file mode 100644
index 0000000..04917ee
--- /dev/null
+++ b/ui/views/ime/text_input_type_tracker.h
@@ -0,0 +1,60 @@
+// 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.
+
+#ifndef UI_VIEWS_IME_TEXT_INPUT_TYPE_TRACKER_H_
+#define UI_VIEWS_IME_TEXT_INPUT_TYPE_TRACKER_H_
+#pragma once
+
+#include "base/memory/singleton.h"
+#include "base/observer_list.h"
+#include "ui/base/ime/text_input_type.h"
+#include "views/views_export.h"
+
+namespace views {
+
+class Widget;
+
+// This interface should be implemented by classes that want to be notified when
+// the text input type of the focused widget is changed or the focused widget is
+// changed.
+class TextInputTypeObserver {
+ public:
+ // This function will be called, when the text input type of focused |widget|
+ // is changed or the the widget is getting/losing focus.
+ virtual void TextInputTypeChanged(ui::TextInputType type, Widget* widget) = 0;
+
+ protected:
+ virtual ~TextInputTypeObserver() {}
+};
+
+// This class is for tracking the text input type of focused widget.
+class VIEWS_EXPORT TextInputTypeTracker {
+ public:
+ // Returns the singleton instance.
+ static TextInputTypeTracker* GetInstance();
+
+ // Adds/removes a TextInputTypeObserver |observer| to the set of
+ // active observers.
+ void AddTextInputTypeObserver(TextInputTypeObserver* observer);
+ void RemoveTextInputTypeObserver(TextInputTypeObserver* observer);
+
+ // views::InputMethod should call this function with new text input |type| and
+ // the |widget| associated with the input method, when the text input type
+ // is changed.
+ // Note: The widget should have focus or is losing focus.
+ void OnTextInputTypeChanged(ui::TextInputType type, Widget* widget);
+
+ private:
+ TextInputTypeTracker();
+ ~TextInputTypeTracker();
+
+ ObserverList<TextInputTypeObserver> text_input_type_observers_;
+
+ friend struct DefaultSingletonTraits<TextInputTypeTracker>;
+ DISALLOW_COPY_AND_ASSIGN(TextInputTypeTracker);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_IME_TEXT_INPUT_TYPE_TRACKER_H_
diff --git a/ui/views/test/test_views_delegate.cc b/ui/views/test/test_views_delegate.cc
new file mode 100644
index 0000000..086e987
--- /dev/null
+++ b/ui/views/test/test_views_delegate.cc
@@ -0,0 +1,51 @@
+// 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 "ui/views/test/test_views_delegate.h"
+
+#include "base/logging.h"
+#include "ui/base/clipboard/clipboard.h"
+
+namespace views {
+
+TestViewsDelegate::TestViewsDelegate()
+ : default_parent_view_(NULL) {
+ DCHECK(!ViewsDelegate::views_delegate);
+ ViewsDelegate::views_delegate = this;
+}
+
+TestViewsDelegate::~TestViewsDelegate() {
+ ViewsDelegate::views_delegate = NULL;
+}
+
+ui::Clipboard* TestViewsDelegate::GetClipboard() const {
+ if (!clipboard_.get()) {
+ // Note that we need a MessageLoop for the next call to work.
+ clipboard_.reset(new ui::Clipboard);
+ }
+ return clipboard_.get();
+}
+
+View* TestViewsDelegate::GetDefaultParentView() {
+ return default_parent_view_;
+}
+
+void TestViewsDelegate::SaveWindowPlacement(const Widget* window,
+ const std::string& window_name,
+ const gfx::Rect& bounds,
+ ui::WindowShowState show_state) {
+}
+
+bool TestViewsDelegate::GetSavedWindowPlacement(
+ const std::string& window_name,
+ gfx::Rect* bounds,
+ ui:: WindowShowState* show_state) const {
+ return false;
+}
+
+int TestViewsDelegate::GetDispositionForEvent(int event_flags) {
+ return 0;
+}
+
+} // namespace views
diff --git a/ui/views/test/test_views_delegate.h b/ui/views/test/test_views_delegate.h
new file mode 100644
index 0000000..eece1f1
--- /dev/null
+++ b/ui/views/test/test_views_delegate.h
@@ -0,0 +1,72 @@
+// 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.
+
+#ifndef UI_VIEWS_TEST_TEST_VIEWS_DELEGATE_H_
+#define UI_VIEWS_TEST_TEST_VIEWS_DELEGATE_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "build/build_config.h"
+#include "ui/base/accessibility/accessibility_types.h"
+#include "views/views_delegate.h"
+
+namespace ui {
+class Clipboard;
+}
+
+namespace views {
+class View;
+class Widget;
+
+class TestViewsDelegate : public ViewsDelegate {
+ public:
+ TestViewsDelegate();
+ virtual ~TestViewsDelegate();
+
+ void set_default_parent_view(View* view) {
+ default_parent_view_ = view;
+ }
+
+ // Overridden from ViewsDelegate:
+ virtual ui::Clipboard* GetClipboard() const OVERRIDE;
+ virtual View* GetDefaultParentView() OVERRIDE;
+ virtual void SaveWindowPlacement(const Widget* window,
+ const std::string& window_name,
+ const gfx::Rect& bounds,
+ ui::WindowShowState show_state) OVERRIDE;
+ virtual bool GetSavedWindowPlacement(
+ const std::string& window_name,
+ gfx::Rect* bounds,
+ ui::WindowShowState* show_state) const OVERRIDE;
+
+ virtual void NotifyAccessibilityEvent(
+ View* view, ui::AccessibilityTypes::Event event_type) OVERRIDE {}
+
+ virtual void NotifyMenuItemFocused(const string16& menu_name,
+ const string16& menu_item_name,
+ int item_index,
+ int item_count,
+ bool has_submenu) OVERRIDE {}
+#if defined(OS_WIN)
+ virtual HICON GetDefaultWindowIcon() const OVERRIDE {
+ return NULL;
+ }
+#endif
+
+ virtual void AddRef() OVERRIDE {}
+ virtual void ReleaseRef() OVERRIDE {}
+
+ virtual int GetDispositionForEvent(int event_flags) OVERRIDE;
+
+ private:
+ View* default_parent_view_;
+ mutable scoped_ptr<ui::Clipboard> clipboard_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestViewsDelegate);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_TEST_TEST_VIEWS_DELEGATE_H_
diff --git a/ui/views/test/views_test_base.cc b/ui/views/test/views_test_base.cc
new file mode 100644
index 0000000..b427004
--- /dev/null
+++ b/ui/views/test/views_test_base.cc
@@ -0,0 +1,64 @@
+// 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 "ui/views/test/views_test_base.h"
+
+#if defined(OS_WIN)
+#include <ole2.h>
+#endif
+
+#if defined(USE_AURA)
+#include "ui/aura/desktop.h"
+#include "ui/aura/test/test_stacking_client.h"
+#endif
+
+namespace views {
+
+ViewsTestBase::ViewsTestBase()
+ : setup_called_(false),
+ teardown_called_(false) {
+#if defined(OS_WIN)
+ OleInitialize(NULL);
+#endif
+#if defined(USE_AURA)
+ new aura::test::TestStackingClient;
+#endif
+}
+
+ViewsTestBase::~ViewsTestBase() {
+#if defined(OS_WIN)
+ OleUninitialize();
+#endif
+ CHECK(setup_called_)
+ << "You have overridden SetUp but never called super class's SetUp";
+ CHECK(teardown_called_)
+ << "You have overrideen TearDown but never called super class's TearDown";
+}
+
+void ViewsTestBase::SetUp() {
+ testing::Test::SetUp();
+ setup_called_ = true;
+ if (!views_delegate_.get())
+ views_delegate_.reset(new TestViewsDelegate());
+}
+
+void ViewsTestBase::TearDown() {
+ // Flush the message loop because we have pending release tasks
+ // and these tasks if un-executed would upset Valgrind.
+ RunPendingMessages();
+ teardown_called_ = true;
+ views_delegate_.reset();
+ testing::Test::TearDown();
+}
+
+void ViewsTestBase::RunPendingMessages() {
+#if defined(USE_AURA)
+ message_loop_.RunAllPendingWithDispatcher(
+ aura::Desktop::GetInstance()->GetDispatcher());
+#else
+ message_loop_.RunAllPending();
+#endif
+}
+
+} // namespace views
diff --git a/ui/views/test/views_test_base.h b/ui/views/test/views_test_base.h
new file mode 100644
index 0000000..51a9df6
--- /dev/null
+++ b/ui/views/test/views_test_base.h
@@ -0,0 +1,49 @@
+// 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.
+
+#ifndef UI_VIEWS_TEST_VIEWS_TEST_BASE_H_
+#define UI_VIEWS_TEST_VIEWS_TEST_BASE_H_
+#pragma once
+
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/views/test/test_views_delegate.h"
+
+namespace views {
+
+class TestViewsDelegate;
+
+// A base class for views unit test. It creates a message loop necessary
+// to drive UI events and takes care of OLE initialization for windows.
+class ViewsTestBase : public testing::Test {
+ public:
+ ViewsTestBase();
+ virtual ~ViewsTestBase();
+
+ // testing::Test:
+ virtual void SetUp() OVERRIDE;
+ virtual void TearDown() OVERRIDE;
+
+ void RunPendingMessages();
+
+ protected:
+ TestViewsDelegate& views_delegate() const { return *views_delegate_.get(); }
+
+ void set_views_delegate(TestViewsDelegate* views_delegate) {
+ views_delegate_.reset(views_delegate);
+ }
+
+ private:
+ MessageLoopForUI message_loop_;
+ scoped_ptr<TestViewsDelegate> views_delegate_;
+ bool setup_called_;
+ bool teardown_called_;
+
+ DISALLOW_COPY_AND_ASSIGN(ViewsTestBase);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_TEST_VIEWS_TEST_BASE_H_
diff --git a/ui/views/touchui/touch_selection_controller_impl_unittest.cc b/ui/views/touchui/touch_selection_controller_impl_unittest.cc
index 55ff3d7..62fc00f 100644
--- a/ui/views/touchui/touch_selection_controller_impl_unittest.cc
+++ b/ui/views/touchui/touch_selection_controller_impl_unittest.cc
@@ -6,11 +6,11 @@
#include "ui/gfx/point.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/render_text.h"
+#include "ui/views/test/views_test_base.h"
#include "ui/views/touchui/touch_selection_controller.h"
#include "ui/views/touchui/touch_selection_controller_impl.h"
#include "views/controls/textfield/native_textfield_views.h"
#include "views/controls/textfield/textfield.h"
-#include "views/test/views_test_base.h"
#include "views/widget/widget.h"
namespace views {