diff options
author | suzhe@google.com <suzhe@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-04-01 22:05:14 +0000 |
---|---|---|
committer | suzhe@google.com <suzhe@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-04-01 22:05:14 +0000 |
commit | 6c8a4312deb82e599b2c9695b2ec22aa1e298271 (patch) | |
tree | b524c5d2d193ca166da62b4906d19e899a850f47 /views | |
parent | 38788d94bc411c89d6a0bbebe6e0628e401e43e4 (diff) | |
download | chromium_src-6c8a4312deb82e599b2c9695b2ec22aa1e298271.zip chromium_src-6c8a4312deb82e599b2c9695b2ec22aa1e298271.tar.gz chromium_src-6c8a4312deb82e599b2c9695b2ec22aa1e298271.tar.bz2 |
Integrate the new input method API for Views into Chromium.
This CL contains following changes:
1. Improve TextfieldViewsModel to support composition text.
2. Implement TextInputClient interface in NativeTextfieldViews.
3. Fix Textfield and native implementations to avoid calling TextfieldController::ContentsChanged() when the text is changed by calling Textfield::SetText() or AppendText().
4. Fix a bug in FocusManager that causes NativeTextfieldViewsTest.FocusTraversalTest to fail.
5. Fix accelerator_handler_touch.cc to send key events to the top-level widget's input method instance instead of the root view.
6. Do the same fix in extension_input_api.cc.
7. Hook InputMethod into WidgetGtk and WidgetWin.
BUG=75003
TEST=views_unittests --gtest_filter=NativeTextfieldViews*
Review URL: http://codereview.chromium.org/6675005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@80226 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'views')
24 files changed, 1439 insertions, 323 deletions
diff --git a/views/controls/textfield/native_textfield_gtk.cc b/views/controls/textfield/native_textfield_gtk.cc index ec7c271..77b3940 100644 --- a/views/controls/textfield/native_textfield_gtk.cc +++ b/views/controls/textfield/native_textfield_gtk.cc @@ -376,6 +376,10 @@ void NativeTextfieldGtk::HandleFocus() { void NativeTextfieldGtk::HandleBlur() { } +TextInputClient* NativeTextfieldGtk::GetTextInputClient() { + return NULL; +} + // static gboolean NativeTextfieldGtk::OnKeyPressEventHandler( GtkWidget* widget, @@ -405,18 +409,16 @@ gboolean NativeTextfieldGtk::OnActivate() { if (!event || event->type != GDK_KEY_PRESS) return false; - GdkEventKey* key_event = reinterpret_cast<GdkEventKey*>(event); + KeyEvent views_key_event(event); gboolean handled = false; TextfieldController* controller = textfield_->GetController(); - if (controller) { - KeyEvent views_key_event(event); + if (controller) handled = controller->HandleKeyEvent(textfield_, views_key_event); - } WidgetGtk* widget = static_cast<WidgetGtk*>(GetWidget()); if (!handled && widget) - handled = widget->HandleKeyboardEvent(key_event); + handled = widget->HandleKeyboardEvent(views_key_event); return handled; } @@ -430,9 +432,6 @@ gboolean NativeTextfieldGtk::OnChangedHandler( gboolean NativeTextfieldGtk::OnChanged() { textfield_->SyncText(); - TextfieldController* controller = textfield_->GetController(); - if (controller) - controller->ContentsChanged(textfield_, GetText()); textfield_->GetWidget()->NotifyAccessibilityEvent( textfield_, ui::AccessibilityTypes::EVENT_TEXT_CHANGED, true); return false; diff --git a/views/controls/textfield/native_textfield_gtk.h b/views/controls/textfield/native_textfield_gtk.h index ee728e5..b28dd2e 100644 --- a/views/controls/textfield/native_textfield_gtk.h +++ b/views/controls/textfield/native_textfield_gtk.h @@ -55,6 +55,7 @@ class NativeTextfieldGtk : public NativeControlGtk, virtual bool HandleKeyReleased(const views::KeyEvent& e) OVERRIDE; virtual void HandleFocus() OVERRIDE; virtual void HandleBlur() OVERRIDE; + virtual TextInputClient* GetTextInputClient() OVERRIDE; // Overridden from NativeControlGtk: virtual void CreateNativeControl() OVERRIDE; diff --git a/views/controls/textfield/native_textfield_views.cc b/views/controls/textfield/native_textfield_views.cc index e1b8ccc..73b95f3 100644 --- a/views/controls/textfield/native_textfield_views.cc +++ b/views/controls/textfield/native_textfield_views.cc @@ -23,6 +23,7 @@ #include "views/controls/textfield/textfield_controller.h" #include "views/controls/textfield/textfield_views_model.h" #include "views/events/event.h" +#include "views/ime/input_method.h" #include "views/metrics.h" #include "views/views_delegate.h" @@ -61,11 +62,12 @@ const char NativeTextfieldViews::kViewClassName[] = NativeTextfieldViews::NativeTextfieldViews(Textfield* parent) : textfield_(parent), - model_(new TextfieldViewsModel()), + ALLOW_THIS_IN_INITIALIZER_LIST(model_(new TextfieldViewsModel(this))), text_border_(new TextfieldBorder()), text_offset_(0), insert_(true), is_cursor_visible_(false), + skip_input_method_cancel_composition_(false), ALLOW_THIS_IN_INITIALIZER_LIST(cursor_timer_(this)), last_mouse_press_time_(base::Time::FromInternalValue(0)), click_state_(NONE) { @@ -160,14 +162,9 @@ string16 NativeTextfieldViews::GetText() const { } void NativeTextfieldViews::UpdateText() { - bool changed = model_->SetText(textfield_->text()); + model_->SetText(textfield_->text()); UpdateCursorBoundsAndTextOffset(); SchedulePaint(); - if (changed) { - TextfieldController* controller = textfield_->GetController(); - if (controller) - controller->ContentsChanged(textfield_, GetText()); - } } void NativeTextfieldViews::AppendText(const string16& text) { @@ -176,10 +173,6 @@ void NativeTextfieldViews::AppendText(const string16& text) { model_->Append(text); UpdateCursorBoundsAndTextOffset(); SchedulePaint(); - - TextfieldController* controller = textfield_->GetController(); - if (controller) - controller->ContentsChanged(textfield_, GetText()); } string16 NativeTextfieldViews::GetSelectedText() const { @@ -220,6 +213,7 @@ void NativeTextfieldViews::UpdateBackgroundColor() { void NativeTextfieldViews::UpdateReadOnly() { SchedulePaint(); + OnTextInputTypeChanged(); } void NativeTextfieldViews::UpdateFont() { @@ -230,11 +224,13 @@ void NativeTextfieldViews::UpdateIsPassword() { model_->set_is_password(textfield_->IsPassword()); UpdateCursorBoundsAndTextOffset(); SchedulePaint(); + OnTextInputTypeChanged(); } void NativeTextfieldViews::UpdateEnabled() { SetEnabled(textfield_->IsEnabled()); SchedulePaint(); + OnTextInputTypeChanged(); } gfx::Insets NativeTextfieldViews::CalculateInsets() { @@ -275,7 +271,7 @@ gfx::NativeView NativeTextfieldViews::GetTestingHandle() const { } bool NativeTextfieldViews::IsIMEComposing() const { - return false; + return model_->HasCompositionText(); } void NativeTextfieldViews::GetSelectedRange(ui::Range* range) const { @@ -307,6 +303,7 @@ bool NativeTextfieldViews::HandleKeyReleased(const views::KeyEvent& e) { void NativeTextfieldViews::HandleFocus() { is_cursor_visible_ = true; SchedulePaint(); + OnCaretBoundsChanged(); // Start blinking cursor. MessageLoop::current()->PostDelayedTask( FROM_HERE, @@ -323,6 +320,10 @@ void NativeTextfieldViews::HandleBlur() { } } +TextInputClient* NativeTextfieldViews::GetTextInputClient() { + return textfield_->read_only() ? NULL : this; +} + ///////////////////////////////////////////////////////////////// // NativeTextfieldViews, ui::SimpleMenuModel::Delegate overrides: @@ -412,7 +413,174 @@ void NativeTextfieldViews::OnBoundsChanged(const gfx::Rect& previous_bounds) { /////////////////////////////////////////////////////////////////////////////// -// NativeTextfieldViews private: +// NativeTextfieldViews, TextInputClient implementation, private: + +void NativeTextfieldViews::SetCompositionText( + const ui::CompositionText& composition) { + if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE) + return; + + OnBeforeUserAction(); + skip_input_method_cancel_composition_ = true; + model_->SetCompositionText(composition); + skip_input_method_cancel_composition_ = false; + UpdateAfterChange(true, true); + OnAfterUserAction(); +} + +void NativeTextfieldViews::ConfirmCompositionText() { + if (!model_->HasCompositionText()) + return; + + OnBeforeUserAction(); + skip_input_method_cancel_composition_ = true; + model_->ConfirmCompositionText(); + skip_input_method_cancel_composition_ = false; + UpdateAfterChange(true, true); + OnAfterUserAction(); +} + +void NativeTextfieldViews::ClearCompositionText() { + if (!model_->HasCompositionText()) + return; + + OnBeforeUserAction(); + skip_input_method_cancel_composition_ = true; + model_->ClearCompositionText(); + skip_input_method_cancel_composition_ = false; + UpdateAfterChange(true, true); + OnAfterUserAction(); +} + +void NativeTextfieldViews::InsertText(const string16& text) { + // TODO(suzhe): Filter invalid characters. + if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE || text.empty()) + return; + + OnBeforeUserAction(); + skip_input_method_cancel_composition_ = true; + if (insert_) + model_->InsertText(text); + else + model_->ReplaceText(text); + skip_input_method_cancel_composition_ = false; + UpdateAfterChange(true, true); + OnAfterUserAction(); +} + +void NativeTextfieldViews::InsertChar(char16 ch, int flags) { + if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE || + !ShouldInsertChar(ch, flags)) { + return; + } + + OnBeforeUserAction(); + skip_input_method_cancel_composition_ = true; + if (insert_) + model_->InsertChar(ch); + else + model_->ReplaceChar(ch); + skip_input_method_cancel_composition_ = false; + UpdateAfterChange(true, true); + OnAfterUserAction(); +} + +ui::TextInputType NativeTextfieldViews::GetTextInputType() { + if (textfield_->read_only() || !textfield_->IsEnabled()) + return ui::TEXT_INPUT_TYPE_NONE; + else if (textfield_->IsPassword()) + return ui::TEXT_INPUT_TYPE_PASSWORD; + return ui::TEXT_INPUT_TYPE_TEXT; +} + +gfx::Rect NativeTextfieldViews::GetCaretBounds() { + return cursor_bounds_; +} + +bool NativeTextfieldViews::HasCompositionText() { + return model_->HasCompositionText(); +} + +bool NativeTextfieldViews::GetTextRange(ui::Range* range) { + // We don't allow the input method to retrieve or delete content from a + // password box. + if (GetTextInputType() != ui::TEXT_INPUT_TYPE_TEXT) + return false; + + model_->GetTextRange(range); + return true; +} + +bool NativeTextfieldViews::GetCompositionTextRange(ui::Range* range) { + if (GetTextInputType() != ui::TEXT_INPUT_TYPE_TEXT) + return false; + + model_->GetCompositionTextRange(range); + return true; +} + +bool NativeTextfieldViews::GetSelectionRange(ui::Range* range) { + if (GetTextInputType() != ui::TEXT_INPUT_TYPE_TEXT) + return false; + + model_->GetSelectedRange(range); + return true; +} + +bool NativeTextfieldViews::SetSelectionRange(const ui::Range& range) { + if (GetTextInputType() != ui::TEXT_INPUT_TYPE_TEXT || !range.IsValid()) + return false; + + OnBeforeUserAction(); + SelectRange(range); + OnAfterUserAction(); + return true; +} + +bool NativeTextfieldViews::DeleteRange(const ui::Range& range) { + if (GetTextInputType() != ui::TEXT_INPUT_TYPE_TEXT || range.is_empty()) + return false; + + OnBeforeUserAction(); + model_->SelectRange(range); + if (model_->HasSelection()) { + model_->DeleteSelection(); + UpdateAfterChange(true, true); + } + OnAfterUserAction(); + return true; +} + +bool NativeTextfieldViews::GetTextFromRange( + const ui::Range& range, + const base::Callback<void(const string16&)>& callback) { + if (GetTextInputType() != ui::TEXT_INPUT_TYPE_TEXT || range.is_empty()) + return false; + + callback.Run(model_->GetTextFromRange(range)); + return true; +} + +void NativeTextfieldViews::OnInputMethodChanged() { + NOTIMPLEMENTED(); +} + +bool NativeTextfieldViews::ChangeTextDirectionAndLayoutAlignment( + base::i18n::TextDirection direction) { + NOTIMPLEMENTED(); + return false; +} + +View* NativeTextfieldViews::GetOwnerViewOfTextInputClient() { + return textfield_; +} + +void NativeTextfieldViews::OnCompositionTextConfirmedOrCleared() { + if (skip_input_method_cancel_composition_) + return; + DCHECK(textfield_->GetInputMethod()); + textfield_->GetInputMethod()->CancelComposition(textfield_); +} const gfx::Font& NativeTextfieldViews::GetFont() const { return textfield_->font(); @@ -475,6 +643,8 @@ void NativeTextfieldViews::UpdateCursorBoundsAndTextOffset() { } // shift cursor bounds to fit insets. cursor_bounds_.set_x(cursor_bounds_.x() + text_offset_ + insets.left()); + + OnCaretBoundsChanged(); } void NativeTextfieldViews::PaintTextAndCursor(gfx::Canvas* canvas) { @@ -501,17 +671,22 @@ void NativeTextfieldViews::PaintTextAndCursor(gfx::Canvas* canvas) { fragments.begin(); iter != fragments.end(); iter++) { - string16 text = model_->GetVisibleText((*iter).begin, (*iter).end); + string16 text = model_->GetVisibleText(iter->start, iter->end); + + gfx::Font font = GetFont(); + if (iter->underline) + font = font.DeriveFont(0, font.GetStyle() | gfx::Font::UNDERLINED); + // TODO(oshima): This does not give the accurate position due to // kerning. Figure out how webkit does this with skia. - int width = GetFont().GetStringWidth(text); + int width = font.GetStringWidth(text); - if ((*iter).selected) { + if (iter->selected) { canvas->FillRectInt(selection_color, x_offset, y, width, text_height); - canvas->DrawStringInt(text, GetFont(), kSelectedTextColor, + canvas->DrawStringInt(text, font, kSelectedTextColor, x_offset, y, width, text_height); } else { - canvas->DrawStringInt(text, GetFont(), text_color, + canvas->DrawStringInt(text, font, text_color, x_offset, y, width, text_height); } x_offset += width; @@ -577,7 +752,7 @@ bool NativeTextfieldViews::HandleKeyEvent(const KeyEvent& key_event) { cursor_changed = true; break; case ui::VKEY_HOME: - model_->MoveCursorToStart(selection); + model_->MoveCursorToHome(selection); cursor_changed = true; break; case ui::VKEY_BACK: @@ -590,7 +765,7 @@ bool NativeTextfieldViews::HandleKeyEvent(const KeyEvent& key_event) { #if defined(OS_WIN) break; #else - model_->MoveCursorToStart(true); + model_->MoveCursorToHome(true); #endif } else if (control) { // If only control is pressed, then erase the previous word. @@ -626,14 +801,9 @@ bool NativeTextfieldViews::HandleKeyEvent(const KeyEvent& key_event) { default: break; } - char16 ch = key_event.GetCharacter(); - if (editable && ShouldInsertChar(ch, key_event.flags())) { - if (insert_) - model_->Insert(ch); - else - model_->Replace(ch); - text_changed = true; - } + + // We must have input method in order to support text input. + DCHECK(textfield_->GetInputMethod()); UpdateAfterChange(text_changed, cursor_changed); OnAfterUserAction(); @@ -717,9 +887,6 @@ void NativeTextfieldViews::SetCursorForMouseClick(const views::MouseEvent& e) { void NativeTextfieldViews::PropagateTextChange() { textfield_->SyncText(); - TextfieldController* controller = textfield_->GetController(); - if (controller) - controller->ContentsChanged(textfield_, GetText()); } void NativeTextfieldViews::UpdateAfterChange(bool text_changed, @@ -750,6 +917,16 @@ void NativeTextfieldViews::InitContextMenuIfRequired() { context_menu_menu_.reset(new Menu2(context_menu_contents_.get())); } +void NativeTextfieldViews::OnTextInputTypeChanged() { + DCHECK(textfield_->GetInputMethod()); + textfield_->GetInputMethod()->OnTextInputTypeChanged(textfield_); +} + +void NativeTextfieldViews::OnCaretBoundsChanged() { + DCHECK(textfield_->GetInputMethod()); + textfield_->GetInputMethod()->OnCaretBoundsChanged(textfield_); +} + void NativeTextfieldViews::OnBeforeUserAction() { TextfieldController* controller = textfield_->GetController(); if (controller) diff --git a/views/controls/textfield/native_textfield_views.h b/views/controls/textfield/native_textfield_views.h index c91281b..68ffbb4 100644 --- a/views/controls/textfield/native_textfield_views.h +++ b/views/controls/textfield/native_textfield_views.h @@ -12,6 +12,8 @@ #include "ui/gfx/font.h" #include "views/border.h" #include "views/controls/textfield/native_textfield_wrapper.h" +#include "views/controls/textfield/textfield_views_model.h" +#include "views/ime/text_input_client.h" #include "views/view.h" namespace base { @@ -26,7 +28,6 @@ namespace views { class KeyEvent; class Menu2; -class TextfieldViewsModel; // A views/skia only implementation of NativeTextfieldWrapper. // No platform specific code is used. @@ -41,7 +42,9 @@ class TextfieldViewsModel; class NativeTextfieldViews : public views::View, public views::ContextMenuController, public NativeTextfieldWrapper, - public ui::SimpleMenuModel::Delegate { + public ui::SimpleMenuModel::Delegate, + public TextInputClient, + public TextfieldViewsModel::Delegate { public: explicit NativeTextfieldViews(Textfield* parent); ~NativeTextfieldViews(); @@ -90,6 +93,7 @@ class NativeTextfieldViews : public views::View, virtual bool HandleKeyReleased(const views::KeyEvent& e) OVERRIDE; virtual void HandleFocus() OVERRIDE; virtual void HandleBlur() OVERRIDE; + virtual TextInputClient* GetTextInputClient() OVERRIDE; // ui::SimpleMenuModel::Delegate overrides virtual bool IsCommandIdChecked(int command_id) const OVERRIDE; @@ -147,6 +151,32 @@ class NativeTextfieldViews : public views::View, DISALLOW_COPY_AND_ASSIGN(TextfieldBorder); }; + // Overridden from TextInputClient: + virtual void SetCompositionText( + const ui::CompositionText& composition) OVERRIDE; + virtual void ConfirmCompositionText() OVERRIDE; + virtual void ClearCompositionText() OVERRIDE; + virtual void InsertText(const string16& text) OVERRIDE; + virtual void InsertChar(char16 ch, int flags) OVERRIDE; + virtual ui::TextInputType GetTextInputType() OVERRIDE; + virtual gfx::Rect GetCaretBounds() OVERRIDE; + virtual bool HasCompositionText() OVERRIDE; + virtual bool GetTextRange(ui::Range* range) OVERRIDE; + virtual bool GetCompositionTextRange(ui::Range* range) OVERRIDE; + virtual bool GetSelectionRange(ui::Range* range) OVERRIDE; + virtual bool SetSelectionRange(const ui::Range& range) OVERRIDE; + virtual bool DeleteRange(const ui::Range& range) OVERRIDE; + virtual bool GetTextFromRange( + const ui::Range& range, + const base::Callback<void(const string16&)>& callback) OVERRIDE; + virtual void OnInputMethodChanged() OVERRIDE; + virtual bool ChangeTextDirectionAndLayoutAlignment( + base::i18n::TextDirection direction) OVERRIDE; + virtual View* GetOwnerViewOfTextInputClient() OVERRIDE; + + // Overridden from TextfieldViewsModel::Delegate: + virtual void OnCompositionTextConfirmedOrCleared() OVERRIDE; + // Returns the Textfield's font. const gfx::Font& GetFont() const; @@ -188,6 +218,12 @@ class NativeTextfieldViews : public views::View, // Utility function to create the context menu if one does not already exist. void InitContextMenuIfRequired(); + // Convenience method to call InputMethod::OnTextInputTypeChanged(); + void OnTextInputTypeChanged(); + + // Convenience method to call InputMethod::OnCaretBoundsChanged(); + void OnCaretBoundsChanged(); + // Convenience method to call TextfieldController::OnBeforeUserAction(); void OnBeforeUserAction(); @@ -219,6 +255,9 @@ class NativeTextfieldViews : public views::View, // The drawing state of cursor. True to draw. bool is_cursor_visible_; + // True if InputMethod::CancelComposition() should not be called. + bool skip_input_method_cancel_composition_; + // A runnable method factory for callback to update the cursor. ScopedRunnableMethodFactory<NativeTextfieldViews> cursor_timer_; diff --git a/views/controls/textfield/native_textfield_views_unittest.cc b/views/controls/textfield/native_textfield_views_unittest.cc index 7e2bab4..92f0e25 100644 --- a/views/controls/textfield/native_textfield_views_unittest.cc +++ b/views/controls/textfield/native_textfield_views_unittest.cc @@ -3,6 +3,9 @@ // found in the LICENSE file. #include "base/auto_reset.h" +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/callback.h" #include "base/message_loop.h" #include "base/utf_string_conversions.h" #include "testing/gtest/include/gtest/gtest.h" @@ -16,11 +19,74 @@ #include "views/controls/textfield/textfield_views_model.h" #include "views/events/event.h" #include "views/focus/focus_manager.h" +#include "views/ime/mock_input_method.h" +#include "views/ime/text_input_client.h" #include "views/test/test_views_delegate.h" #include "views/test/views_test_base.h" #include "views/views_delegate.h" +#include "views/widget/native_widget.h" #include "views/widget/widget.h" +namespace { + +// A wrapper of Textfield to intercept the result of OnKeyPressed() and +// OnKeyReleased() methods. +class TestTextfield : public views::Textfield { + public: + TestTextfield() + : key_handled_(false), + key_received_(false) { + } + + explicit TestTextfield(StyleFlags style) + : Textfield(style), + key_handled_(false), + key_received_(false) { + } + + virtual bool OnKeyPressed(const views::KeyEvent& e) OVERRIDE { + key_received_ = true; + key_handled_ = views::Textfield::OnKeyPressed(e); + return key_handled_; + } + + virtual bool OnKeyReleased(const views::KeyEvent& e) OVERRIDE { + key_received_ = true; + key_handled_ = views::Textfield::OnKeyReleased(e); + return key_handled_; + } + + bool key_handled() const { return key_handled_; } + bool key_received() const { return key_received_; } + + void clear() { + key_received_ = key_handled_ = false; + } + + private: + bool key_handled_; + bool key_received_; + + DISALLOW_COPY_AND_ASSIGN(TestTextfield); +}; + +// A helper class for use with TextInputClient::GetTextFromRange(). +class GetTextHelper { + public: + GetTextHelper() { + } + + void set_text(const string16& text) { text_ = text; } + const string16& text() const { return text_; } + + private: + string16 text_; + + DISALLOW_COPY_AND_ASSIGN(GetTextHelper); +}; + +} // namespace + namespace views { // Convert to Wide so that the printed string will be readable when @@ -40,7 +106,10 @@ class NativeTextfieldViewsTest : public ViewsTestBase, : widget_(NULL), textfield_(NULL), textfield_view_(NULL), - model_(NULL) { + model_(NULL), + input_method_(NULL), + on_before_user_action_(0), + on_after_user_action_(0) { } // ::testing::Test: @@ -57,7 +126,8 @@ class NativeTextfieldViewsTest : public ViewsTestBase, // TextfieldController: virtual void ContentsChanged(Textfield* sender, - const string16& new_contents){ + const string16& new_contents) { + ASSERT_NE(last_contents_, new_contents); last_contents_ = new_contents; } @@ -68,13 +138,21 @@ class NativeTextfieldViewsTest : public ViewsTestBase, return false; } + virtual void OnBeforeUserAction(Textfield* sender) { + ++on_before_user_action_; + } + + virtual void OnAfterUserAction(Textfield* sender) { + ++on_after_user_action_; + } + void InitTextfield(Textfield::StyleFlags style) { InitTextfields(style, 1); } void InitTextfields(Textfield::StyleFlags style, int count) { ASSERT_FALSE(textfield_); - textfield_ = new Textfield(style); + textfield_ = new TestTextfield(style); textfield_->SetController(this); Widget::CreateParams params(Widget::CreateParams::TYPE_POPUP); params.mirror_origin_in_rtl = false; @@ -96,6 +174,14 @@ class NativeTextfieldViewsTest : public ViewsTestBase, DCHECK(textfield_view_); model_ = textfield_view_->model_.get(); + + input_method_ = new MockInputMethod(); + widget_->native_widget()->ReplaceInputMethod(input_method_); + + // Assumes the Widget is always focused. + input_method_->OnFocus(); + + textfield_->RequestFocus(); } views::Menu2* GetContextMenu() { @@ -108,25 +194,23 @@ class NativeTextfieldViewsTest : public ViewsTestBase, } protected: - bool SendKeyEventToTextfieldViews(ui::KeyboardCode key_code, - bool shift, - bool control, - bool capslock) { + void SendKeyEvent(ui::KeyboardCode key_code, + bool shift, + bool control, + bool capslock) { int flags = (shift ? ui::EF_SHIFT_DOWN : 0) | (control ? ui::EF_CONTROL_DOWN : 0) | (capslock ? ui::EF_CAPS_LOCK_DOWN : 0); KeyEvent event(ui::ET_KEY_PRESSED, key_code, flags); - return textfield_->OnKeyPressed(event); + input_method_->DispatchKeyEvent(event); } - bool SendKeyEventToTextfieldViews(ui::KeyboardCode key_code, - bool shift, - bool control) { - return SendKeyEventToTextfieldViews(key_code, shift, control, false); + void SendKeyEvent(ui::KeyboardCode key_code, bool shift, bool control) { + SendKeyEvent(key_code, shift, control, false); } - bool SendKeyEventToTextfieldViews(ui::KeyboardCode key_code) { - return SendKeyEventToTextfieldViews(key_code, false, false); + void SendKeyEvent(ui::KeyboardCode key_code) { + SendKeyEvent(key_code, false, false); } View* GetFocusedView() { @@ -136,62 +220,66 @@ class NativeTextfieldViewsTest : public ViewsTestBase, // We need widget to populate wrapper class. Widget* widget_; - Textfield* textfield_; + TestTextfield* textfield_; NativeTextfieldViews* textfield_view_; TextfieldViewsModel* model_; // The string from Controller::ContentsChanged callback. string16 last_contents_; + // For testing input method related behaviors. + MockInputMethod* input_method_; + + // Indicates how many times OnBeforeUserAction() is called. + int on_before_user_action_; + + // Indicates how many times OnAfterUserAction() is called. + int on_after_user_action_; + private: DISALLOW_COPY_AND_ASSIGN(NativeTextfieldViewsTest); }; -TEST_F(NativeTextfieldViewsTest, ModelChangesTeset) { +TEST_F(NativeTextfieldViewsTest, ModelChangesTest) { InitTextfield(Textfield::STYLE_DEFAULT); + + // TextfieldController::ContentsChanged() shouldn't be called when changing + // text programmatically. + last_contents_.clear(); textfield_->SetText(ASCIIToUTF16("this is")); EXPECT_STR_EQ("this is", model_->text()); - EXPECT_STR_EQ("this is", last_contents_); - last_contents_.clear(); + EXPECT_STR_EQ("this is", textfield_->text()); + EXPECT_TRUE(last_contents_.empty()); textfield_->AppendText(ASCIIToUTF16(" a test")); EXPECT_STR_EQ("this is a test", model_->text()); - EXPECT_STR_EQ("this is a test", last_contents_); - last_contents_.clear(); - - // Cases where the callback should not be called. - textfield_->SetText(ASCIIToUTF16("this is a test")); - EXPECT_STR_EQ("this is a test", model_->text()); - EXPECT_EQ(string16(), last_contents_); - - textfield_->AppendText(string16()); - EXPECT_STR_EQ("this is a test", model_->text()); - EXPECT_EQ(string16(), last_contents_); + EXPECT_STR_EQ("this is a test", textfield_->text()); + EXPECT_TRUE(last_contents_.empty()); EXPECT_EQ(string16(), textfield_->GetSelectedText()); textfield_->SelectAll(); EXPECT_STR_EQ("this is a test", textfield_->GetSelectedText()); - EXPECT_EQ(string16(), last_contents_); + EXPECT_TRUE(last_contents_.empty()); } TEST_F(NativeTextfieldViewsTest, KeyTest) { InitTextfield(Textfield::STYLE_DEFAULT); - SendKeyEventToTextfieldViews(ui::VKEY_C, true, false); + SendKeyEvent(ui::VKEY_C, true, false); EXPECT_STR_EQ("C", textfield_->text()); EXPECT_STR_EQ("C", last_contents_); last_contents_.clear(); - SendKeyEventToTextfieldViews(ui::VKEY_R, false, false); + SendKeyEvent(ui::VKEY_R, false, false); EXPECT_STR_EQ("Cr", textfield_->text()); EXPECT_STR_EQ("Cr", last_contents_); textfield_->SetText(ASCIIToUTF16("")); - SendKeyEventToTextfieldViews(ui::VKEY_C, true, false, true); - SendKeyEventToTextfieldViews(ui::VKEY_C, false, false, true); - SendKeyEventToTextfieldViews(ui::VKEY_1, false, false, true); - SendKeyEventToTextfieldViews(ui::VKEY_1, true, false, true); - SendKeyEventToTextfieldViews(ui::VKEY_1, true, false, false); + SendKeyEvent(ui::VKEY_C, true, false, true); + SendKeyEvent(ui::VKEY_C, false, false, true); + SendKeyEvent(ui::VKEY_1, false, false, true); + SendKeyEvent(ui::VKEY_1, true, false, true); + SendKeyEvent(ui::VKEY_1, true, false, false); EXPECT_STR_EQ("cC1!!", textfield_->text()); EXPECT_STR_EQ("cC1!!", last_contents_); } @@ -200,34 +288,34 @@ TEST_F(NativeTextfieldViewsTest, ControlAndSelectTest) { // Insert a test string in a textfield. InitTextfield(Textfield::STYLE_DEFAULT); textfield_->SetText(ASCIIToUTF16("one two three")); - SendKeyEventToTextfieldViews(ui::VKEY_RIGHT, + SendKeyEvent(ui::VKEY_RIGHT, true /* shift */, false /* control */); - SendKeyEventToTextfieldViews(ui::VKEY_RIGHT, true, false); - SendKeyEventToTextfieldViews(ui::VKEY_RIGHT, true, false); + SendKeyEvent(ui::VKEY_RIGHT, true, false); + SendKeyEvent(ui::VKEY_RIGHT, true, false); EXPECT_STR_EQ("one", textfield_->GetSelectedText()); // Test word select. - SendKeyEventToTextfieldViews(ui::VKEY_RIGHT, true, true); + SendKeyEvent(ui::VKEY_RIGHT, true, true); EXPECT_STR_EQ("one two", textfield_->GetSelectedText()); - SendKeyEventToTextfieldViews(ui::VKEY_RIGHT, true, true); + SendKeyEvent(ui::VKEY_RIGHT, true, true); EXPECT_STR_EQ("one two three", textfield_->GetSelectedText()); - SendKeyEventToTextfieldViews(ui::VKEY_LEFT, true, true); + SendKeyEvent(ui::VKEY_LEFT, true, true); EXPECT_STR_EQ("one two ", textfield_->GetSelectedText()); - SendKeyEventToTextfieldViews(ui::VKEY_LEFT, true, true); + SendKeyEvent(ui::VKEY_LEFT, true, true); EXPECT_STR_EQ("one ", textfield_->GetSelectedText()); // Replace the selected text. - SendKeyEventToTextfieldViews(ui::VKEY_Z, true, false); - SendKeyEventToTextfieldViews(ui::VKEY_E, true, false); - SendKeyEventToTextfieldViews(ui::VKEY_R, true, false); - SendKeyEventToTextfieldViews(ui::VKEY_O, true, false); - SendKeyEventToTextfieldViews(ui::VKEY_SPACE, false, false); + SendKeyEvent(ui::VKEY_Z, true, false); + SendKeyEvent(ui::VKEY_E, true, false); + SendKeyEvent(ui::VKEY_R, true, false); + SendKeyEvent(ui::VKEY_O, true, false); + SendKeyEvent(ui::VKEY_SPACE, false, false); EXPECT_STR_EQ("ZERO two three", textfield_->text()); - SendKeyEventToTextfieldViews(ui::VKEY_END, true, false); + SendKeyEvent(ui::VKEY_END, true, false); EXPECT_STR_EQ("two three", textfield_->GetSelectedText()); - SendKeyEventToTextfieldViews(ui::VKEY_HOME, true, false); + SendKeyEvent(ui::VKEY_HOME, true, false); EXPECT_STR_EQ("ZERO ", textfield_->GetSelectedText()); } @@ -242,41 +330,41 @@ TEST_F(NativeTextfieldViewsTest, InsertionDeletionTest) { ui::KeyboardCode code = c == ' ' ? ui::VKEY_SPACE : static_cast<ui::KeyboardCode>(ui::VKEY_A + c - 'a'); - SendKeyEventToTextfieldViews(code); + SendKeyEvent(code); } EXPECT_STR_EQ(test_str, textfield_->text()); // Move the cursor around. for (int i = 0; i < 6; i++) { - SendKeyEventToTextfieldViews(ui::VKEY_LEFT); + SendKeyEvent(ui::VKEY_LEFT); } - SendKeyEventToTextfieldViews(ui::VKEY_RIGHT); + SendKeyEvent(ui::VKEY_RIGHT); // Delete using backspace and check resulting string. - SendKeyEventToTextfieldViews(ui::VKEY_BACK); + SendKeyEvent(ui::VKEY_BACK); EXPECT_STR_EQ("this is test", textfield_->text()); // Delete using delete key and check resulting string. for (int i = 0; i < 5; i++) { - SendKeyEventToTextfieldViews(ui::VKEY_DELETE); + SendKeyEvent(ui::VKEY_DELETE); } EXPECT_STR_EQ("this is ", textfield_->text()); // Select all and replace with "k". textfield_->SelectAll(); - SendKeyEventToTextfieldViews(ui::VKEY_K); + SendKeyEvent(ui::VKEY_K); EXPECT_STR_EQ("k", textfield_->text()); // Delete the previous word from cursor. textfield_->SetText(ASCIIToUTF16("one two three four")); - SendKeyEventToTextfieldViews(ui::VKEY_END); - SendKeyEventToTextfieldViews(ui::VKEY_BACK, false, true, false); + SendKeyEvent(ui::VKEY_END); + SendKeyEvent(ui::VKEY_BACK, false, true, false); EXPECT_STR_EQ("one two three ", textfield_->text()); // Delete upto the beginning of the buffer from cursor in chromeos, do nothing // in windows. - SendKeyEventToTextfieldViews(ui::VKEY_LEFT, false, true, false); - SendKeyEventToTextfieldViews(ui::VKEY_BACK, true, true, false); + SendKeyEvent(ui::VKEY_LEFT, false, true, false); + SendKeyEvent(ui::VKEY_BACK, true, true, false); #if defined(OS_WIN) EXPECT_STR_EQ("one two three ", textfield_->text()); #else @@ -285,14 +373,14 @@ TEST_F(NativeTextfieldViewsTest, InsertionDeletionTest) { // Delete the next word from cursor. textfield_->SetText(ASCIIToUTF16("one two three four")); - SendKeyEventToTextfieldViews(ui::VKEY_HOME); - SendKeyEventToTextfieldViews(ui::VKEY_DELETE, false, true, false); + SendKeyEvent(ui::VKEY_HOME); + SendKeyEvent(ui::VKEY_DELETE, false, true, false); EXPECT_STR_EQ(" two three four", textfield_->text()); // Delete upto the end of the buffer from cursor in chromeos, do nothing // in windows. - SendKeyEventToTextfieldViews(ui::VKEY_RIGHT, false, true, false); - SendKeyEventToTextfieldViews(ui::VKEY_DELETE, true, true, false); + SendKeyEvent(ui::VKEY_RIGHT, false, true, false); + SendKeyEvent(ui::VKEY_DELETE, true, true, false); #if defined(OS_WIN) EXPECT_STR_EQ(" two three four", textfield_->text()); #else @@ -302,20 +390,44 @@ TEST_F(NativeTextfieldViewsTest, InsertionDeletionTest) { TEST_F(NativeTextfieldViewsTest, PasswordTest) { InitTextfield(Textfield::STYLE_PASSWORD); + + last_contents_.clear(); textfield_->SetText(ASCIIToUTF16("my password")); // Just to make sure the text() and callback returns // the actual text instead of "*". EXPECT_STR_EQ("my password", textfield_->text()); - EXPECT_STR_EQ("my password", last_contents_); + EXPECT_TRUE(last_contents_.empty()); } TEST_F(NativeTextfieldViewsTest, OnKeyPressReturnValueTest) { InitTextfield(Textfield::STYLE_DEFAULT); - EXPECT_TRUE(SendKeyEventToTextfieldViews(ui::VKEY_A)); + + // Character keys will be handled by input method. + SendKeyEvent(ui::VKEY_A); + EXPECT_TRUE(textfield_->key_received()); + EXPECT_FALSE(textfield_->key_handled()); + textfield_->clear(); + + // Home will be handled. + SendKeyEvent(ui::VKEY_HOME); + EXPECT_TRUE(textfield_->key_received()); + EXPECT_TRUE(textfield_->key_handled()); + textfield_->clear(); + // F24, up/down key won't be handled. - EXPECT_FALSE(SendKeyEventToTextfieldViews(ui::VKEY_F24)); - EXPECT_FALSE(SendKeyEventToTextfieldViews(ui::VKEY_UP)); - EXPECT_FALSE(SendKeyEventToTextfieldViews(ui::VKEY_DOWN)); + SendKeyEvent(ui::VKEY_F24); + EXPECT_TRUE(textfield_->key_received()); + EXPECT_FALSE(textfield_->key_handled()); + textfield_->clear(); + + SendKeyEvent(ui::VKEY_UP); + EXPECT_TRUE(textfield_->key_received()); + EXPECT_FALSE(textfield_->key_handled()); + textfield_->clear(); + + SendKeyEvent(ui::VKEY_DOWN); + EXPECT_TRUE(textfield_->key_received()); + EXPECT_FALSE(textfield_->key_handled()); } TEST_F(NativeTextfieldViewsTest, CursorMovement) { @@ -325,23 +437,23 @@ TEST_F(NativeTextfieldViewsTest, CursorMovement) { textfield_->SetText(ASCIIToUTF16("one two hre ")); // Send the cursor at the end. - SendKeyEventToTextfieldViews(ui::VKEY_END); + SendKeyEvent(ui::VKEY_END); // Ctrl+Left should move the cursor just before the last word. - SendKeyEventToTextfieldViews(ui::VKEY_LEFT, false, true); - SendKeyEventToTextfieldViews(ui::VKEY_T); + SendKeyEvent(ui::VKEY_LEFT, false, true); + SendKeyEvent(ui::VKEY_T); EXPECT_STR_EQ("one two thre ", textfield_->text()); EXPECT_STR_EQ("one two thre ", last_contents_); // Ctrl+Right should move the cursor to the end of the last word. - SendKeyEventToTextfieldViews(ui::VKEY_RIGHT, false, true); - SendKeyEventToTextfieldViews(ui::VKEY_E); + SendKeyEvent(ui::VKEY_RIGHT, false, true); + SendKeyEvent(ui::VKEY_E); EXPECT_STR_EQ("one two three ", textfield_->text()); EXPECT_STR_EQ("one two three ", last_contents_); // Ctrl+Right again should move the cursor to the end. - SendKeyEventToTextfieldViews(ui::VKEY_RIGHT, false, true); - SendKeyEventToTextfieldViews(ui::VKEY_BACK); + SendKeyEvent(ui::VKEY_RIGHT, false, true); + SendKeyEvent(ui::VKEY_BACK); EXPECT_STR_EQ("one two three", textfield_->text()); EXPECT_STR_EQ("one two three", last_contents_); @@ -349,34 +461,26 @@ TEST_F(NativeTextfieldViewsTest, CursorMovement) { textfield_->SetText(ASCIIToUTF16(" ne two")); // Send the cursor at the beginning. - SendKeyEventToTextfieldViews(ui::VKEY_HOME); + SendKeyEvent(ui::VKEY_HOME); // Ctrl+Right, then Ctrl+Left should move the cursor to the beginning of the // first word. - SendKeyEventToTextfieldViews(ui::VKEY_RIGHT, false, true); - SendKeyEventToTextfieldViews(ui::VKEY_LEFT, false, true); - SendKeyEventToTextfieldViews(ui::VKEY_O); + SendKeyEvent(ui::VKEY_RIGHT, false, true); + SendKeyEvent(ui::VKEY_LEFT, false, true); + SendKeyEvent(ui::VKEY_O); EXPECT_STR_EQ(" one two", textfield_->text()); EXPECT_STR_EQ(" one two", last_contents_); // Ctrl+Left to move the cursor to the beginning of the first word. - SendKeyEventToTextfieldViews(ui::VKEY_LEFT, false, true); + SendKeyEvent(ui::VKEY_LEFT, false, true); // Ctrl+Left again should move the cursor back to the very beginning. - SendKeyEventToTextfieldViews(ui::VKEY_LEFT, false, true); - SendKeyEventToTextfieldViews(ui::VKEY_DELETE); + SendKeyEvent(ui::VKEY_LEFT, false, true); + SendKeyEvent(ui::VKEY_DELETE); EXPECT_STR_EQ("one two", textfield_->text()); EXPECT_STR_EQ("one two", last_contents_); } -#if defined(OS_WIN) -// TODO(oshima): Windows' FocusManager::ClearNativeFocus() resets the -// focused view to NULL, which causes crash in this test. Figure out -// why and fix this. -#define MAYBE_FocusTraversalTest DISABLED_FocusTraversalTest -#else -#define MAYBE_FocusTraversalTest FocusTraversalTest -#endif -TEST_F(NativeTextfieldViewsTest, MAYBE_FocusTraversalTest) { +TEST_F(NativeTextfieldViewsTest, FocusTraversalTest) { InitTextfields(Textfield::STYLE_DEFAULT, 3); textfield_->RequestFocus(); @@ -464,19 +568,19 @@ TEST_F(NativeTextfieldViewsTest, ReadOnlyTest) { InitTextfield(Textfield::STYLE_DEFAULT); textfield_->SetText(ASCIIToUTF16(" one two three ")); textfield_->SetReadOnly(true); - SendKeyEventToTextfieldViews(ui::VKEY_HOME); + SendKeyEvent(ui::VKEY_HOME); EXPECT_EQ(0U, textfield_->GetCursorPosition()); - SendKeyEventToTextfieldViews(ui::VKEY_END); + SendKeyEvent(ui::VKEY_END); EXPECT_EQ(15U, textfield_->GetCursorPosition()); - SendKeyEventToTextfieldViews(ui::VKEY_LEFT, false, false); + SendKeyEvent(ui::VKEY_LEFT, false, false); EXPECT_EQ(14U, textfield_->GetCursorPosition()); - SendKeyEventToTextfieldViews(ui::VKEY_LEFT, false, true); + SendKeyEvent(ui::VKEY_LEFT, false, true); EXPECT_EQ(9U, textfield_->GetCursorPosition()); - SendKeyEventToTextfieldViews(ui::VKEY_LEFT, true, true); + SendKeyEvent(ui::VKEY_LEFT, true, true); EXPECT_EQ(5U, textfield_->GetCursorPosition()); EXPECT_STR_EQ("two ", textfield_->GetSelectedText()); @@ -484,14 +588,14 @@ TEST_F(NativeTextfieldViewsTest, ReadOnlyTest) { EXPECT_STR_EQ(" one two three ", textfield_->GetSelectedText()); // CUT&PASTE does not work, but COPY works - SendKeyEventToTextfieldViews(ui::VKEY_X, false, true); + SendKeyEvent(ui::VKEY_X, false, true); EXPECT_STR_EQ(" one two three ", textfield_->GetSelectedText()); string16 str; views::ViewsDelegate::views_delegate->GetClipboard()-> ReadText(ui::Clipboard::BUFFER_STANDARD, &str); EXPECT_STR_NE(" one two three ", str); - SendKeyEventToTextfieldViews(ui::VKEY_C, false, true); + SendKeyEvent(ui::VKEY_C, false, true); views::ViewsDelegate::views_delegate->GetClipboard()-> ReadText(ui::Clipboard::BUFFER_STANDARD, &str); EXPECT_STR_EQ(" one two three ", str); @@ -501,7 +605,7 @@ TEST_F(NativeTextfieldViewsTest, ReadOnlyTest) { EXPECT_STR_EQ(" four five six ", textfield_->text()); // Paste shouldn't work. - SendKeyEventToTextfieldViews(ui::VKEY_V, false, true); + SendKeyEvent(ui::VKEY_V, false, true); EXPECT_STR_EQ(" four five six ", textfield_->text()); EXPECT_TRUE(textfield_->GetSelectedText().empty()); @@ -509,12 +613,103 @@ TEST_F(NativeTextfieldViewsTest, ReadOnlyTest) { EXPECT_STR_EQ(" four five six ", textfield_->GetSelectedText()); // Text field is unmodifiable and selection shouldn't change. - SendKeyEventToTextfieldViews(ui::VKEY_DELETE); + SendKeyEvent(ui::VKEY_DELETE); EXPECT_STR_EQ(" four five six ", textfield_->GetSelectedText()); - SendKeyEventToTextfieldViews(ui::VKEY_BACK); + SendKeyEvent(ui::VKEY_BACK); EXPECT_STR_EQ(" four five six ", textfield_->GetSelectedText()); - SendKeyEventToTextfieldViews(ui::VKEY_T); + SendKeyEvent(ui::VKEY_T); EXPECT_STR_EQ(" four five six ", textfield_->GetSelectedText()); } +TEST_F(NativeTextfieldViewsTest, TextInputClientTest) { + InitTextfield(Textfield::STYLE_DEFAULT); + TextInputClient* client = textfield_->GetTextInputClient(); + EXPECT_TRUE(client); + EXPECT_EQ(ui::TEXT_INPUT_TYPE_TEXT, client->GetTextInputType()); + + textfield_->SetText(ASCIIToUTF16("0123456789")); + ui::Range range; + EXPECT_TRUE(client->GetTextRange(&range)); + EXPECT_EQ(0U, range.start()); + EXPECT_EQ(10U, range.end()); + + EXPECT_TRUE(client->SetSelectionRange(ui::Range(1, 4))); + EXPECT_TRUE(client->GetSelectionRange(&range)); + EXPECT_EQ(ui::Range(1,4), range); + + // This code can't be compiled because of a bug in base::Callback. +#if 0 + GetTextHelper helper; + base::Callback<void(string16)> callback = + base::Bind(&GetTextHelper::set_text, base::Unretained(&helper)); + + EXPECT_TRUE(client->GetTextFromRange(range, callback)); + EXPECT_STR_EQ("123", helper.text()); +#endif + + EXPECT_TRUE(client->DeleteRange(range)); + EXPECT_STR_EQ("0456789", textfield_->text()); + + ui::CompositionText composition; + composition.text = UTF8ToUTF16("321"); + // Set composition through input method. + input_method_->Clear(); + input_method_->SetCompositionTextForNextKey(composition); + textfield_->clear(); + + on_before_user_action_ = on_after_user_action_ = 0; + SendKeyEvent(ui::VKEY_A); + EXPECT_TRUE(textfield_->key_received()); + EXPECT_FALSE(textfield_->key_handled()); + EXPECT_TRUE(client->HasCompositionText()); + EXPECT_TRUE(client->GetCompositionTextRange(&range)); + EXPECT_STR_EQ("0321456789", textfield_->text()); + EXPECT_EQ(ui::Range(1,4), range); + EXPECT_EQ(2, on_before_user_action_); + EXPECT_EQ(2, on_after_user_action_); + + input_method_->SetResultTextForNextKey(UTF8ToUTF16("123")); + on_before_user_action_ = on_after_user_action_ = 0; + textfield_->clear(); + SendKeyEvent(ui::VKEY_A); + EXPECT_TRUE(textfield_->key_received()); + EXPECT_FALSE(textfield_->key_handled()); + EXPECT_FALSE(client->HasCompositionText()); + EXPECT_FALSE(input_method_->cancel_composition_called()); + EXPECT_STR_EQ("0123456789", textfield_->text()); + EXPECT_EQ(2, on_before_user_action_); + EXPECT_EQ(2, on_after_user_action_); + + input_method_->Clear(); + input_method_->SetCompositionTextForNextKey(composition); + textfield_->clear(); + SendKeyEvent(ui::VKEY_A); + EXPECT_TRUE(client->HasCompositionText()); + EXPECT_STR_EQ("0123321456789", textfield_->text()); + + on_before_user_action_ = on_after_user_action_ = 0; + textfield_->clear(); + SendKeyEvent(ui::VKEY_RIGHT); + EXPECT_FALSE(client->HasCompositionText()); + EXPECT_TRUE(input_method_->cancel_composition_called()); + EXPECT_TRUE(textfield_->key_received()); + EXPECT_TRUE(textfield_->key_handled()); + EXPECT_STR_EQ("0123321456789", textfield_->text()); + EXPECT_EQ(8U, textfield_->GetCursorPosition()); + EXPECT_EQ(1, on_before_user_action_); + EXPECT_EQ(1, on_after_user_action_); + + input_method_->Clear(); + textfield_->SetReadOnly(true); + EXPECT_TRUE(input_method_->text_input_type_changed()); + EXPECT_FALSE(textfield_->GetTextInputClient()); + + textfield_->SetReadOnly(false); + input_method_->Clear(); + textfield_->SetPassword(true); + EXPECT_TRUE(input_method_->text_input_type_changed()); + EXPECT_TRUE(textfield_->GetTextInputClient()); +} + + } // namespace views diff --git a/views/controls/textfield/native_textfield_win.cc b/views/controls/textfield/native_textfield_win.cc index a147987..a7e7443 100644 --- a/views/controls/textfield/native_textfield_win.cc +++ b/views/controls/textfield/native_textfield_win.cc @@ -366,6 +366,10 @@ void NativeTextfieldWin::HandleFocus() { void NativeTextfieldWin::HandleBlur() { } +TextInputClient* NativeTextfieldWin::GetTextInputClient() { + return NULL; +} + //////////////////////////////////////////////////////////////////////////////// // NativeTextfieldWin, ui::SimpleMenuModel::Delegate implementation: @@ -602,8 +606,6 @@ LRESULT NativeTextfieldWin::OnImeEndComposition(UINT message, // this case, we need to update the find results when a composition is // finished or canceled. textfield_->SyncText(); - if (textfield_->GetController()) - textfield_->GetController()->ContentsChanged(textfield_, GetText()); return DefWindowProc(message, wparam, lparam); } @@ -1016,8 +1018,6 @@ void NativeTextfieldWin::OnAfterPossibleChange(bool should_redraw_text) { } textfield_->SyncText(); UpdateAccessibleValue(textfield_->text()); - if (textfield_->GetController()) - textfield_->GetController()->ContentsChanged(textfield_, new_text); if (should_redraw_text) { CHARRANGE original_sel; diff --git a/views/controls/textfield/native_textfield_win.h b/views/controls/textfield/native_textfield_win.h index 9ec30d2..69a7103 100644 --- a/views/controls/textfield/native_textfield_win.h +++ b/views/controls/textfield/native_textfield_win.h @@ -85,6 +85,7 @@ class NativeTextfieldWin virtual bool HandleKeyReleased(const views::KeyEvent& event) OVERRIDE; virtual void HandleFocus() OVERRIDE; virtual void HandleBlur() OVERRIDE; + virtual TextInputClient* GetTextInputClient() OVERRIDE; // Overridden from ui::SimpleMenuModel::Delegate: virtual bool IsCommandIdChecked(int command_id) const OVERRIDE; diff --git a/views/controls/textfield/native_textfield_wrapper.h b/views/controls/textfield/native_textfield_wrapper.h index 2ad5b6c..b3a6edb 100644 --- a/views/controls/textfield/native_textfield_wrapper.h +++ b/views/controls/textfield/native_textfield_wrapper.h @@ -21,6 +21,7 @@ namespace views { class KeyEvent; class Textfield; +class TextInputClient; class View; // An interface implemented by an object that provides a platform-native @@ -120,6 +121,10 @@ class NativeTextfieldWrapper { virtual void HandleFocus() = 0; virtual void HandleBlur() = 0; + // Returns the View's TextInputClient instance or NULL if the View doesn't + // support text input. + virtual TextInputClient* GetTextInputClient() = 0; + // Creates an appropriate NativeTextfieldWrapper for the platform. static NativeTextfieldWrapper* CreateWrapper(Textfield* field); }; diff --git a/views/controls/textfield/textfield.cc b/views/controls/textfield/textfield.cc index 7dfa4da..1d6b078 100644 --- a/views/controls/textfield/textfield.cc +++ b/views/controls/textfield/textfield.cc @@ -244,8 +244,14 @@ void Textfield::UpdateAllProperties() { } void Textfield::SyncText() { - if (native_wrapper_) - text_ = native_wrapper_->GetText(); + if (native_wrapper_) { + string16 new_text = native_wrapper_->GetText(); + if (new_text != text_) { + text_ = new_text; + if (controller_) + controller_->ContentsChanged(this, text_); + } + } } bool Textfield::IsIMEComposing() const { @@ -367,6 +373,10 @@ void Textfield::GetAccessibleState(ui::AccessibleViewState* state) { state->selection_end = range.end(); } +TextInputClient* Textfield::GetTextInputClient() { + return native_wrapper_ ? native_wrapper_->GetTextInputClient() : NULL; +} + void Textfield::SetEnabled(bool enabled) { View::SetEnabled(enabled); if (native_wrapper_) diff --git a/views/controls/textfield/textfield.h b/views/controls/textfield/textfield.h index bbea47d..9ef8711 100644 --- a/views/controls/textfield/textfield.h +++ b/views/controls/textfield/textfield.h @@ -215,6 +215,7 @@ class Textfield : public View { virtual void OnFocus() OVERRIDE; virtual void OnBlur() OVERRIDE; virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE; + virtual TextInputClient* GetTextInputClient() OVERRIDE; protected: virtual void ViewHierarchyChanged(bool is_add, View* parent, diff --git a/views/controls/textfield/textfield_controller.h b/views/controls/textfield/textfield_controller.h index ee8df1b..a50f2e9 100644 --- a/views/controls/textfield/textfield_controller.h +++ b/views/controls/textfield/textfield_controller.h @@ -17,7 +17,9 @@ class Textfield; // in the state of a text field. class TextfieldController { public: - // This method is called whenever the text in the field changes. + // This method is called whenever the text in the field is changed by the + // user. It won't be called if the text is changed by calling + // Textfield::SetText() or Textfield::AppendText(). virtual void ContentsChanged(Textfield* sender, const string16& new_contents) = 0; diff --git a/views/controls/textfield/textfield_views_model.cc b/views/controls/textfield/textfield_views_model.cc index ef12404..6e3ebf7 100644 --- a/views/controls/textfield/textfield_views_model.cc +++ b/views/controls/textfield/textfield_views_model.cc @@ -18,9 +18,15 @@ namespace views { -TextfieldViewsModel::TextfieldViewsModel() - : cursor_pos_(0), - selection_begin_(0), +TextfieldViewsModel::Delegate::~Delegate() { +} + +TextfieldViewsModel::TextfieldViewsModel(Delegate* delegate) + : delegate_(delegate), + cursor_pos_(0), + selection_start_(0), + composition_start_(0), + composition_end_(0), is_password_(false) { } @@ -30,57 +36,159 @@ TextfieldViewsModel::~TextfieldViewsModel() { void TextfieldViewsModel::GetFragments(TextFragments* fragments) const { DCHECK(fragments); fragments->clear(); - if (HasSelection()) { - int begin = std::min(selection_begin_, cursor_pos_); - int end = std::max(selection_begin_, cursor_pos_); - if (begin != 0) { - fragments->push_back(TextFragment(0, begin, false)); + if (HasCompositionText()) { + if (composition_start_) + fragments->push_back(TextFragment(0, composition_start_, false, false)); + + size_t selection_start = std::min(selection_start_, cursor_pos_); + size_t selection_end = std::max(selection_start_, cursor_pos_); + size_t last_end = composition_start_; + for (ui::CompositionUnderlines::const_iterator i = + composition_underlines_.begin(); + i != composition_underlines_.end(); ++i) { + size_t fragment_start = + std::min(i->start_offset, i->end_offset) + composition_start_; + size_t fragment_end = + std::max(i->start_offset, i->end_offset) + composition_start_; + + fragment_start = std::max(last_end, fragment_start); + fragment_end = std::min(fragment_end, composition_end_); + + if (fragment_start >= fragment_end) + break; + + // If there is no selection, then just add a text fragment with underline. + if (selection_start == selection_end) { + if (last_end < fragment_start) { + fragments->push_back( + TextFragment(last_end, fragment_start, false, false)); + } + fragments->push_back( + TextFragment(fragment_start, fragment_end, false, true)); + last_end = fragment_end; + continue; + } + + size_t end = std::min(fragment_start, selection_start); + if (last_end < end) + fragments->push_back(TextFragment(last_end, end, false, false)); + + last_end = fragment_end; + + if (selection_start < fragment_start) { + end = std::min(selection_end, fragment_start); + fragments->push_back(TextFragment(selection_start, end, true, false)); + selection_start = end; + } else if (selection_start > fragment_start) { + end = std::min(selection_start, fragment_end); + fragments->push_back(TextFragment(fragment_start, end, false, true)); + fragment_start = end; + if (fragment_start == fragment_end) + continue; + } + + if (fragment_start < selection_end) { + DCHECK_EQ(selection_start, fragment_start); + end = std::min(fragment_end, selection_end); + fragments->push_back(TextFragment(fragment_start, end, true, true)); + fragment_start = end; + selection_start = end; + if (fragment_start == fragment_end) + continue; + } + + DCHECK_LT(fragment_start, fragment_end); + fragments->push_back( + TextFragment(fragment_start, fragment_end, false, true)); } - fragments->push_back(TextFragment(begin, end, true)); - int len = text_.length(); - if (end != len) { - fragments->push_back(TextFragment(end, len, false)); + + if (last_end < composition_end_) { + if (selection_start < selection_end) { + DCHECK_LE(last_end, selection_start); + if (last_end < selection_start) { + fragments->push_back( + TextFragment(last_end, selection_start, false, false)); + } + fragments->push_back( + TextFragment(selection_start, selection_end, true, false)); + if (selection_end < composition_end_) { + fragments->push_back( + TextFragment(selection_end, composition_end_, false, false)); + } + } else { + fragments->push_back( + TextFragment(last_end, composition_end_, false, false)); + } } + + size_t len = text_.length(); + if (composition_end_ < len) + fragments->push_back(TextFragment(composition_end_, len, false, false)); + } else if (HasSelection()) { + size_t start = std::min(selection_start_, cursor_pos_); + size_t end = std::max(selection_start_, cursor_pos_); + if (start) + fragments->push_back(TextFragment(0, start, false, false)); + fragments->push_back(TextFragment(start, end, true, false)); + size_t len = text_.length(); + if (end != len) + fragments->push_back(TextFragment(end, len, false, false)); } else { - fragments->push_back(TextFragment(0, text_.length(), false)); + fragments->push_back(TextFragment(0, text_.length(), false, false)); } } bool TextfieldViewsModel::SetText(const string16& text) { - bool changed = text_ != text; - if (changed) { + bool changed = false; + if (HasCompositionText()) { + ConfirmCompositionText(); + changed = true; + } + if (text_ != text) { text_ = text; if (cursor_pos_ > text.length()) { cursor_pos_ = text.length(); } + changed = true; } ClearSelection(); return changed; } -void TextfieldViewsModel::Insert(char16 c) { - if (HasSelection()) +void TextfieldViewsModel::InsertText(const string16& text) { + if (HasCompositionText()) + ClearCompositionText(); + else if (HasSelection()) DeleteSelection(); - text_.insert(cursor_pos_, 1, c); - cursor_pos_++; + text_.insert(cursor_pos_, text); + cursor_pos_ += text.size(); ClearSelection(); } -void TextfieldViewsModel::Replace(char16 c) { - if (!HasSelection()) - Delete(); - Insert(c); +void TextfieldViewsModel::ReplaceText(const string16& text) { + if (HasCompositionText()) + ClearCompositionText(); + else if (!HasSelection()) + SelectRange(ui::Range(cursor_pos_, cursor_pos_ + text.length())); + InsertText(text); } void TextfieldViewsModel::Append(const string16& text) { + if (HasCompositionText()) + ConfirmCompositionText(); text_ += text; } bool TextfieldViewsModel::Delete() { + if (HasCompositionText()) { + ClearCompositionText(); + return true; + } if (HasSelection()) { DeleteSelection(); return true; - } else if (text_.length() > cursor_pos_) { + } + if (text_.length() > cursor_pos_) { text_.erase(cursor_pos_, 1); return true; } @@ -88,10 +196,15 @@ bool TextfieldViewsModel::Delete() { } bool TextfieldViewsModel::Backspace() { + if (HasCompositionText()) { + ClearCompositionText(); + return true; + } if (HasSelection()) { DeleteSelection(); return true; - } else if (cursor_pos_ > 0) { + } + if (cursor_pos_ > 0) { cursor_pos_--; text_.erase(cursor_pos_, 1); ClearSelection(); @@ -101,13 +214,15 @@ bool TextfieldViewsModel::Backspace() { } void TextfieldViewsModel::MoveCursorLeft(bool select) { + if (HasCompositionText()) + ConfirmCompositionText(); // TODO(oshima): support BIDI if (select) { if (cursor_pos_ > 0) cursor_pos_--; } else { if (HasSelection()) - cursor_pos_ = std::min(cursor_pos_, selection_begin_); + cursor_pos_ = std::min(cursor_pos_, selection_start_); else if (cursor_pos_ > 0) cursor_pos_--; ClearSelection(); @@ -115,12 +230,14 @@ void TextfieldViewsModel::MoveCursorLeft(bool select) { } void TextfieldViewsModel::MoveCursorRight(bool select) { + if (HasCompositionText()) + ConfirmCompositionText(); // TODO(oshima): support BIDI if (select) { cursor_pos_ = std::min(text_.length(), cursor_pos_ + 1); } else { if (HasSelection()) - cursor_pos_ = std::max(cursor_pos_, selection_begin_); + cursor_pos_ = std::max(cursor_pos_, selection_start_); else cursor_pos_ = std::min(text_.length(), cursor_pos_ + 1); ClearSelection(); @@ -128,6 +245,8 @@ void TextfieldViewsModel::MoveCursorRight(bool select) { } void TextfieldViewsModel::MoveCursorToPreviousWord(bool select) { + if (HasCompositionText()) + ConfirmCompositionText(); // Notes: We always iterate words from the begining. // This is probably fast enough for our usage, but we may // want to modify WordIterator so that it can start from the @@ -162,6 +281,8 @@ void TextfieldViewsModel::MoveCursorToPreviousWord(bool select) { } void TextfieldViewsModel::MoveCursorToNextWord(bool select) { + if (HasCompositionText()) + ConfirmCompositionText(); base::BreakIterator iter(&text_, base::BreakIterator::BREAK_WORD); bool success = iter.Init(); DCHECK(success); @@ -179,19 +300,25 @@ void TextfieldViewsModel::MoveCursorToNextWord(bool select) { ClearSelection(); } -void TextfieldViewsModel::MoveCursorToStart(bool select) { +void TextfieldViewsModel::MoveCursorToHome(bool select) { + if (HasCompositionText()) + ConfirmCompositionText(); cursor_pos_ = 0; if (!select) ClearSelection(); } void TextfieldViewsModel::MoveCursorToEnd(bool select) { + if (HasCompositionText()) + ConfirmCompositionText(); cursor_pos_ = text_.length(); if (!select) ClearSelection(); } bool TextfieldViewsModel::MoveCursorTo(size_t pos, bool select) { + if (HasCompositionText()) + ConfirmCompositionText(); bool cursor_changed = false; if (cursor_pos_ != pos) { cursor_pos_ = pos; @@ -217,48 +344,54 @@ gfx::Rect TextfieldViewsModel::GetCursorBounds(const gfx::Font& font) const { string16 TextfieldViewsModel::GetSelectedText() const { return text_.substr( - std::min(cursor_pos_, selection_begin_), - std::abs(static_cast<long>(cursor_pos_ - selection_begin_))); + std::min(cursor_pos_, selection_start_), + std::abs(static_cast<long>(cursor_pos_ - selection_start_))); } void TextfieldViewsModel::GetSelectedRange(ui::Range* range) const { - *range = ui::Range(selection_begin_, cursor_pos_); + *range = ui::Range(selection_start_, cursor_pos_); } void TextfieldViewsModel::SelectRange(const ui::Range& range) { - selection_begin_ = GetSafePosition(range.start()); + if (HasCompositionText()) + ConfirmCompositionText(); + selection_start_ = GetSafePosition(range.start()); cursor_pos_ = GetSafePosition(range.end()); } void TextfieldViewsModel::SelectAll() { + if (HasCompositionText()) + ConfirmCompositionText(); // SelectAll selects towards the end. cursor_pos_ = text_.length(); - selection_begin_ = 0; + selection_start_ = 0; } void TextfieldViewsModel::SelectWord() { - // First we setup selection_begin_ and cursor_pos_. There are so many cases + if (HasCompositionText()) + ConfirmCompositionText(); + // First we setup selection_start_ and cursor_pos_. There are so many cases // because we try to emulate what select-word looks like in a gtk textfield. // See associated testcase for different cases. if (cursor_pos_ > 0 && cursor_pos_ < text_.length()) { if (isalnum(text_[cursor_pos_])) { - selection_begin_ = cursor_pos_; + selection_start_ = cursor_pos_; cursor_pos_++; } else - selection_begin_ = cursor_pos_ - 1; + selection_start_ = cursor_pos_ - 1; } else if (cursor_pos_ == 0) { - selection_begin_ = cursor_pos_; + selection_start_ = cursor_pos_; if (text_.length() > 0) cursor_pos_++; } else { - selection_begin_ = cursor_pos_ - 1; + selection_start_ = cursor_pos_ - 1; } - // Now we move selection_begin_ to beginning of selection. Selection boundary + // Now we move selection_start_ to beginning of selection. Selection boundary // is defined as the position where we have alpha-num character on one side // and non-alpha-num char on the other side. - for (; selection_begin_ > 0; selection_begin_--) { - if (IsPositionAtWordSelectionBoundary(selection_begin_)) + for (; selection_start_ > 0; selection_start_--) { + if (IsPositionAtWordSelectionBoundary(selection_start_)) break; } @@ -272,11 +405,13 @@ void TextfieldViewsModel::SelectWord() { } void TextfieldViewsModel::ClearSelection() { - selection_begin_ = cursor_pos_; + if (HasCompositionText()) + ConfirmCompositionText(); + selection_start_ = cursor_pos_; } bool TextfieldViewsModel::Cut() { - if (HasSelection()) { + if (!HasCompositionText() && HasSelection()) { ui::ScopedClipboardWriter(views::ViewsDelegate::views_delegate ->GetClipboard()).WriteText(GetSelectedText()); DeleteSelection(); @@ -286,7 +421,7 @@ bool TextfieldViewsModel::Cut() { } void TextfieldViewsModel::Copy() { - if (HasSelection()) { + if (!HasCompositionText() && HasSelection()) { ui::ScopedClipboardWriter(views::ViewsDelegate::views_delegate ->GetClipboard()).WriteText(GetSelectedText()); } @@ -297,7 +432,9 @@ bool TextfieldViewsModel::Paste() { views::ViewsDelegate::views_delegate->GetClipboard() ->ReadText(ui::Clipboard::BUFFER_STANDARD, &result); if (!result.empty()) { - if (HasSelection()) + if (HasCompositionText()) + ConfirmCompositionText(); + else if (HasSelection()) DeleteSelection(); text_.insert(cursor_pos_, result); cursor_pos_ += result.length(); @@ -308,23 +445,94 @@ bool TextfieldViewsModel::Paste() { } bool TextfieldViewsModel::HasSelection() const { - return selection_begin_ != cursor_pos_; + return selection_start_ != cursor_pos_; } void TextfieldViewsModel::DeleteSelection() { + DCHECK(!HasCompositionText()); DCHECK(HasSelection()); - size_t n = std::abs(static_cast<long>(cursor_pos_ - selection_begin_)); - size_t begin = std::min(cursor_pos_, selection_begin_); + size_t n = std::abs(static_cast<long>(cursor_pos_ - selection_start_)); + size_t begin = std::min(cursor_pos_, selection_start_); text_.erase(begin, n); cursor_pos_ = begin; ClearSelection(); } +string16 TextfieldViewsModel::GetTextFromRange(const ui::Range& range) const { + if (range.IsValid() && range.GetMin() < text_.length()) + return text_.substr(range.GetMin(), range.length()); + return string16(); +} + +void TextfieldViewsModel::GetTextRange(ui::Range* range) const { + *range = ui::Range(0, text_.length()); +} + +void TextfieldViewsModel::SetCompositionText( + const ui::CompositionText& composition) { + if (HasCompositionText()) + ClearCompositionText(); + else if (HasSelection()) + DeleteSelection(); + + if (composition.text.empty()) + return; + + size_t length = composition.text.length(); + text_.insert(cursor_pos_, composition.text); + composition_start_ = cursor_pos_; + composition_end_ = composition_start_ + length; + composition_underlines_ = composition.underlines; + + if (composition.selection.IsValid()) { + selection_start_ = + std::min(composition_start_ + composition.selection.start(), + composition_end_); + cursor_pos_ = + std::min(composition_start_ + composition.selection.end(), + composition_end_); + } else { + cursor_pos_ = composition_end_; + ClearSelection(); + } +} + +void TextfieldViewsModel::ConfirmCompositionText() { + DCHECK(HasCompositionText()); + cursor_pos_ = composition_end_; + composition_start_ = composition_end_ = string16::npos; + composition_underlines_.clear(); + ClearSelection(); + if (delegate_) + delegate_->OnCompositionTextConfirmedOrCleared(); +} + +void TextfieldViewsModel::ClearCompositionText() { + DCHECK(HasCompositionText()); + text_.erase(composition_start_, composition_end_ - composition_start_); + cursor_pos_ = composition_start_; + composition_start_ = composition_end_ = string16::npos; + composition_underlines_.clear(); + ClearSelection(); + if (delegate_) + delegate_->OnCompositionTextConfirmedOrCleared(); +} + +void TextfieldViewsModel::GetCompositionTextRange(ui::Range* range) const { + if (HasCompositionText()) + *range = ui::Range(composition_start_, composition_end_); + else + *range = ui::Range::InvalidRange(); +} + +bool TextfieldViewsModel::HasCompositionText() const { + return composition_start_ != composition_end_; +} + string16 TextfieldViewsModel::GetVisibleText(size_t begin, size_t end) const { DCHECK(end >= begin); - if (is_password_) { + if (is_password_) return string16(end - begin, '*'); - } return text_.substr(begin, end - begin); } diff --git a/views/controls/textfield/textfield_views_model.h b/views/controls/textfield/textfield_views_model.h index c7e7e9a..260d493 100644 --- a/views/controls/textfield/textfield_views_model.h +++ b/views/controls/textfield/textfield_views_model.h @@ -10,6 +10,7 @@ #include "base/string16.h" #include "third_party/skia/include/core/SkColor.h" +#include "ui/base/ime/composition_text.h" #include "ui/gfx/rect.h" namespace gfx { @@ -26,7 +27,19 @@ namespace views { // It supports editing, selection and cursor manipulation. class TextfieldViewsModel { public: - TextfieldViewsModel(); + + // Delegate interface implemented by the textfield view class to provided + // additional functionalities required by the model. + class Delegate { + public: + // Called when the current composition text is confirmed or cleared. + virtual void OnCompositionTextConfirmedOrCleared() = 0; + + protected: + virtual ~Delegate(); + }; + + explicit TextfieldViewsModel(Delegate* delegate); virtual ~TextfieldViewsModel(); // Text fragment info. Used to draw selected text. @@ -34,13 +47,16 @@ class TextfieldViewsModel { // in the future to support multi-color text // for omnibox. struct TextFragment { - TextFragment(size_t b, size_t e, bool s) - : begin(b), end(e), selected(s) { + TextFragment(size_t s, size_t e, bool sel, bool u) + : start(s), end(e), selected(sel), underline(u) { } - // The begin and end position of text fragment. - size_t begin, end; + // The start and end position of text fragment. + size_t start, end; // True if the text is selected. bool selected; + // True if the text has underline. + // TODO(suzhe): support underline color and thick style. + bool underline; }; typedef std::vector<TextFragment> TextFragments; @@ -55,25 +71,41 @@ class TextfieldViewsModel { // Edit related methods. // Sest the text. Returns true if the text has been modified. + // The current composition text will be confirmed first. bool SetText(const string16& text); + // Inserts given |text| at the current cursor position. + // The current composition text will be cleared. + void InsertText(const string16& text); + // Inserts a character at the current cursor position. - void Insert(char16 c); + void InsertChar(char16 c) { + InsertText(string16(&c, 1)); + } + + // Replaces characters at the current position with characters in given text. + // The current composition text will be cleared. + void ReplaceText(const string16& text); // Replaces the char at the current position with given character. - void Replace(char16 c); + void ReplaceChar(char16 c) { + ReplaceText(string16(&c, 1)); + } // Appends the text. + // The current composition text will be confirmed. void Append(const string16& text); // Deletes the first character after the current cursor position (as if, the // the user has pressed delete key in the textfield). Returns true if // the deletion is successful. + // If there is composition text, it'll be deleted instead. bool Delete(); // Deletes the first character before the current cursor position (as if, the // the user has pressed backspace key in the textfield). Returns true if // the removal is successful. + // If there is composition text, it'll be deleted instead. bool Backspace(); // Cursor related methods. @@ -83,31 +115,38 @@ class TextfieldViewsModel { // Moves the cursor left by one position (as if, the user has pressed the left // arrow key). If |select| is true, it updates the selection accordingly. + // The current composition text will be confirmed. void MoveCursorLeft(bool select); // Moves the cursor right by one position (as if, the user has pressed the // right arrow key). If |select| is true, it updates the selection // accordingly. + // The current composition text will be confirmed. void MoveCursorRight(bool select); // Moves the cursor left by one word (word boundry is defined by space). // If |select| is true, it updates the selection accordingly. + // The current composition text will be confirmed. void MoveCursorToPreviousWord(bool select); // Moves the cursor right by one word (word boundry is defined by space). // If |select| is true, it updates the selection accordingly. + // The current composition text will be confirmed. void MoveCursorToNextWord(bool select); // Moves the cursor to start of the textfield contents. // If |select| is true, it updates the selection accordingly. - void MoveCursorToStart(bool select); + // The current composition text will be confirmed. + void MoveCursorToHome(bool select); // Moves the cursor to end of the textfield contents. // If |select| is true, it updates the selection accordingly. + // The current composition text will be confirmed. void MoveCursorToEnd(bool select); // Moves the cursor to the specified |position|. // If |select| is true, it updates the selection accordingly. + // The current composition text will be confirmed. bool MoveCursorTo(size_t position, bool select); // Returns the bounds of character at the current cursor. @@ -120,15 +159,19 @@ class TextfieldViewsModel { void GetSelectedRange(ui::Range* range) const; + // The current composition text will be confirmed. void SelectRange(const ui::Range& range); // Selects all text. + // The current composition text will be confirmed. void SelectAll(); // Selects the word at which the cursor is currently positioned. + // The current composition text will be confirmed. void SelectWord(); // Clears selection. + // The current composition text will be confirmed. void ClearSelection(); // Returns visible text. If the field is password, it returns the @@ -148,15 +191,43 @@ class TextfieldViewsModel { // if text has changed after pasting. bool Paste(); - // Tells if any text is selected. + // Tells if any text is selected, even if the selection is in composition + // text. bool HasSelection() const; + // Deletes the selected text. This method shouldn't be called with + // composition text. + void DeleteSelection(); + + // Retrieves the text content in a given range. + string16 GetTextFromRange(const ui::Range& range) const; + + // Retrieves the range containing all text in the model. + void GetTextRange(ui::Range* range) const; + + // Sets composition text and attributes. If there is composition text already, + // it’ll be replaced by the new one. Otherwise, current selection will be + // replaced. If there is no selection, the composition text will be inserted + // at the insertion point. + // Any changes to the model except text insertion will confirm the current + // composition text. + void SetCompositionText(const ui::CompositionText& composition); + + // Converts current composition text into final content. + void ConfirmCompositionText(); + + // Removes current composition text. + void ClearCompositionText(); + + // Retrieves the range of current composition text. + void GetCompositionTextRange(ui::Range* range) const; + + // Returns true if there is composition text. + bool HasCompositionText() const; + private: friend class NativeTextfieldViews; - // Deletes the selected text. - void DeleteSelection(); - // Returns the visible text given |start| and |end|. string16 GetVisibleText(size_t start, size_t end) const; @@ -167,6 +238,10 @@ class TextfieldViewsModel { // text length. size_t GetSafePosition(size_t position) const; + // Pointer to a TextfieldViewsModel::Delegate instance, should be provided by + // the View object. + Delegate* delegate_; + // The text in utf16 format. string16 text_; @@ -174,7 +249,14 @@ class TextfieldViewsModel { size_t cursor_pos_; // Selection range. - size_t selection_begin_; + size_t selection_start_; + + // Composition text range. + size_t composition_start_; + size_t composition_end_; + + // Underline information of the composition text. + ui::CompositionUnderlines composition_underlines_; // True if the text is the password. bool is_password_; diff --git a/views/controls/textfield/textfield_views_model_unittest.cc b/views/controls/textfield/textfield_views_model_unittest.cc index 4339dac..c735332 100644 --- a/views/controls/textfield/textfield_views_model_unittest.cc +++ b/views/controls/textfield/textfield_views_model_unittest.cc @@ -20,9 +20,20 @@ namespace views { #include "views/test/views_test_base.h" -class TextfieldViewsModelTest : public ViewsTestBase { +class TextfieldViewsModelTest : public ViewsTestBase, + public TextfieldViewsModel::Delegate { public: - TextfieldViewsModelTest() : ViewsTestBase() {} + TextfieldViewsModelTest() + : ViewsTestBase(), + composition_text_confirmed_or_cleared_(false) { + } + + virtual void OnCompositionTextConfirmedOrCleared() { + composition_text_confirmed_or_cleared_ = true; + } + + protected: + bool composition_text_confirmed_or_cleared_; private: DISALLOW_COPY_AND_ASSIGN(TextfieldViewsModelTest); @@ -32,7 +43,7 @@ class TextfieldViewsModelTest : public ViewsTestBase { EXPECT_EQ(ASCIIToWide(ascii), UTF16ToWide(utf16)) TEST_F(TextfieldViewsModelTest, EditString) { - TextfieldViewsModel model; + TextfieldViewsModel model(NULL); // append two strings model.Append(ASCIIToUTF16("HILL")); EXPECT_STR_EQ("HILL", model.text()); @@ -41,13 +52,13 @@ TEST_F(TextfieldViewsModelTest, EditString) { // Insert "E" to make hello model.MoveCursorRight(false); - model.Insert('E'); + model.InsertChar('E'); EXPECT_STR_EQ("HEILLWORLD", model.text()); // Replace "I" with "L" - model.Replace('L'); + model.ReplaceChar('L'); EXPECT_STR_EQ("HELLLWORLD", model.text()); - model.Replace('L'); - model.Replace('O'); + model.ReplaceChar('L'); + model.ReplaceChar('O'); EXPECT_STR_EQ("HELLOWORLD", model.text()); // Delete 6th char "W", then delete 5th char O" @@ -59,7 +70,7 @@ TEST_F(TextfieldViewsModelTest, EditString) { EXPECT_STR_EQ("HELLORLD", model.text()); // Move the cursor to start. backspace should fail. - model.MoveCursorToStart(false); + model.MoveCursorToHome(false); EXPECT_FALSE(model.Backspace()); EXPECT_STR_EQ("HELLORLD", model.text()); // Move the cursor to the end. delete should fail. @@ -72,7 +83,7 @@ TEST_F(TextfieldViewsModelTest, EditString) { } TEST_F(TextfieldViewsModelTest, EmptyString) { - TextfieldViewsModel model; + TextfieldViewsModel model(NULL); EXPECT_EQ(string16(), model.text()); EXPECT_EQ(string16(), model.GetSelectedText()); EXPECT_EQ(string16(), model.GetVisibleText()); @@ -89,7 +100,7 @@ TEST_F(TextfieldViewsModelTest, EmptyString) { } TEST_F(TextfieldViewsModelTest, Selection) { - TextfieldViewsModel model; + TextfieldViewsModel model(NULL); model.Append(ASCIIToUTF16("HELLO")); model.MoveCursorRight(false); model.MoveCursorRight(true); @@ -97,7 +108,7 @@ TEST_F(TextfieldViewsModelTest, Selection) { model.MoveCursorRight(true); EXPECT_STR_EQ("EL", model.GetSelectedText()); - model.MoveCursorToStart(true); + model.MoveCursorToHome(true); EXPECT_STR_EQ("H", model.GetSelectedText()); model.MoveCursorToEnd(true); EXPECT_STR_EQ("ELLO", model.GetSelectedText()); @@ -132,7 +143,7 @@ TEST_F(TextfieldViewsModelTest, Selection) { } TEST_F(TextfieldViewsModelTest, SelectionAndEdit) { - TextfieldViewsModel model; + TextfieldViewsModel model(NULL); model.Append(ASCIIToUTF16("HELLO")); model.MoveCursorRight(false); model.MoveCursorRight(true); @@ -147,21 +158,21 @@ TEST_F(TextfieldViewsModelTest, SelectionAndEdit) { EXPECT_STR_EQ("HILL", model.text()); EXPECT_EQ(1U, model.cursor_pos()); model.MoveCursorRight(true); // select "I" - model.Insert('E'); + model.InsertChar('E'); EXPECT_STR_EQ("HELL", model.text()); - model.MoveCursorToStart(false); + model.MoveCursorToHome(false); model.MoveCursorRight(true); // select "H" - model.Replace('B'); + model.ReplaceChar('B'); EXPECT_STR_EQ("BELL", model.text()); model.MoveCursorToEnd(false); model.MoveCursorLeft(true); model.MoveCursorLeft(true); // select ">LL" - model.Replace('E'); + model.ReplaceChar('E'); EXPECT_STR_EQ("BEE", model.text()); } TEST_F(TextfieldViewsModelTest, Password) { - TextfieldViewsModel model; + TextfieldViewsModel model(NULL); model.set_is_password(true); model.Append(ASCIIToUTF16("HELLO")); EXPECT_STR_EQ("*****", model.GetVisibleText()); @@ -176,13 +187,13 @@ TEST_F(TextfieldViewsModelTest, Password) { EXPECT_STR_EQ("ELLO", model.GetSelectedText()); EXPECT_EQ(4U, model.cursor_pos()); - model.Insert('X'); + model.InsertChar('X'); EXPECT_STR_EQ("*", model.GetVisibleText()); EXPECT_STR_EQ("X", model.text()); } TEST_F(TextfieldViewsModelTest, Word) { - TextfieldViewsModel model; + TextfieldViewsModel model(NULL); model.Append( ASCIIToUTF16("The answer to Life, the Universe, and Everything")); model.MoveCursorToNextWord(false); @@ -206,7 +217,7 @@ TEST_F(TextfieldViewsModelTest, Word) { // Should be safe to go next word at the end. model.MoveCursorToNextWord(true); EXPECT_STR_EQ(", the Universe, and Everything", model.GetSelectedText()); - model.Insert('2'); + model.InsertChar('2'); EXPECT_EQ(19U, model.cursor_pos()); // Now backwards. @@ -223,18 +234,18 @@ TEST_F(TextfieldViewsModelTest, Word) { // Should be safe to go pervious word at the begining. model.MoveCursorToPreviousWord(true); EXPECT_STR_EQ("The answer to Life", model.GetSelectedText()); - model.Replace('4'); + model.ReplaceChar('4'); EXPECT_EQ(string16(), model.GetSelectedText()); EXPECT_STR_EQ("42", model.GetVisibleText()); } TEST_F(TextfieldViewsModelTest, TextFragment) { - TextfieldViewsModel model; + TextfieldViewsModel model(NULL); TextfieldViewsModel::TextFragments fragments; // Empty string model.GetFragments(&fragments); EXPECT_EQ(1U, fragments.size()); - EXPECT_EQ(0U, fragments[0].begin); + EXPECT_EQ(0U, fragments[0].start); EXPECT_EQ(0U, fragments[0].end); EXPECT_FALSE(fragments[0].selected); @@ -242,7 +253,7 @@ TEST_F(TextfieldViewsModelTest, TextFragment) { model.Append(ASCIIToUTF16("Hello world")); model.GetFragments(&fragments); EXPECT_EQ(1U, fragments.size()); - EXPECT_EQ(0U, fragments[0].begin); + EXPECT_EQ(0U, fragments[0].start); EXPECT_EQ(11U, fragments[0].end); EXPECT_FALSE(fragments[0].selected); @@ -250,10 +261,10 @@ TEST_F(TextfieldViewsModelTest, TextFragment) { model.MoveCursorToNextWord(true); model.GetFragments(&fragments); EXPECT_EQ(2U, fragments.size()); - EXPECT_EQ(0U, fragments[0].begin); + EXPECT_EQ(0U, fragments[0].start); EXPECT_EQ(5U, fragments[0].end); EXPECT_TRUE(fragments[0].selected); - EXPECT_EQ(5U, fragments[1].begin); + EXPECT_EQ(5U, fragments[1].start); EXPECT_EQ(11U, fragments[1].end); EXPECT_FALSE(fragments[1].selected); @@ -262,14 +273,14 @@ TEST_F(TextfieldViewsModelTest, TextFragment) { model.MoveCursorRight(true); model.GetFragments(&fragments); EXPECT_EQ(3U, fragments.size()); - EXPECT_EQ(0U, fragments[0].begin); + EXPECT_EQ(0U, fragments[0].start); EXPECT_EQ(5U, fragments[0].end); EXPECT_FALSE(fragments[0].selected); - EXPECT_EQ(5U, fragments[1].begin); + EXPECT_EQ(5U, fragments[1].start); EXPECT_EQ(6U, fragments[1].end); EXPECT_TRUE(fragments[1].selected); - EXPECT_EQ(6U, fragments[2].begin); + EXPECT_EQ(6U, fragments[2].start); EXPECT_EQ(11U, fragments[2].end); EXPECT_FALSE(fragments[2].selected); @@ -277,16 +288,16 @@ TEST_F(TextfieldViewsModelTest, TextFragment) { model.MoveCursorToEnd(true); model.GetFragments(&fragments); EXPECT_EQ(2U, fragments.size()); - EXPECT_EQ(0U, fragments[0].begin); + EXPECT_EQ(0U, fragments[0].start); EXPECT_EQ(5U, fragments[0].end); EXPECT_FALSE(fragments[0].selected); - EXPECT_EQ(5U, fragments[1].begin); + EXPECT_EQ(5U, fragments[1].start); EXPECT_EQ(11U, fragments[1].end); EXPECT_TRUE(fragments[1].selected); } TEST_F(TextfieldViewsModelTest, SetText) { - TextfieldViewsModel model; + TextfieldViewsModel model(NULL); model.Append(ASCIIToUTF16("HELLO")); model.MoveCursorToEnd(false); model.SetText(ASCIIToUTF16("GOODBYE")); @@ -296,7 +307,7 @@ TEST_F(TextfieldViewsModelTest, SetText) { EXPECT_STR_EQ("GOODBYE", model.GetSelectedText()); // Selection move the current pos to the end. EXPECT_EQ(7U, model.cursor_pos()); - model.MoveCursorToStart(false); + model.MoveCursorToHome(false); EXPECT_EQ(0U, model.cursor_pos()); model.MoveCursorToEnd(false); @@ -316,7 +327,7 @@ TEST_F(TextfieldViewsModelTest, Clipboard) { string16 initial_clipboard_text; clipboard->ReadText(ui::Clipboard::BUFFER_STANDARD, &initial_clipboard_text); string16 clipboard_text; - TextfieldViewsModel model; + TextfieldViewsModel model(NULL); model.Append(ASCIIToUTF16("HELLO WORLD")); model.MoveCursorToEnd(false); @@ -369,11 +380,11 @@ void SelectWordTestVerifier(TextfieldViewsModel &model, } TEST_F(TextfieldViewsModelTest, SelectWordTest) { - TextfieldViewsModel model; + TextfieldViewsModel model(NULL); model.Append(ASCIIToUTF16(" HELLO !! WO RLD ")); // Test when cursor is at the beginning. - model.MoveCursorToStart(false); + model.MoveCursorToHome(false); model.SelectWord(); SelectWordTestVerifier(model, " ", 2U); @@ -406,9 +417,9 @@ TEST_F(TextfieldViewsModelTest, SelectWordTest) { } TEST_F(TextfieldViewsModelTest, RangeTest) { - TextfieldViewsModel model; + TextfieldViewsModel model(NULL); model.Append(ASCIIToUTF16("HELLO WORLD")); - model.MoveCursorToStart(false); + model.MoveCursorToHome(false); ui::Range range; model.GetSelectedRange(&range); EXPECT_TRUE(range.is_empty()); @@ -462,7 +473,7 @@ TEST_F(TextfieldViewsModelTest, RangeTest) { EXPECT_EQ(11U, range.end()); // Select All - model.MoveCursorToStart(true); + model.MoveCursorToHome(true); model.GetSelectedRange(&range); EXPECT_FALSE(range.is_empty()); EXPECT_TRUE(range.is_reversed()); @@ -471,7 +482,7 @@ TEST_F(TextfieldViewsModelTest, RangeTest) { } TEST_F(TextfieldViewsModelTest, SelectRangeTest) { - TextfieldViewsModel model; + TextfieldViewsModel model(NULL); model.Append(ASCIIToUTF16("HELLO WORLD")); ui::Range range(0, 6); EXPECT_FALSE(range.is_reversed()); @@ -514,4 +525,187 @@ TEST_F(TextfieldViewsModelTest, SelectRangeTest) { EXPECT_TRUE(model.GetSelectedText().empty()); } +TEST_F(TextfieldViewsModelTest, CompositionTextTest) { + TextfieldViewsModel model(this); + model.Append(ASCIIToUTF16("1234590")); + model.SelectRange(ui::Range(5, 5)); + EXPECT_FALSE(model.HasSelection()); + EXPECT_EQ(5U, model.cursor_pos()); + + ui::Range range; + model.GetTextRange(&range); + EXPECT_EQ(0U, range.start()); + EXPECT_EQ(7U, range.end()); + + ui::CompositionText composition; + composition.text = ASCIIToUTF16("678"); + composition.underlines.push_back(ui::CompositionUnderline(0, 3, 0, false)); + composition.selection = ui::Range(2, 3); + model.SetCompositionText(composition); + EXPECT_TRUE(model.HasCompositionText()); + EXPECT_TRUE(model.HasSelection()); + + model.GetTextRange(&range); + EXPECT_EQ(10U, range.end()); + + model.GetCompositionTextRange(&range); + EXPECT_EQ(5U, range.start()); + EXPECT_EQ(8U, range.end()); + + model.GetSelectedRange(&range); + EXPECT_EQ(7U, range.start()); + EXPECT_EQ(8U, range.end()); + + EXPECT_STR_EQ("1234567890", model.text()); + EXPECT_STR_EQ("8", model.GetSelectedText()); + EXPECT_STR_EQ("456", model.GetTextFromRange(ui::Range(3, 6))); + + TextfieldViewsModel::TextFragments fragments; + model.GetFragments(&fragments); + EXPECT_EQ(4U, fragments.size()); + EXPECT_EQ(0U, fragments[0].start); + EXPECT_EQ(5U, fragments[0].end); + EXPECT_FALSE(fragments[0].selected); + EXPECT_FALSE(fragments[0].underline); + EXPECT_EQ(5U, fragments[1].start); + EXPECT_EQ(7U, fragments[1].end); + EXPECT_FALSE(fragments[1].selected); + EXPECT_TRUE(fragments[1].underline); + EXPECT_EQ(7U, fragments[2].start); + EXPECT_EQ(8U, fragments[2].end); + EXPECT_TRUE(fragments[2].selected); + EXPECT_TRUE(fragments[2].underline); + EXPECT_EQ(8U, fragments[3].start); + EXPECT_EQ(10U, fragments[3].end); + EXPECT_FALSE(fragments[3].selected); + EXPECT_FALSE(fragments[3].underline); + + EXPECT_FALSE(composition_text_confirmed_or_cleared_); + model.ClearCompositionText(); + EXPECT_TRUE(composition_text_confirmed_or_cleared_); + composition_text_confirmed_or_cleared_ = false; + EXPECT_FALSE(model.HasCompositionText()); + EXPECT_FALSE(model.HasSelection()); + EXPECT_EQ(5U, model.cursor_pos()); + + model.SetCompositionText(composition); + EXPECT_STR_EQ("1234567890", model.text()); + EXPECT_TRUE(model.SetText(ASCIIToUTF16("1234567890"))); + EXPECT_TRUE(composition_text_confirmed_or_cleared_); + composition_text_confirmed_or_cleared_ = false; + model.MoveCursorToEnd(false); + + model.SetCompositionText(composition); + EXPECT_STR_EQ("1234567890678", model.text()); + + model.InsertText(UTF8ToUTF16("-")); + EXPECT_TRUE(composition_text_confirmed_or_cleared_); + composition_text_confirmed_or_cleared_ = false; + EXPECT_STR_EQ("1234567890-", model.text()); + EXPECT_FALSE(model.HasCompositionText()); + EXPECT_FALSE(model.HasSelection()); + + model.MoveCursorLeft(true); + EXPECT_STR_EQ("-", model.GetSelectedText()); + model.SetCompositionText(composition); + EXPECT_STR_EQ("1234567890678", model.text()); + + model.ReplaceText(UTF8ToUTF16("-")); + EXPECT_TRUE(composition_text_confirmed_or_cleared_); + composition_text_confirmed_or_cleared_ = false; + EXPECT_STR_EQ("1234567890-", model.text()); + EXPECT_FALSE(model.HasCompositionText()); + EXPECT_FALSE(model.HasSelection()); + + model.SetCompositionText(composition); + model.Append(UTF8ToUTF16("-")); + EXPECT_TRUE(composition_text_confirmed_or_cleared_); + composition_text_confirmed_or_cleared_ = false; + EXPECT_STR_EQ("1234567890-678-", model.text()); + + model.SetCompositionText(composition); + model.Delete(); + EXPECT_TRUE(composition_text_confirmed_or_cleared_); + composition_text_confirmed_or_cleared_ = false; + EXPECT_STR_EQ("1234567890-678-", model.text()); + + model.SetCompositionText(composition); + model.Backspace(); + EXPECT_TRUE(composition_text_confirmed_or_cleared_); + composition_text_confirmed_or_cleared_ = false; + EXPECT_STR_EQ("1234567890-678-", model.text()); + + model.SetText(string16()); + model.SetCompositionText(composition); + model.MoveCursorLeft(false); + EXPECT_TRUE(composition_text_confirmed_or_cleared_); + composition_text_confirmed_or_cleared_ = false; + EXPECT_STR_EQ("678", model.text()); + EXPECT_EQ(2U, model.cursor_pos()); + + model.SetCompositionText(composition); + model.MoveCursorRight(false); + EXPECT_TRUE(composition_text_confirmed_or_cleared_); + composition_text_confirmed_or_cleared_ = false; + EXPECT_STR_EQ("676788", model.text()); + EXPECT_EQ(6U, model.cursor_pos()); + + model.SetCompositionText(composition); + model.MoveCursorToPreviousWord(false); + EXPECT_TRUE(composition_text_confirmed_or_cleared_); + composition_text_confirmed_or_cleared_ = false; + EXPECT_STR_EQ("676788678", model.text()); + + model.SetText(string16()); + model.SetCompositionText(composition); + model.MoveCursorToNextWord(false); + EXPECT_TRUE(composition_text_confirmed_or_cleared_); + composition_text_confirmed_or_cleared_ = false; + + model.SetCompositionText(composition); + model.MoveCursorToHome(true); + EXPECT_TRUE(composition_text_confirmed_or_cleared_); + composition_text_confirmed_or_cleared_ = false; + EXPECT_STR_EQ("678678", model.text()); + + model.SetCompositionText(composition); + model.MoveCursorToEnd(false); + EXPECT_TRUE(composition_text_confirmed_or_cleared_); + composition_text_confirmed_or_cleared_ = false; + EXPECT_STR_EQ("678", model.text()); + + model.SetCompositionText(composition); + model.MoveCursorTo(0, true); + EXPECT_TRUE(composition_text_confirmed_or_cleared_); + composition_text_confirmed_or_cleared_ = false; + EXPECT_STR_EQ("678678", model.text()); + + model.SetCompositionText(composition); + model.SelectRange(ui::Range(0, 3)); + EXPECT_TRUE(composition_text_confirmed_or_cleared_); + composition_text_confirmed_or_cleared_ = false; + EXPECT_STR_EQ("678", model.text()); + + model.SetCompositionText(composition); + model.SelectAll(); + EXPECT_TRUE(composition_text_confirmed_or_cleared_); + composition_text_confirmed_or_cleared_ = false; + EXPECT_STR_EQ("678", model.text()); + + model.SetCompositionText(composition); + model.SelectWord(); + EXPECT_TRUE(composition_text_confirmed_or_cleared_); + composition_text_confirmed_or_cleared_ = false; + EXPECT_STR_EQ("678", model.text()); + + model.SetCompositionText(composition); + model.ClearSelection(); + EXPECT_TRUE(composition_text_confirmed_or_cleared_); + composition_text_confirmed_or_cleared_ = false; + + model.SetCompositionText(composition); + EXPECT_FALSE(model.Cut()); + EXPECT_FALSE(composition_text_confirmed_or_cleared_); +} + } // namespace views diff --git a/views/focus/accelerator_handler_touch.cc b/views/focus/accelerator_handler_touch.cc index 3755e76..04dbec8 100644 --- a/views/focus/accelerator_handler_touch.cc +++ b/views/focus/accelerator_handler_touch.cc @@ -15,6 +15,7 @@ #include "views/accelerator.h" #include "views/events/event.h" #include "views/focus/focus_manager.h" +#include "views/ime/input_method.h" #include "views/touchui/touch_factory.h" #include "views/widget/root_view.h" #include "views/widget/widget_gtk.h" @@ -23,7 +24,7 @@ namespace views { namespace { -RootView* FindRootViewForGdkWindow(GdkWindow* gdk_window) { +Widget* FindWidgetForGdkWindow(GdkWindow* gdk_window) { gpointer data = NULL; gdk_window_get_user_data(gdk_window, &data); GtkWidget* gtk_widget = reinterpret_cast<GtkWidget*>(data); @@ -37,7 +38,7 @@ RootView* FindRootViewForGdkWindow(GdkWindow* gdk_window) { DLOG(WARNING) << "no WidgetGtk found for that GtkWidget"; return NULL; } - return widget->GetWidget()->GetRootView(); + return widget->GetWidget(); } #if defined(HAVE_XINPUT2) @@ -162,13 +163,21 @@ bool DispatchXEvent(XEvent* xev) { #endif GdkWindow* gwind = gdk_window_lookup_for_display(gdisp, xwindow); - - if (RootView* root = FindRootViewForGdkWindow(gwind)) { + Widget* widget = FindWidgetForGdkWindow(gwind); + if (widget) { + RootView* root = widget->GetRootView(); switch (xev->type) { case KeyPress: case KeyRelease: { Event::FromNativeEvent2 from_native; KeyEvent keyev(xev, from_native); + InputMethod* ime = widget->GetInputMethod(); + // Always dispatch key events to the input method first, to make sure + // that the input method's hotkeys work all time. + if (ime) { + ime->DispatchKeyEvent(keyev); + return true; + } return root->ProcessKeyEvent(keyev); } diff --git a/views/focus/focus_manager.cc b/views/focus/focus_manager.cc index 5712b2e..e26cfd2 100644 --- a/views/focus/focus_manager.cc +++ b/views/focus/focus_manager.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// 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. @@ -368,10 +368,6 @@ void FocusManager::RestoreFocusedView() { focus_change_reason_ = kReasonFocusRestore; } } - } else { - // Clearing the focus will focus the root window, so we still get key - // events. - ClearFocus(); } } diff --git a/views/view.cc b/views/view.cc index e2acab9..5494a4e 100644 --- a/views/view.cc +++ b/views/view.cc @@ -846,8 +846,8 @@ TextInputClient* View::GetTextInputClient() { } InputMethod* View::GetInputMethod() { - NOTIMPLEMENTED(); - return NULL; + Widget* widget = GetWidget(); + return widget ? widget->GetInputMethod() : NULL; } // Accelerators ---------------------------------------------------------------- diff --git a/views/widget/native_widget.h b/views/widget/native_widget.h index 9b28ac7..d9a1184 100644 --- a/views/widget/native_widget.h +++ b/views/widget/native_widget.h @@ -20,6 +20,7 @@ class OSExchangeData; namespace views { +class InputMethod; class TooltipManager; class Widget; @@ -81,6 +82,17 @@ class NativeWidget { // Returns true if this native widget is capturing all events. virtual bool HasMouseCapture() const = 0; + // Returns the InputMethod for this native widget. + // Note that all widgets in a widget hierarchy share the same input method. + // TODO(suzhe): rename to GetInputMethod() when NativeWidget implementation + // class doesn't inherit Widget anymore. + virtual InputMethod* GetInputMethodNative() = 0; + + // Sets a different InputMethod instance to this native widget. The instance + // must not be initialized, the ownership will be assumed by the native + // widget. It's only for testing purpose. + virtual void ReplaceInputMethod(InputMethod* input_method) = 0; + protected: friend class Widget; diff --git a/views/widget/widget.cc b/views/widget/widget.cc index 2a31aa1..320f69f 100644 --- a/views/widget/widget.cc +++ b/views/widget/widget.cc @@ -8,6 +8,7 @@ #include "base/message_loop.h" #include "ui/gfx/compositor/compositor.h" #include "views/focus/view_storage.h" +#include "views/ime/input_method.h" #include "views/widget/default_theme_provider.h" #include "views/widget/root_view.h" #include "views/widget/native_widget.h" @@ -219,6 +220,12 @@ FocusManager* Widget::GetFocusManager() { return focus_manager_.get(); } +InputMethod* Widget::GetInputMethod() { + Widget* toplevel_widget = GetTopLevelWidget(); + return toplevel_widget ? + toplevel_widget->native_widget()->GetInputMethodNative() : NULL; +} + bool Widget::ContainsNativeView(gfx::NativeView native_view) { if (native_widget_->ContainsNativeView(native_view)) return true; diff --git a/views/widget/widget.h b/views/widget/widget.h index c57375d..8bf3ec0 100644 --- a/views/widget/widget.h +++ b/views/widget/widget.h @@ -33,6 +33,7 @@ using ui::ThemeProvider; namespace views { class DefaultThemeProvider; +class InputMethod; class NativeWidget; class RootView; class TooltipManager; @@ -224,6 +225,10 @@ class Widget : public internal::NativeWidgetDelegate, // TODO(beng): remove virtual. virtual FocusManager* GetFocusManager(); + // Returns the InputMethod for this widget. + // Note that all widgets in a widget hierarchy share the same input method. + InputMethod* GetInputMethod(); + // Returns true if the native view |native_view| is contained in the // views::View hierarchy rooted at this widget. // TODO(beng): const. diff --git a/views/widget/widget_gtk.cc b/views/widget/widget_gtk.cc index 9f53543..00824f5 100644 --- a/views/widget/widget_gtk.cc +++ b/views/widget/widget_gtk.cc @@ -25,6 +25,7 @@ #include "ui/gfx/canvas_skia_paint.h" #include "ui/gfx/path.h" #include "views/views_delegate.h" +#include "views/controls/textfield/native_textfield_views.h" #include "views/focus/view_storage.h" #include "views/widget/drop_target_gtk.h" #include "views/widget/gtk_views_fixed.h" @@ -294,6 +295,9 @@ WidgetGtk::WidgetGtk(Type type) } WidgetGtk::~WidgetGtk() { + // We need to delete the input method before calling DestroyRootView(), + // because it'll set focus_manager_ to NULL. + input_method_.reset(); DestroyRootView(); DCHECK(delete_on_destroy_ || widget_ == NULL); if (type_ != TYPE_CHILD) @@ -516,6 +520,20 @@ void WidgetGtk::Init(GtkWidget* parent, CreateGtkWidget(parent, bounds); delegate_->OnNativeWidgetCreated(); + // Creates input method for toplevel widget after calling + // delegate_->OnNativeWidgetCreated(), to make sure that focus manager is + // already created at this point. + // TODO(suzhe): Always enable input method when we start to use + // RenderWidgetHostViewViews in normal ChromeOS. +#if !defined(TOUCH_UI) + if (type_ != TYPE_CHILD && NativeTextfieldViews::IsTextfieldViewsEnabled()) { +#else + if (type_ != TYPE_CHILD) { +#endif + input_method_.reset(new InputMethodGtk(this)); + input_method_->Init(GetWidget()); + } + if (opacity_ != 255) SetOpacity(opacity_); @@ -665,22 +683,21 @@ void WidgetGtk::ClearNativeFocus() { gtk_window_set_focus(GTK_WINDOW(GetNativeView()), NULL); } -bool WidgetGtk::HandleKeyboardEvent(GdkEventKey* event) { +bool WidgetGtk::HandleKeyboardEvent(const KeyEvent& key) { if (!GetFocusManager()) return false; - KeyEvent key(reinterpret_cast<NativeEvent>(event)); - int key_code = key.key_code(); + const int key_code = key.key_code(); bool handled = false; // Always reset |should_handle_menu_key_release_| unless we are handling a // VKEY_MENU key release event. It ensures that VKEY_MENU accelerator can only // be activated when handling a VKEY_MENU key release event which is preceded // by an un-handled VKEY_MENU key press event. - if (key_code != ui::VKEY_MENU || event->type != GDK_KEY_RELEASE) + if (key_code != ui::VKEY_MENU || key.type() != ui::ET_KEY_RELEASED) should_handle_menu_key_release_ = false; - if (event->type == GDK_KEY_PRESS) { + if (key.type() == ui::ET_KEY_PRESSED) { // VKEY_MENU is triggered by key release event. // FocusManager::OnKeyEvent() returns false when the key has been consumed. if (key_code != ui::VKEY_MENU) @@ -768,6 +785,18 @@ bool WidgetGtk::HasMouseCapture() const { return GTK_WIDGET_HAS_GRAB(window_contents_); } +InputMethod* WidgetGtk::GetInputMethodNative() { + return input_method_.get(); +} + +void WidgetGtk::ReplaceInputMethod(InputMethod* input_method) { + input_method_.reset(input_method); + if (input_method) { + input_method->set_delegate(this); + input_method->Init(GetWidget()); + } +} + gfx::Rect WidgetGtk::GetWindowScreenBounds() const { // Client == Window bounds on Gtk. return GetClientAreaScreenBounds(); @@ -858,6 +887,7 @@ void WidgetGtk::Close() { void WidgetGtk::CloseNow() { if (widget_) { + input_method_.reset(); gtk_widget_destroy(widget_); // Triggers OnDestroy(). } } @@ -1195,6 +1225,10 @@ gboolean WidgetGtk::OnFocusIn(GtkWidget* widget, GdkEventFocus* event) { if (type_ == TYPE_CHILD) return false; + // Only top-level Widget should have an InputMethod instance. + if (input_method_.get()) + input_method_->OnFocus(); + // See description of got_initial_focus_in_ for details on this. if (!got_initial_focus_in_) { got_initial_focus_in_ = true; @@ -1213,6 +1247,10 @@ gboolean WidgetGtk::OnFocusOut(GtkWidget* widget, GdkEventFocus* event) { if (type_ == TYPE_CHILD) return false; + // Only top-level Widget should have an InputMethod instance. + if (input_method_.get()) + input_method_->OnBlur(); + // The top-level window lost focus, store the focused view. GetFocusManager()->StoreFocusedView(); return false; @@ -1220,43 +1258,13 @@ gboolean WidgetGtk::OnFocusOut(GtkWidget* widget, GdkEventFocus* event) { gboolean WidgetGtk::OnKeyEvent(GtkWidget* widget, GdkEventKey* event) { KeyEvent key(reinterpret_cast<NativeEvent>(event)); + if (input_method_.get()) + input_method_->DispatchKeyEvent(key); + else + DispatchKeyEventPostIME(key); - // Always reset |should_handle_menu_key_release_| unless we are handling a - // VKEY_MENU key release event. It ensures that VKEY_MENU accelerator can only - // be activated when handling a VKEY_MENU key release event which is preceded - // by an unhandled VKEY_MENU key press event. See also HandleKeyboardEvent(). - if (key.key_code() != ui::VKEY_MENU || event->type != GDK_KEY_RELEASE) - should_handle_menu_key_release_ = false; - - bool handled = false; - - // Dispatch the key event to View hierarchy first. - handled = GetRootView()->ProcessKeyEvent(key); - - // Dispatch the key event to native GtkWidget hierarchy. - // To prevent GtkWindow from handling the key event as a keybinding, we need - // to bypass GtkWindow's default key event handler and dispatch the event - // here. - if (!handled && GTK_IS_WINDOW(widget)) - handled = gtk_window_propagate_key_event(GTK_WINDOW(widget), event); - - // On Linux, in order to handle VKEY_MENU (Alt) accelerator key correctly and - // avoid issues like: http://crbug.com/40966 and http://crbug.com/49701, we - // should only send the key event to the focus manager if it's not handled by - // any View or native GtkWidget. - // The flow is different when the focus is in a RenderWidgetHostViewGtk, which - // always consumes the key event and send it back to us later by calling - // HandleKeyboardEvent() directly, if it's not handled by webkit. - if (!handled) - handled = HandleKeyboardEvent(event); - - // Dispatch the key event for bindings processing. - if (!handled && GTK_IS_WINDOW(widget)) - handled = gtk_bindings_activate_event(GTK_OBJECT(widget), event); - - // Always return true for toplevel window to prevents GtkWindow's default key - // event handler. - return GTK_IS_WINDOW(widget) ? true : handled; + // Returns true to prevent GtkWindow's default key event handler. + return true; } gboolean WidgetGtk::OnQueryTooltip(GtkWidget* widget, @@ -1334,6 +1342,45 @@ gfx::AcceleratedWidget WidgetGtk::GetAcceleratedWidget() { return GDK_WINDOW_XID(window_contents_->window); } +void WidgetGtk::DispatchKeyEventPostIME(const KeyEvent& key) { + // Always reset |should_handle_menu_key_release_| unless we are handling a + // VKEY_MENU key release event. It ensures that VKEY_MENU accelerator can only + // be activated when handling a VKEY_MENU key release event which is preceded + // by an unhandled VKEY_MENU key press event. See also HandleKeyboardEvent(). + if (key.key_code() != ui::VKEY_MENU || key.type() != ui::ET_KEY_RELEASED) + should_handle_menu_key_release_ = false; + + bool handled = false; + + // Dispatch the key event to View hierarchy first. + handled = GetRootView()->ProcessKeyEvent(key); + + if (key.key_code() == ui::VKEY_PROCESSKEY || handled) + return; + + // Dispatch the key event to native GtkWidget hierarchy. + // To prevent GtkWindow from handling the key event as a keybinding, we need + // to bypass GtkWindow's default key event handler and dispatch the event + // here. + GdkEventKey* event = reinterpret_cast<GdkEventKey*>(key.native_event()); + if (!handled && event && GTK_IS_WINDOW(widget_)) + handled = gtk_window_propagate_key_event(GTK_WINDOW(widget_), event); + + // On Linux, in order to handle VKEY_MENU (Alt) accelerator key correctly and + // avoid issues like: http://crbug.com/40966 and http://crbug.com/49701, we + // should only send the key event to the focus manager if it's not handled by + // any View or native GtkWidget. + // The flow is different when the focus is in a RenderWidgetHostViewGtk, which + // always consumes the key event and send it back to us later by calling + // HandleKeyboardEvent() directly, if it's not handled by webkit. + if (!handled) + handled = HandleKeyboardEvent(key); + + // Dispatch the key event for bindings processing. + if (!handled && event && GTK_IS_WINDOW(widget_)) + gtk_bindings_activate_event(GTK_OBJECT(widget_), event); +} + gboolean WidgetGtk::OnWindowPaint(GtkWidget* widget, GdkEventExpose* event) { // Clear the background to be totally transparent. We don't need to // paint the root view here as that is done by OnPaint. diff --git a/views/widget/widget_gtk.h b/views/widget/widget_gtk.h index 9ddc0a4..323f886 100644 --- a/views/widget/widget_gtk.h +++ b/views/widget/widget_gtk.h @@ -13,6 +13,8 @@ #include "ui/base/x/active_window_watcher_x.h" #include "ui/gfx/size.h" #include "views/focus/focus_manager.h" +#include "views/ime/input_method_delegate.h" +#include "views/ime/input_method_gtk.h" #include "views/widget/native_widget.h" #include "views/widget/widget.h" @@ -41,7 +43,8 @@ class NativeWidgetDelegate; // Widget implementation for GTK. class WidgetGtk : public Widget, public NativeWidget, - public ui::ActiveWindowWatcherX::Observer { + public ui::ActiveWindowWatcherX::Observer, + public internal::InputMethodDelegate { public: // Type of widget. enum Type { @@ -171,7 +174,7 @@ class WidgetGtk : public Widget, // Handles a keyboard event by sending it to our focus manager. // Returns true if it's handled by the focus manager. - bool HandleKeyboardEvent(GdkEventKey* event); + bool HandleKeyboardEvent(const KeyEvent& key); // Enables debug painting. See |debug_paint_enabled_| for details. static void EnableDebugPaint(); @@ -196,6 +199,8 @@ class WidgetGtk : public Widget, virtual void SetMouseCapture() OVERRIDE; virtual void ReleaseMouseCapture() OVERRIDE; virtual bool HasMouseCapture() const OVERRIDE; + virtual InputMethod* GetInputMethodNative() OVERRIDE; + virtual void ReplaceInputMethod(InputMethod* input_method) OVERRIDE; virtual gfx::Rect GetWindowScreenBounds() const OVERRIDE; virtual gfx::Rect GetClientAreaScreenBounds() const OVERRIDE; virtual void SetBounds(const gfx::Rect& bounds) OVERRIDE; @@ -297,6 +302,9 @@ class WidgetGtk : public Widget, // Overridden from NativeWidget virtual gfx::AcceleratedWidget GetAcceleratedWidget() OVERRIDE; + // Overridden from internal::InputMethodDelegate + virtual void DispatchKeyEventPostIME(const KeyEvent& key) OVERRIDE; + CHROMEGTK_CALLBACK_1(WidgetGtk, gboolean, OnWindowPaint, GdkEventExpose*); // Returns the first ancestor of |widget| that is a window. @@ -414,6 +422,8 @@ class WidgetGtk : public Widget, // that window manager shows the window only after the window is painted. bool painted_; + scoped_ptr<InputMethod> input_method_; + DISALLOW_COPY_AND_ASSIGN(WidgetGtk); }; diff --git a/views/widget/widget_win.cc b/views/widget/widget_win.cc index 0d6f47c..1cadfb1 100644 --- a/views/widget/widget_win.cc +++ b/views/widget/widget_win.cc @@ -23,9 +23,11 @@ #include "ui/gfx/path.h" #include "views/accessibility/native_view_accessibility_win.h" #include "views/controls/native_control_win.h" +#include "views/controls/textfield/native_textfield_views.h" #include "views/focus/accelerator_handler.h" #include "views/focus/focus_util_win.h" #include "views/focus/view_storage.h" +#include "views/ime/input_method_win.h" #include "views/views_delegate.h" #include "views/widget/aero_tooltip_manager.h" #include "views/widget/child_window_message_processor.h" @@ -143,11 +145,15 @@ WidgetWin::WidgetWin() restore_focus_when_enabled_(false), accessibility_view_events_index_(-1), accessibility_view_events_(kMaxAccessibilityViewEvents), - previous_cursor_(NULL) { + previous_cursor_(NULL), + is_input_method_win_(false) { set_native_widget(this); } WidgetWin::~WidgetWin() { + // We need to delete the input method before calling DestroyRootView(), + // because it'll set focus_manager_ to NULL. + input_method_.reset(); DestroyRootView(); } @@ -333,6 +339,19 @@ bool WidgetWin::HasMouseCapture() const { return GetCapture() == hwnd(); } +InputMethod* WidgetWin::GetInputMethodNative() { + return input_method_.get(); +} + +void WidgetWin::ReplaceInputMethod(InputMethod* input_method) { + input_method_.reset(input_method); + if (input_method) { + input_method->set_delegate(this); + input_method->Init(GetWidget()); + } + is_input_method_win_ = false; +} + gfx::Rect WidgetWin::GetWindowScreenBounds() const { RECT r; GetWindowRect(&r); @@ -383,6 +402,11 @@ void WidgetWin::Close() { } void WidgetWin::CloseNow() { + // Destroys the input method before closing the window so that it can be + // detached from the widget correctly. + input_method_.reset(); + is_input_method_win_ = false; + // We may already have been destroyed if the selection resulted in a tab // switch which will have reactivated the browser window and closed us, so // we need to check to see if we're still a window before trying to destroy @@ -620,6 +644,15 @@ LRESULT WidgetWin::OnCreate(CREATESTRUCT* create_struct) { ClientAreaSizeChanged(); delegate_->OnNativeWidgetCreated(); + + // delegate_->OnNativeWidgetCreated() creates the focus manager for top-level + // widget. Only top-level widget should have an input method. + if (delegate_->HasFocusManager() && + NativeTextfieldViews::IsTextfieldViewsEnabled()) { + input_method_.reset(new InputMethodWin(this)); + input_method_->Init(GetWidget()); + is_input_method_win_ = true; + } return 0; } @@ -699,6 +732,45 @@ void WidgetWin::OnHScroll(int scroll_type, short position, HWND scrollbar) { SetMsgHandled(FALSE); } +LRESULT WidgetWin::OnImeMessages(UINT message, WPARAM w_param, LPARAM l_param) { + if (!is_input_method_win_) { + SetMsgHandled(FALSE); + return 0; + } + + InputMethodWin* ime = static_cast<InputMethodWin*>(input_method_.get()); + BOOL handled = FALSE; + LRESULT result = 0; + switch (message) { + case WM_IME_SETCONTEXT: + result = ime->OnImeSetContext(message, w_param, l_param, &handled); + break; + case WM_IME_STARTCOMPOSITION: + result = ime->OnImeStartComposition(message, w_param, l_param, &handled); + break; + case WM_IME_COMPOSITION: + result = ime->OnImeComposition(message, w_param, l_param, &handled); + break; + case WM_IME_ENDCOMPOSITION: + result = ime->OnImeEndComposition(message, w_param, l_param, &handled); + break; + case WM_CHAR: + case WM_SYSCHAR: + result = ime->OnChar(message, w_param, l_param, &handled); + break; + case WM_DEADCHAR: + case WM_SYSDEADCHAR: + result = ime->OnDeadChar(message, w_param, l_param, &handled); + break; + default: + NOTREACHED() << "Unknown IME message:" << message; + break; + } + + SetMsgHandled(handled); + return result; +} + void WidgetWin::OnInitMenu(HMENU menu) { SetMsgHandled(FALSE); } @@ -709,28 +781,37 @@ void WidgetWin::OnInitMenuPopup(HMENU menu, SetMsgHandled(FALSE); } -LRESULT WidgetWin::OnKeyDown(UINT message, WPARAM w_param, LPARAM l_param) { - RootView* root_view = GetFocusedViewRootView(); - if (!root_view) - root_view = GetRootView(); +void WidgetWin::OnInputLangChange(DWORD character_set, HKL input_language_id) { + if (is_input_method_win_) { + static_cast<InputMethodWin*>(input_method_.get())->OnInputLangChange( + character_set, input_language_id); + } +} +LRESULT WidgetWin::OnKeyDown(UINT message, WPARAM w_param, LPARAM l_param) { MSG msg = { hwnd(), message, w_param, l_param }; - SetMsgHandled(root_view->ProcessKeyEvent(KeyEvent(msg))); + KeyEvent key(msg); + if (input_method_.get()) + input_method_->DispatchKeyEvent(key); + else + DispatchKeyEventPostIME(key); return 0; } LRESULT WidgetWin::OnKeyUp(UINT message, WPARAM w_param, LPARAM l_param) { - RootView* root_view = GetFocusedViewRootView(); - if (!root_view) - root_view = GetRootView(); - MSG msg = { hwnd(), message, w_param, l_param }; - SetMsgHandled(root_view->ProcessKeyEvent(KeyEvent(msg))); + KeyEvent key(msg); + if (input_method_.get()) + input_method_->DispatchKeyEvent(key); + else + DispatchKeyEventPostIME(key); return 0; } void WidgetWin::OnKillFocus(HWND focused_window) { delegate_->OnNativeBlur(focused_window); + if (input_method_.get()) + input_method_->OnBlur(); SetMsgHandled(FALSE); } @@ -863,6 +944,8 @@ LRESULT WidgetWin::OnReflectedMessage(UINT msg, void WidgetWin::OnSetFocus(HWND focused_window) { delegate_->OnNativeFocus(focused_window); + if (input_method_.get()) + input_method_->OnFocus(); SetMsgHandled(FALSE); } @@ -1057,6 +1140,14 @@ gfx::AcceleratedWidget WidgetWin::GetAcceleratedWidget() { return gfx::kNullAcceleratedWidget; } +void WidgetWin::DispatchKeyEventPostIME(const KeyEvent& key) { + RootView* root_view = GetFocusedViewRootView(); + if (!root_view) + root_view = GetRootView(); + + SetMsgHandled(root_view->ProcessKeyEvent(key)); +} + //////////////////////////////////////////////////////////////////////////////// // Widget, public: diff --git a/views/widget/widget_win.h b/views/widget/widget_win.h index 53e5f50..6c72a22 100644 --- a/views/widget/widget_win.h +++ b/views/widget/widget_win.h @@ -20,6 +20,7 @@ #include "base/win/scoped_comptr.h" #include "ui/base/win/window_impl.h" #include "views/focus/focus_manager.h" +#include "views/ime/input_method_delegate.h" #include "views/layout/layout_manager.h" #include "views/widget/native_widget.h" #include "views/widget/widget.h" @@ -81,7 +82,8 @@ const int WM_NCUAHDRAWFRAME = 0xAF; class WidgetWin : public ui::WindowImpl, public Widget, public NativeWidget, - public MessageLoopForUI::Observer { + public MessageLoopForUI::Observer, + public internal::InputMethodDelegate { public: WidgetWin(); virtual ~WidgetWin(); @@ -209,6 +211,8 @@ class WidgetWin : public ui::WindowImpl, virtual void SetMouseCapture() OVERRIDE; virtual void ReleaseMouseCapture() OVERRIDE; virtual bool HasMouseCapture() const OVERRIDE; + virtual InputMethod* GetInputMethodNative() OVERRIDE; + virtual void ReplaceInputMethod(InputMethod* input_method) OVERRIDE; virtual gfx::Rect GetWindowScreenBounds() const OVERRIDE; virtual gfx::Rect GetClientAreaScreenBounds() const OVERRIDE; virtual void SetBounds(const gfx::Rect& bounds) OVERRIDE; @@ -270,8 +274,18 @@ class WidgetWin : public ui::WindowImpl, // Key events. MESSAGE_HANDLER_EX(WM_KEYDOWN, OnKeyDown) MESSAGE_HANDLER_EX(WM_KEYUP, OnKeyUp) - MESSAGE_HANDLER_EX(WM_SYSKEYDOWN, OnKeyDown); - MESSAGE_HANDLER_EX(WM_SYSKEYUP, OnKeyUp); + MESSAGE_HANDLER_EX(WM_SYSKEYDOWN, OnKeyDown) + MESSAGE_HANDLER_EX(WM_SYSKEYUP, OnKeyUp) + + // IME Events. + MESSAGE_HANDLER_EX(WM_IME_SETCONTEXT, OnImeMessages) + MESSAGE_HANDLER_EX(WM_IME_STARTCOMPOSITION, OnImeMessages) + MESSAGE_HANDLER_EX(WM_IME_COMPOSITION, OnImeMessages) + MESSAGE_HANDLER_EX(WM_IME_ENDCOMPOSITION, OnImeMessages) + MESSAGE_HANDLER_EX(WM_CHAR, OnImeMessages) + MESSAGE_HANDLER_EX(WM_SYSCHAR, OnImeMessages) + MESSAGE_HANDLER_EX(WM_DEADCHAR, OnImeMessages) + MESSAGE_HANDLER_EX(WM_SYSDEADCHAR, OnImeMessages) // This list is in _ALPHABETICAL_ order! OR I WILL HURT YOU. MSG_WM_ACTIVATE(OnActivate) @@ -293,6 +307,7 @@ class WidgetWin : public ui::WindowImpl, MSG_WM_HSCROLL(OnHScroll) MSG_WM_INITMENU(OnInitMenu) MSG_WM_INITMENUPOPUP(OnInitMenuPopup) + MSG_WM_INPUTLANGCHANGE(OnInputLangChange) MSG_WM_KILLFOCUS(OnKillFocus) MSG_WM_MOVE(OnMove) MSG_WM_MOVING(OnMoving) @@ -347,8 +362,10 @@ class WidgetWin : public ui::WindowImpl, virtual LRESULT OnGetObject(UINT uMsg, WPARAM w_param, LPARAM l_param); virtual void OnGetMinMaxInfo(MINMAXINFO* minmax_info); virtual void OnHScroll(int scroll_type, short position, HWND scrollbar); + virtual LRESULT OnImeMessages(UINT message, WPARAM w_param, LPARAM l_param); virtual void OnInitMenu(HMENU menu); virtual void OnInitMenuPopup(HMENU menu, UINT position, BOOL is_system_menu); + virtual void OnInputLangChange(DWORD character_set, HKL input_language_id); virtual LRESULT OnKeyDown(UINT message, WPARAM w_param, LPARAM l_param); virtual LRESULT OnKeyUp(UINT message, WPARAM w_param, LPARAM l_param); virtual void OnKillFocus(HWND focused_window); @@ -431,6 +448,9 @@ class WidgetWin : public ui::WindowImpl, // Overridden from NativeWidget. virtual gfx::AcceleratedWidget GetAcceleratedWidget() OVERRIDE; + // Overridden from internal::InputMethodDelegate + virtual void DispatchKeyEventPostIME(const KeyEvent& key) OVERRIDE; + // A delegate implementation that handles events received here. internal::NativeWidgetDelegate* delegate_; @@ -505,6 +525,11 @@ class WidgetWin : public ui::WindowImpl, ViewProps props_; + scoped_ptr<InputMethod> input_method_; + + // Indicates if the |input_method_| is an InputMethodWin instance. + bool is_input_method_win_; + DISALLOW_COPY_AND_ASSIGN(WidgetWin); }; |