Chromium Code Reviews| Index: views/controls/textfield/native_textfield_views.cc |
| diff --git a/views/controls/textfield/native_textfield_views.cc b/views/controls/textfield/native_textfield_views.cc |
| index 48bdea92d3a2986e3e8af06d0f28b39891ebf635..6ddb8ba15da2510e527f555de2201df6b24a4364 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) { |
| @@ -164,14 +166,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) { |
| @@ -180,10 +177,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 { |
| @@ -224,6 +217,7 @@ void NativeTextfieldViews::UpdateBackgroundColor() { |
| void NativeTextfieldViews::UpdateReadOnly() { |
| SchedulePaint(); |
| + OnTextInputTypeChanged(); |
| } |
| void NativeTextfieldViews::UpdateFont() { |
| @@ -234,11 +228,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() { |
| @@ -279,7 +275,7 @@ gfx::NativeView NativeTextfieldViews::GetTestingHandle() const { |
| } |
| bool NativeTextfieldViews::IsIMEComposing() const { |
| - return false; |
| + return model_->HasCompositionText(); |
| } |
| void NativeTextfieldViews::GetSelectedRange(ui::Range* range) const { |
| @@ -311,6 +307,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, |
| @@ -327,6 +324,10 @@ void NativeTextfieldViews::HandleBlur() { |
| } |
| } |
| +TextInputClient* NativeTextfieldViews::GetTextInputClient() { |
| + return textfield_->read_only() ? NULL : this; |
| +} |
| + |
| ///////////////////////////////////////////////////////////////// |
| // NativeTextfieldViews, ui::SimpleMenuModel::Delegate overrides: |
| @@ -416,7 +417,172 @@ 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; |
| +} |
| + |
| +void NativeTextfieldViews::OnCompositionTextConfirmedOrCleared() { |
| + if (skip_input_method_cancel_composition_ || !textfield_->HasFocus()) |
| + return; |
| + |
| + InputMethod* input_method = textfield_->GetInputMethod(); |
| + if (input_method) |
| + input_method->CancelComposition(textfield_); |
| +} |
| const gfx::Font& NativeTextfieldViews::GetFont() const { |
| return textfield_->font(); |
| @@ -479,6 +645,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) { |
| @@ -505,17 +673,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; |
| @@ -581,7 +754,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: |
| @@ -594,7 +767,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. |
| @@ -630,13 +803,17 @@ 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; |
| + |
| + // Only handle text input by ourselves if there is no input method. |
| + if (!textfield_->GetInputMethod()) { |
| + char16 ch = key_event.GetCharacter(); |
| + if (editable && ShouldInsertChar(ch, key_event.flags())) { |
| + if (insert_) |
| + model_->InsertChar(ch); |
| + else |
| + model_->ReplaceChar(ch); |
| + text_changed = true; |
| + } |
| } |
| UpdateAfterChange(text_changed, cursor_changed); |
| @@ -721,9 +898,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, |
| @@ -754,6 +928,24 @@ void NativeTextfieldViews::InitContextMenuIfRequired() { |
| context_menu_menu_.reset(new Menu2(context_menu_contents_.get())); |
| } |
| +void NativeTextfieldViews::OnTextInputTypeChanged() { |
| + if (!textfield_->HasFocus()) |
| + return; |
|
oshima
2011/03/29 19:07:55
can we check this in input_method? is it bad idea?
James Su
2011/03/29 19:21:51
InputMethod implementations also do this check. Th
oshima
2011/03/30 21:06:34
The code that has no effect or speculative code ma
Peng
2011/03/30 21:28:27
We have several input_method implementations (Ibus
James Su
2011/03/30 21:30:32
Existing InputMethod implementations simply return
James Su
2011/03/30 21:30:32
Yes. I updated comments of these methods to clarif
oshima
2011/03/30 21:51:25
Thanks. Just for clarification, I was fine with ei
|
| + |
| + InputMethod* input_method = textfield_->GetInputMethod(); |
|
oshima
2011/03/29 19:07:55
when input_method can be NULL?
James Su
2011/03/29 19:21:51
Right now, it's only possible in test code, where
oshima
2011/03/30 21:06:34
If it shouldn't be null for regular case, I'd pref
James Su
2011/03/30 21:30:32
I'd prefer to keep this check, otherwise we need s
oshima
2011/03/30 21:51:25
Altering the code for unittest to pass sounds wron
James Su
2011/03/30 22:46:38
Sounds reasonable. I'll remove the check.
|
| + if (input_method) |
| + input_method->OnTextInputTypeChanged(textfield_); |
| +} |
| + |
| +void NativeTextfieldViews::OnCaretBoundsChanged() { |
| + if (!textfield_->HasFocus()) |
| + return; |
| + |
| + InputMethod* input_method = textfield_->GetInputMethod(); |
| + if (input_method) |
| + input_method->OnCaretBoundsChanged(textfield_); |
| +} |
| + |
| void NativeTextfieldViews::OnBeforeUserAction() { |
| TextfieldController* controller = textfield_->GetController(); |
| if (controller) |