diff options
author | tfarina@chromium.org <tfarina@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-11-17 00:46:42 +0000 |
---|---|---|
committer | tfarina@chromium.org <tfarina@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-11-17 00:46:42 +0000 |
commit | d9646d2805844990e1c9d79d69c06fd502fb5783 (patch) | |
tree | 5086f93061d3a3dc0dbe1472410b767ba38249b9 /ui | |
parent | 97c29575ab1cc3b1bcb53449c3d9627ee1382d04 (diff) | |
download | chromium_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')
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 { |