Chromium Code Reviews| Index: ui/views/controls/textfield/textfield.cc |
| diff --git a/ui/views/controls/textfield/textfield.cc b/ui/views/controls/textfield/textfield.cc |
| index 69adbb771fcd99e29d7deefda4c758b1e6aaeeb2..8d70cf413ae13fbec97d8f88f2ae7d84519debb1 100644 |
| --- a/ui/views/controls/textfield/textfield.cc |
| +++ b/ui/views/controls/textfield/textfield.cc |
| @@ -6,34 +6,52 @@ |
| #include <string> |
| -#include "base/command_line.h" |
| -#include "base/strings/string_util.h" |
| -#include "base/strings/utf_string_conversions.h" |
| +#include "base/debug/trace_event.h" |
| +#include "base/i18n/case_conversion.h" |
| +#include "grit/ui_strings.h" |
| #include "ui/base/accessibility/accessible_view_state.h" |
| -#include "ui/base/ime/text_input_type.h" |
| +#include "ui/base/dragdrop/drag_drop_types.h" |
| +#include "ui/base/dragdrop/drag_utils.h" |
| #include "ui/base/resource/resource_bundle.h" |
| -#include "ui/base/ui_base_switches.h" |
| +#include "ui/base/ui_base_switches_util.h" |
| #include "ui/events/event.h" |
| #include "ui/events/keycodes/keyboard_codes.h" |
| +#include "ui/gfx/canvas.h" |
| #include "ui/gfx/insets.h" |
| -#include "ui/gfx/range/range.h" |
| -#include "ui/gfx/selection_model.h" |
| #include "ui/native_theme/native_theme.h" |
| +#include "ui/views/background.h" |
| +#include "ui/views/controls/focusable_border.h" |
| +#include "ui/views/controls/menu/menu_item_view.h" |
| +#include "ui/views/controls/menu/menu_model_adapter.h" |
| +#include "ui/views/controls/menu/menu_runner.h" |
| #include "ui/views/controls/native/native_view_host.h" |
| -#include "ui/views/controls/textfield/native_textfield_views.h" |
| #include "ui/views/controls/textfield/textfield_controller.h" |
| +#include "ui/views/drag_utils.h" |
| +#include "ui/views/ime/input_method.h" |
| +#include "ui/views/metrics.h" |
| #include "ui/views/painter.h" |
| #include "ui/views/views_delegate.h" |
| #include "ui/views/widget/widget.h" |
| +#if defined(USE_AURA) |
| +#include "ui/base/cursor/cursor.h" |
| +#endif |
| + |
| +#if defined(OS_WIN) && defined(USE_AURA) |
| +#include "base/win/win_util.h" |
| +#endif |
| + |
| namespace { |
| // Default placeholder text color. |
| const SkColor kDefaultPlaceholderTextColor = SK_ColorLTGRAY; |
| -gfx::FontList GetDefaultFontList() { |
| - return ResourceBundle::GetSharedInstance().GetFontList( |
| - ResourceBundle::BaseFont); |
| +void ConvertRectToScreen(const views::View* src, gfx::Rect* r) { |
| + DCHECK(src); |
| + |
| + gfx::Point new_origin = r->origin(); |
| + views::View::ConvertPointToScreen(src, &new_origin); |
| + r->set_origin(new_origin); |
| } |
| } // namespace |
| @@ -55,22 +73,30 @@ size_t Textfield::GetCaretBlinkMs() { |
| } |
| Textfield::Textfield() |
| - : textfield_view_(NULL), |
| + : model_(new TextfieldViewsModel(this)), |
| controller_(NULL), |
| style_(STYLE_DEFAULT), |
| - font_list_(GetDefaultFontList()), |
| read_only_(false), |
| default_width_in_chars_(0), |
| + text_border_(new FocusableBorder()), |
| draw_border_(true), |
| text_color_(SK_ColorBLACK), |
| use_default_text_color_(true), |
| background_color_(SK_ColorWHITE), |
| use_default_background_color_(true), |
| horizontal_margins_were_set_(false), |
| - vertical_margins_were_set_(false), |
| placeholder_text_color_(kDefaultPlaceholderTextColor), |
| text_input_type_(ui::TEXT_INPUT_TYPE_TEXT), |
| - weak_ptr_factory_(this) { |
| + skip_input_method_cancel_composition_(false), |
| + is_cursor_visible_(false), |
| + is_drop_cursor_visible_(false), |
| + initiating_drag_(false), |
| + aggregated_clicks_(0), |
| + weak_ptr_factory_(this), |
| + cursor_weak_ptr_factory_(this) { |
| + set_context_menu_controller(this); |
| + set_drag_controller(this); |
| + set_border(text_border_); |
| SetFocusable(true); |
| if (ViewsDelegate::views_delegate) { |
| @@ -83,23 +109,32 @@ Textfield::Textfield() |
| } |
| Textfield::Textfield(StyleFlags style) |
| - : textfield_view_(NULL), |
| + : model_(new TextfieldViewsModel(this)), |
| controller_(NULL), |
| style_(style), |
| - font_list_(GetDefaultFontList()), |
| read_only_(false), |
| default_width_in_chars_(0), |
| + text_border_(new FocusableBorder()), |
| draw_border_(true), |
| text_color_(SK_ColorBLACK), |
| use_default_text_color_(true), |
| background_color_(SK_ColorWHITE), |
| use_default_background_color_(true), |
| horizontal_margins_were_set_(false), |
| - vertical_margins_were_set_(false), |
| placeholder_text_color_(kDefaultPlaceholderTextColor), |
| text_input_type_(ui::TEXT_INPUT_TYPE_TEXT), |
| - weak_ptr_factory_(this) { |
| + skip_input_method_cancel_composition_(false), |
| + is_cursor_visible_(false), |
| + is_drop_cursor_visible_(false), |
| + initiating_drag_(false), |
| + aggregated_clicks_(0), |
| + weak_ptr_factory_(this), |
| + cursor_weak_ptr_factory_(this) { |
| + set_context_menu_controller(this); |
| + set_drag_controller(this); |
| + set_border(text_border_); |
|
sky
2014/01/09 21:43:11
Caching the Border is a bad pattern. In particular
msw
2014/01/10 20:34:55
Added a TODO with the |text_border_| declaration.
|
| SetFocusable(true); |
| + |
| if (IsObscured()) |
| SetTextInputType(ui::TEXT_INPUT_TYPE_PASSWORD); |
| @@ -126,11 +161,10 @@ TextfieldController* Textfield::GetController() const { |
| void Textfield::SetReadOnly(bool read_only) { |
| // Update read-only without changing the focusable state (or active, etc.). |
| read_only_ = read_only; |
| - if (textfield_view_) { |
| - textfield_view_->UpdateReadOnly(); |
| - textfield_view_->UpdateTextColor(); |
| - textfield_view_->UpdateBackgroundColor(); |
| - } |
| + if (GetInputMethod()) |
| + GetInputMethod()->OnTextInputTypeChanged(this); |
| + SetColor(GetTextColor()); |
| + UpdateBackgroundColor(); |
| } |
| bool Textfield::IsObscured() const { |
| @@ -145,14 +179,11 @@ void Textfield::SetObscured(bool obscured) { |
| style_ = static_cast<StyleFlags>(style_ & ~STYLE_OBSCURED); |
| SetTextInputType(ui::TEXT_INPUT_TYPE_TEXT); |
| } |
| - if (textfield_view_) |
| - textfield_view_->UpdateIsObscured(); |
| -} |
| - |
| -ui::TextInputType Textfield::GetTextInputType() const { |
| - if (read_only() || !enabled()) |
| - return ui::TEXT_INPUT_TYPE_NONE; |
| - return text_input_type_; |
| + GetRenderText()->SetObscured(obscured); |
| + OnCaretBoundsChanged(); |
| + if (GetInputMethod()) |
| + GetInputMethod()->OnTextInputTypeChanged(this); |
| + SchedulePaint(); |
| } |
| void Textfield::SetTextInputType(ui::TextInputType type) { |
| @@ -162,47 +193,51 @@ void Textfield::SetTextInputType(ui::TextInputType type) { |
| SetObscured(should_be_obscured); |
| } |
| -void Textfield::SetText(const base::string16& text) { |
| - text_ = text; |
| - if (textfield_view_) |
| - textfield_view_->UpdateText(); |
| +void Textfield::SetText(const base::string16& new_text) { |
| + model_->SetText(GetTextForDisplay(new_text)); |
| + OnCaretBoundsChanged(); |
| + SchedulePaint(); |
| + NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_TEXT_CHANGED, true); |
| } |
| -void Textfield::AppendText(const base::string16& text) { |
| - text_ += text; |
| - if (textfield_view_) |
| - textfield_view_->AppendText(text); |
| +void Textfield::AppendText(const base::string16& new_text) { |
| + if (new_text.empty()) |
| + return; |
| + model_->Append(GetTextForDisplay(new_text)); |
| + OnCaretBoundsChanged(); |
| + SchedulePaint(); |
| } |
| -void Textfield::InsertOrReplaceText(const base::string16& text) { |
| - if (textfield_view_) { |
| - textfield_view_->InsertOrReplaceText(text); |
| - text_ = textfield_view_->GetText(); |
| - } |
| +void Textfield::InsertOrReplaceText(const base::string16& new_text) { |
| + if (new_text.empty()) |
| + return; |
| + model_->InsertText(new_text); |
| + OnCaretBoundsChanged(); |
| + SchedulePaint(); |
| } |
| base::i18n::TextDirection Textfield::GetTextDirection() const { |
| - return textfield_view_ ? |
| - textfield_view_->GetTextDirection() : base::i18n::UNKNOWN_DIRECTION; |
| + return GetRenderText()->GetTextDirection(); |
| } |
| void Textfield::SelectAll(bool reversed) { |
| - if (textfield_view_) |
| - textfield_view_->SelectAll(reversed); |
| + model_->SelectAll(reversed); |
| + OnCaretBoundsChanged(); |
| + SchedulePaint(); |
| } |
| base::string16 Textfield::GetSelectedText() const { |
| - return textfield_view_ ? textfield_view_->GetSelectedText() : |
| - base::string16(); |
| + return model_->GetSelectedText(); |
| } |
| -void Textfield::ClearSelection() const { |
| - if (textfield_view_) |
| - textfield_view_->ClearSelection(); |
| +void Textfield::ClearSelection() { |
| + model_->ClearSelection(); |
| + OnCaretBoundsChanged(); |
| + SchedulePaint(); |
| } |
| bool Textfield::HasSelection() const { |
| - return textfield_view_ && !textfield_view_->GetSelectedRange().is_empty(); |
| + return !GetSelectedRange().is_empty(); |
| } |
| SkColor Textfield::GetTextColor() const { |
| @@ -217,14 +252,12 @@ SkColor Textfield::GetTextColor() const { |
| void Textfield::SetTextColor(SkColor color) { |
| text_color_ = color; |
| use_default_text_color_ = false; |
| - if (textfield_view_) |
| - textfield_view_->UpdateTextColor(); |
| + SetColor(color); |
| } |
| void Textfield::UseDefaultTextColor() { |
| use_default_text_color_ = true; |
| - if (textfield_view_) |
| - textfield_view_->UpdateTextColor(); |
| + SetColor(GetTextColor()); |
| } |
| SkColor Textfield::GetBackgroundColor() const { |
| @@ -239,38 +272,30 @@ SkColor Textfield::GetBackgroundColor() const { |
| void Textfield::SetBackgroundColor(SkColor color) { |
| background_color_ = color; |
| use_default_background_color_ = false; |
| - if (textfield_view_) |
| - textfield_view_->UpdateBackgroundColor(); |
| + UpdateBackgroundColor(); |
| } |
| void Textfield::UseDefaultBackgroundColor() { |
| use_default_background_color_ = true; |
| - if (textfield_view_) |
| - textfield_view_->UpdateBackgroundColor(); |
| + UpdateBackgroundColor(); |
| } |
| bool Textfield::GetCursorEnabled() const { |
| - return textfield_view_ && textfield_view_->GetCursorEnabled(); |
| + return GetRenderText()->cursor_enabled(); |
| } |
| void Textfield::SetCursorEnabled(bool enabled) { |
| - if (textfield_view_) |
| - textfield_view_->SetCursorEnabled(enabled); |
| -} |
| - |
| -void Textfield::SetFontList(const gfx::FontList& font_list) { |
| - font_list_ = font_list; |
| - if (textfield_view_) |
| - textfield_view_->UpdateFont(); |
| - PreferredSizeChanged(); |
| + GetRenderText()->SetCursorEnabled(enabled); |
| } |
| -const gfx::Font& Textfield::GetPrimaryFont() const { |
| - return font_list_.GetPrimaryFont(); |
| +const gfx::FontList& Textfield::GetFontList() const { |
| + return GetRenderText()->font_list(); |
| } |
| -void Textfield::SetFont(const gfx::Font& font) { |
| - SetFontList(gfx::FontList(font)); |
| +void Textfield::SetFontList(const gfx::FontList& font_list) { |
| + GetRenderText()->SetFontList(font_list); |
| + OnCaretBoundsChanged(); |
| + PreferredSizeChanged(); |
| } |
| void Textfield::SetHorizontalMargins(int left, int right) { |
| @@ -280,20 +305,7 @@ void Textfield::SetHorizontalMargins(int left, int right) { |
| } |
| margins_.Set(margins_.top(), left, margins_.bottom(), right); |
| horizontal_margins_were_set_ = true; |
| - if (textfield_view_) |
| - textfield_view_->UpdateHorizontalMargins(); |
| - PreferredSizeChanged(); |
| -} |
| - |
| -void Textfield::SetVerticalMargins(int top, int bottom) { |
| - if (vertical_margins_were_set_ && |
| - top == margins_.top() && bottom == margins_.bottom()) { |
| - return; |
| - } |
| - margins_.Set(top, margins_.left(), bottom, margins_.right()); |
| - vertical_margins_were_set_ = true; |
| - if (textfield_view_) |
| - textfield_view_->UpdateVerticalMargins(); |
| + UpdateHorizontalMargins(); |
| PreferredSizeChanged(); |
| } |
| @@ -302,8 +314,14 @@ void Textfield::RemoveBorder() { |
| return; |
| draw_border_ = false; |
| - if (textfield_view_) |
| - textfield_view_->UpdateBorder(); |
| + // By default, if a caller calls Textfield::RemoveBorder() and does not set |
| + // any explicit margins, they should get zero margins. But also call |
| + // UpdateHorizontalMargins() so we respect any explicitly-set margins. |
| + // |
| + // NOTE: If someday Textfield supports toggling |draw_border_| back on, we'll |
| + // need to update this conditional to set the insets to their default values. |
| + text_border_->SetInsets(0, 0, 0, 0); |
| + UpdateHorizontalMargins(); |
| } |
| base::string16 Textfield::GetPlaceholderText() const { |
| @@ -319,85 +337,60 @@ bool Textfield::GetHorizontalMargins(int* left, int* right) { |
| return true; |
| } |
| -bool Textfield::GetVerticalMargins(int* top, int* bottom) { |
| - if (!vertical_margins_were_set_) |
| - return false; |
| - |
| - *top = margins_.top(); |
| - *bottom = margins_.bottom(); |
| - return true; |
| -} |
| - |
| -void Textfield::UpdateAllProperties() { |
| - if (textfield_view_) { |
| - textfield_view_->UpdateText(); |
| - textfield_view_->UpdateTextColor(); |
| - textfield_view_->UpdateBackgroundColor(); |
| - textfield_view_->UpdateReadOnly(); |
| - textfield_view_->UpdateFont(); |
| - textfield_view_->UpdateEnabled(); |
| - textfield_view_->UpdateBorder(); |
| - textfield_view_->UpdateIsObscured(); |
| - textfield_view_->UpdateHorizontalMargins(); |
| - textfield_view_->UpdateVerticalMargins(); |
| - } |
| -} |
| - |
| -void Textfield::SyncText() { |
| - if (textfield_view_) { |
| - base::string16 new_text = textfield_view_->GetText(); |
| - if (new_text != text_) { |
| - text_ = new_text; |
| - if (controller_) |
| - controller_->ContentsChanged(this, text_); |
| - } |
| - } |
| -} |
| - |
| bool Textfield::IsIMEComposing() const { |
| - return textfield_view_ && textfield_view_->IsIMEComposing(); |
| + return model_->HasCompositionText(); |
| } |
| const gfx::Range& Textfield::GetSelectedRange() const { |
| - return textfield_view_->GetSelectedRange(); |
| + return GetRenderText()->selection(); |
| } |
| void Textfield::SelectRange(const gfx::Range& range) { |
| - textfield_view_->SelectRange(range); |
| + model_->SelectRange(range); |
| + OnCaretBoundsChanged(); |
| + SchedulePaint(); |
| + NotifyAccessibilityEvent( |
| + ui::AccessibilityTypes::EVENT_SELECTION_CHANGED, true); |
| } |
| const gfx::SelectionModel& Textfield::GetSelectionModel() const { |
| - return textfield_view_->GetSelectionModel(); |
| + return GetRenderText()->selection_model(); |
| } |
| void Textfield::SelectSelectionModel(const gfx::SelectionModel& sel) { |
| - textfield_view_->SelectSelectionModel(sel); |
| + model_->SelectSelectionModel(sel); |
| + OnCaretBoundsChanged(); |
| + SchedulePaint(); |
| } |
| size_t Textfield::GetCursorPosition() const { |
| - return textfield_view_->GetCursorPosition(); |
| + return model_->GetCursorPosition(); |
| } |
| void Textfield::SetColor(SkColor value) { |
| - return textfield_view_->SetColor(value); |
| + GetRenderText()->SetColor(value); |
| + SchedulePaint(); |
| } |
| void Textfield::ApplyColor(SkColor value, const gfx::Range& range) { |
| - return textfield_view_->ApplyColor(value, range); |
| + GetRenderText()->ApplyColor(value, range); |
| + SchedulePaint(); |
| } |
| void Textfield::SetStyle(gfx::TextStyle style, bool value) { |
| - return textfield_view_->SetStyle(style, value); |
| + GetRenderText()->SetStyle(style, value); |
| + SchedulePaint(); |
| } |
| void Textfield::ApplyStyle(gfx::TextStyle style, |
| bool value, |
| const gfx::Range& range) { |
| - return textfield_view_->ApplyStyle(style, value, range); |
| + GetRenderText()->ApplyStyle(style, value, range); |
| + SchedulePaint(); |
| } |
| void Textfield::ClearEditHistory() { |
| - textfield_view_->ClearEditHistory(); |
| + model_->ClearEditHistory(); |
| } |
| void Textfield::SetAccessibleName(const base::string16& name) { |
| @@ -405,7 +398,7 @@ void Textfield::SetAccessibleName(const base::string16& name) { |
| } |
| void Textfield::ExecuteCommand(int command_id) { |
| - textfield_view_->ExecuteCommand(command_id, ui::EF_NONE); |
| + ExecuteCommand(command_id, ui::EF_NONE); |
| } |
| void Textfield::SetFocusPainter(scoped_ptr<Painter> focus_painter) { |
| @@ -413,35 +406,20 @@ void Textfield::SetFocusPainter(scoped_ptr<Painter> focus_painter) { |
| } |
| bool Textfield::HasTextBeingDragged() { |
| - return textfield_view_->HasTextBeingDragged(); |
| + return initiating_drag_; |
| } |
| //////////////////////////////////////////////////////////////////////////////// |
| // Textfield, View overrides: |
| -void Textfield::Layout() { |
| - if (textfield_view_) { |
| - textfield_view_->SetBoundsRect(GetContentsBounds()); |
| - textfield_view_->Layout(); |
| - } |
| -} |
| - |
| int Textfield::GetBaseline() const { |
| - gfx::Insets insets = GetTextInsets(); |
| - const int baseline = textfield_view_ ? |
| - textfield_view_->GetTextfieldBaseline() : font_list_.GetBaseline(); |
| - return insets.top() + baseline; |
| + return GetInsets().top() + GetRenderText()->GetBaseline(); |
| } |
| gfx::Size Textfield::GetPreferredSize() { |
| - gfx::Insets insets = GetTextInsets(); |
| - |
| - const int font_height = textfield_view_ ? textfield_view_->GetFontHeight() : |
| - font_list_.GetHeight(); |
| - return gfx::Size( |
| - GetPrimaryFont().GetExpectedTextWidth(default_width_in_chars_) |
| - + insets.width(), |
| - font_height + insets.height()); |
| + const gfx::Insets& insets = GetInsets(); |
| + return gfx::Size(GetFontList().GetExpectedTextWidth(default_width_in_chars_) + |
| + insets.width(), GetFontList().GetHeight() + insets.height()); |
| } |
| void Textfield::AboutToRequestFocusFromTabTraversal(bool reverse) { |
| @@ -455,35 +433,252 @@ bool Textfield::SkipDefaultKeyEventProcessing(const ui::KeyEvent& e) { |
| } |
| void Textfield::OnPaint(gfx::Canvas* canvas) { |
| - View::OnPaint(canvas); |
| + OnPaintBackground(canvas); |
| + PaintTextAndCursor(canvas); |
| + if (draw_border_) |
| + OnPaintBorder(canvas); |
| if (NativeViewHost::kRenderNativeControlFocus) |
| Painter::PaintFocusPainter(this, canvas, focus_painter_.get()); |
| } |
| -bool Textfield::OnKeyPressed(const ui::KeyEvent& e) { |
| - return textfield_view_ && textfield_view_->HandleKeyPressed(e); |
| -} |
| +bool Textfield::OnKeyPressed(const ui::KeyEvent& event) { |
| + bool handled = controller_ && controller_->HandleKeyEvent(this, event); |
| + touch_selection_controller_.reset(); |
| + if (handled) |
| + return true; |
| + |
| + // TODO(oshima): Refactor and consolidate with ExecuteCommand. |
| + if (event.type() == ui::ET_KEY_PRESSED) { |
| + ui::KeyboardCode key_code = event.key_code(); |
| + if (key_code == ui::VKEY_TAB || event.IsUnicodeKeyCode()) |
| + return false; |
| + |
| + gfx::RenderText* render_text = GetRenderText(); |
| + const bool editable = !read_only(); |
| + const bool readable = !IsObscured(); |
| + const bool shift = event.IsShiftDown(); |
| + const bool control = event.IsControlDown(); |
| + const bool alt = event.IsAltDown() || event.IsAltGrDown(); |
| + bool text_changed = false; |
| + bool cursor_changed = false; |
| + |
| + OnBeforeUserAction(); |
| + switch (key_code) { |
| + case ui::VKEY_Z: |
| + if (control && !shift && !alt && editable) |
| + cursor_changed = text_changed = model_->Undo(); |
| + else if (control && shift && !alt && editable) |
| + cursor_changed = text_changed = model_->Redo(); |
| + break; |
| + case ui::VKEY_Y: |
| + if (control && !alt && editable) |
| + cursor_changed = text_changed = model_->Redo(); |
| + break; |
| + case ui::VKEY_A: |
| + if (control && !alt) { |
| + model_->SelectAll(false); |
| + cursor_changed = true; |
| + } |
| + break; |
| + case ui::VKEY_X: |
| + if (control && !alt && editable && readable) |
| + cursor_changed = text_changed = Cut(); |
| + break; |
| + case ui::VKEY_C: |
| + if (control && !alt && readable) |
| + Copy(); |
| + break; |
| + case ui::VKEY_V: |
| + if (control && !alt && editable) |
| + cursor_changed = text_changed = Paste(); |
| + break; |
| + case ui::VKEY_RIGHT: |
| + case ui::VKEY_LEFT: { |
| + // We should ignore the alt-left/right keys because alt key doesn't make |
| + // any special effects for them and they can be shortcut keys such like |
| + // forward/back of the browser history. |
| + if (alt) |
| + break; |
| + const gfx::Range selection_range = render_text->selection(); |
| + model_->MoveCursor( |
| + control ? gfx::WORD_BREAK : gfx::CHARACTER_BREAK, |
| + (key_code == ui::VKEY_RIGHT) ? gfx::CURSOR_RIGHT : gfx::CURSOR_LEFT, |
| + shift); |
| + cursor_changed = render_text->selection() != selection_range; |
| + break; |
| + } |
| + case ui::VKEY_END: |
| + case ui::VKEY_HOME: |
| + if ((key_code == ui::VKEY_HOME) == |
| + (render_text->GetTextDirection() == base::i18n::RIGHT_TO_LEFT)) |
| + model_->MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_RIGHT, shift); |
| + else |
| + model_->MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_LEFT, shift); |
| + cursor_changed = true; |
| + break; |
| + case ui::VKEY_BACK: |
| + case ui::VKEY_DELETE: |
| + if (!editable) |
| + break; |
| + if (!model_->HasSelection()) { |
| + gfx::VisualCursorDirection direction = (key_code == ui::VKEY_DELETE) ? |
| + gfx::CURSOR_RIGHT : gfx::CURSOR_LEFT; |
| + if (shift && control) { |
| + // If both shift and control are pressed, then erase up to the |
| + // beginning/end of the buffer in ChromeOS. In windows, do nothing. |
|
sky
2014/01/09 21:43:11
chromeos->non windows (since linux-aura will get t
msw
2014/01/10 20:34:55
Done.
|
| +#if defined(OS_WIN) |
| + break; |
| +#else |
| + model_->MoveCursor(gfx::LINE_BREAK, direction, true); |
| +#endif |
| + } else if (control) { |
| + // If only control is pressed, then erase the previous/next word. |
| + model_->MoveCursor(gfx::WORD_BREAK, direction, true); |
| + } |
| + } |
| + if (key_code == ui::VKEY_BACK) |
| + model_->Backspace(); |
| + else if (shift && model_->HasSelection() && readable) |
| + Cut(); |
| + else |
| + model_->Delete(); |
| + |
| + // Consume backspace and delete keys even if the edit did nothing. This |
| + // prevents potential unintended side-effects of further event handling. |
| + text_changed = true; |
| + break; |
| + case ui::VKEY_INSERT: |
| + if (control && !shift && readable) |
| + Copy(); |
| + else if (shift && !control && editable) |
| + cursor_changed = text_changed = Paste(); |
| + break; |
| + default: |
| + break; |
| + } |
| -bool Textfield::OnKeyReleased(const ui::KeyEvent& e) { |
| - return textfield_view_ && textfield_view_->HandleKeyReleased(e); |
| + // We must have input method in order to support text input. |
| + DCHECK(GetInputMethod()); |
| + UpdateAfterChange(text_changed, cursor_changed); |
| + OnAfterUserAction(); |
| + return (text_changed || cursor_changed); |
| + } |
| + return false; |
| +} |
| + |
| +bool Textfield::OnMousePressed(const ui::MouseEvent& event) { |
| + OnBeforeUserAction(); |
| + TrackMouseClicks(event); |
| + |
| + if (!controller_ || !controller_->HandleMouseEvent(this, event)) { |
| + if (event.IsOnlyLeftMouseButton() || event.IsOnlyRightMouseButton()) |
| + RequestFocus(); |
| + |
| + if (event.IsOnlyLeftMouseButton()) { |
| + initiating_drag_ = false; |
| + bool can_drag = true; |
| + |
| + switch (aggregated_clicks_) { |
| + case 0: |
| + if (can_drag && |
| + GetRenderText()->IsPointInSelection(event.location())) { |
| + initiating_drag_ = true; |
| + } else { |
| + MoveCursorTo(event.location(), event.IsShiftDown()); |
| + } |
| + break; |
| + case 1: |
| + MoveCursorTo(event.location(), false); |
| + model_->SelectWord(); |
| + double_click_word_ = GetRenderText()->selection(); |
| + OnCaretBoundsChanged(); |
| + break; |
| + case 2: |
| + model_->SelectAll(false); |
| + OnCaretBoundsChanged(); |
| + break; |
| + default: |
| + NOTREACHED(); |
| + } |
| + } |
| + SchedulePaint(); |
| + } |
| + |
| + OnAfterUserAction(); |
| + touch_selection_controller_.reset(); |
| + return true; |
| } |
| -bool Textfield::OnMouseDragged(const ui::MouseEvent& e) { |
| - if (!e.IsOnlyRightMouseButton()) |
| - return View::OnMouseDragged(e); |
| +bool Textfield::OnMouseDragged(const ui::MouseEvent& event) { |
| + // Don't adjust the cursor on a potential drag and drop, or if the mouse |
| + // movement from the last mouse click does not exceed the drag threshold. |
| + if (initiating_drag_ || !event.IsOnlyLeftMouseButton() || |
| + !ExceededDragThreshold(event.location() - last_click_location_)) { |
| + return true; |
| + } |
| + |
| + if (!event.IsOnlyRightMouseButton()) { |
| + OnBeforeUserAction(); |
| + MoveCursorTo(event.location(), true); |
| + if (aggregated_clicks_ == 1) { |
| + model_->SelectWord(); |
| + // Expand the selection so the initially selected word remains selected. |
| + gfx::Range selection = GetRenderText()->selection(); |
| + const size_t min = std::min(selection.GetMin(), |
| + double_click_word_.GetMin()); |
| + const size_t max = std::max(selection.GetMax(), |
| + double_click_word_.GetMax()); |
| + const bool reversed = selection.is_reversed(); |
| + selection.set_start(reversed ? max : min); |
| + selection.set_end(reversed ? min : max); |
| + model_->SelectRange(selection); |
| + } |
| + SchedulePaint(); |
| + OnAfterUserAction(); |
| + } |
| return true; |
| } |
| +void Textfield::OnMouseReleased(const ui::MouseEvent& event) { |
| + OnBeforeUserAction(); |
| + // Cancel suspected drag initiations, the user was clicking in the selection. |
| + if (initiating_drag_ && MoveCursorTo(event.location(), false)) |
| + SchedulePaint(); |
| + initiating_drag_ = false; |
| + OnAfterUserAction(); |
| +} |
| + |
| void Textfield::OnFocus() { |
| - if (textfield_view_) |
| - textfield_view_->HandleFocus(); |
| + GetRenderText()->set_focused(true); |
| + is_cursor_visible_ = true; |
| + SchedulePaint(); |
| + GetInputMethod()->OnFocus(); |
| + OnCaretBoundsChanged(); |
| + |
| + const size_t caret_blink_ms = Textfield::GetCaretBlinkMs(); |
| + if (caret_blink_ms != 0) { |
| + base::MessageLoop::current()->PostDelayedTask( |
| + FROM_HERE, |
| + base::Bind(&Textfield::UpdateCursor, |
| + cursor_weak_ptr_factory_.GetWeakPtr()), |
| + base::TimeDelta::FromMilliseconds(caret_blink_ms)); |
| + } |
| + |
| View::OnFocus(); |
| SchedulePaint(); |
| } |
| void Textfield::OnBlur() { |
| - if (textfield_view_) |
| - textfield_view_->HandleBlur(); |
| + GetRenderText()->set_focused(false); |
| + GetInputMethod()->OnBlur(); |
| + // Stop blinking cursor. |
| + cursor_weak_ptr_factory_.InvalidateWeakPtrs(); |
| + if (is_cursor_visible_) { |
| + is_cursor_visible_ = false; |
| + RepaintCursor(); |
| + } |
| + |
| + touch_selection_controller_.reset(); |
| // Border typically draws focus indicator. |
| SchedulePaint(); |
| @@ -496,9 +691,9 @@ void Textfield::GetAccessibleState(ui::AccessibleViewState* state) { |
| state->state |= ui::AccessibilityTypes::STATE_READONLY; |
| if (IsObscured()) |
| state->state |= ui::AccessibilityTypes::STATE_PROTECTED; |
| - state->value = text_; |
| + state->value = text(); |
| - const gfx::Range range = textfield_view_->GetSelectedRange(); |
| + const gfx::Range range = GetSelectedRange(); |
| state->selection_start = range.start(); |
| state->selection_end = range.end(); |
| @@ -510,43 +705,695 @@ void Textfield::GetAccessibleState(ui::AccessibleViewState* state) { |
| } |
| ui::TextInputClient* Textfield::GetTextInputClient() { |
| - return textfield_view_ ? textfield_view_->GetTextInputClient() : NULL; |
| + return read_only_ ? NULL : this; |
| } |
| gfx::Point Textfield::GetKeyboardContextMenuLocation() { |
| - return textfield_view_ ? textfield_view_->GetContextMenuLocation() : |
| - View::GetKeyboardContextMenuLocation(); |
| + return GetCaretBounds().bottom_right(); |
| +} |
| + |
| +void Textfield::OnNativeThemeChanged(const ui::NativeTheme* theme) { |
| + UpdateColorsFromTheme(theme); |
| } |
| void Textfield::OnEnabledChanged() { |
| View::OnEnabledChanged(); |
| - if (textfield_view_) |
| - textfield_view_->UpdateEnabled(); |
| + if (GetInputMethod()) |
| + GetInputMethod()->OnTextInputTypeChanged(this); |
| + SchedulePaint(); |
| +} |
| + |
| +const char* Textfield::GetClassName() const { |
| + return kViewClassName; |
| } |
| -void Textfield::ViewHierarchyChanged( |
| - const ViewHierarchyChangedDetails& details) { |
| - if (details.is_add && !textfield_view_ && GetWidget()) { |
| - // The textfield view's lifetime is managed by the view hierarchy. |
| - textfield_view_ = new NativeTextfieldViews(this); |
| - AddChildViewAt(textfield_view_, 0); |
| - Layout(); |
| - UpdateAllProperties(); |
| +gfx::NativeCursor Textfield::GetCursor(const ui::MouseEvent& event) { |
| + bool in_selection = GetRenderText()->IsPointInSelection(event.location()); |
| + bool drag_event = event.type() == ui::ET_MOUSE_DRAGGED; |
| + bool text_cursor = !initiating_drag_ && (drag_event || !in_selection); |
| +#if defined(USE_AURA) |
| + return text_cursor ? ui::kCursorIBeam : ui::kCursorNull; |
| +#elif defined(OS_WIN) |
| + static HCURSOR ibeam = LoadCursor(NULL, IDC_IBEAM); |
| + static HCURSOR arrow = LoadCursor(NULL, IDC_ARROW); |
| + return text_cursor ? ibeam : arrow; |
| +#endif |
| +} |
| + |
| +void Textfield::OnGestureEvent(ui::GestureEvent* event) { |
| + switch (event->type()) { |
| + case ui::ET_GESTURE_TAP_DOWN: |
| + OnBeforeUserAction(); |
| + RequestFocus(); |
| + // We don't deselect if the point is in the selection |
| + // because TAP_DOWN may turn into a LONG_PRESS. |
| + if (!GetRenderText()->IsPointInSelection(event->location()) && |
| + MoveCursorTo(event->location(), false)) |
| + SchedulePaint(); |
| + OnAfterUserAction(); |
| + event->SetHandled(); |
| + break; |
| + case ui::ET_GESTURE_SCROLL_UPDATE: |
| + OnBeforeUserAction(); |
| + if (MoveCursorTo(event->location(), true)) |
| + SchedulePaint(); |
| + OnAfterUserAction(); |
| + event->SetHandled(); |
| + break; |
| + case ui::ET_GESTURE_SCROLL_END: |
| + case ui::ET_SCROLL_FLING_START: |
| + CreateTouchSelectionControllerAndNotifyIt(); |
| + event->SetHandled(); |
| + break; |
| + case ui::ET_GESTURE_TAP: |
| + if (event->details().tap_count() == 1) { |
| + CreateTouchSelectionControllerAndNotifyIt(); |
| + } else { |
| + OnBeforeUserAction(); |
| + SelectAll(false); |
| + OnAfterUserAction(); |
| + event->SetHandled(); |
| + } |
| +#if defined(OS_WIN) && defined(USE_AURA) |
| + if (!read_only()) |
| + base::win::DisplayVirtualKeyboard(); |
| +#endif |
| + break; |
| + case ui::ET_GESTURE_LONG_PRESS: |
| + // If long press happens outside selection, select word and show context |
| + // menu (If touch selection is enabled, context menu is shown by the |
| + // |touch_selection_controller_|, hence we mark the event handled. |
| + // Otherwise, the regular context menu will be shown by views). |
| + // If long press happens in selected text and touch drag drop is enabled, |
| + // we will turn off touch selection (if one exists) and let views do drag |
| + // drop. |
| + if (!GetRenderText()->IsPointInSelection(event->location())) { |
| + OnBeforeUserAction(); |
| + model_->SelectWord(); |
| + touch_selection_controller_.reset( |
| + ui::TouchSelectionController::create(this)); |
| + OnCaretBoundsChanged(); |
| + SchedulePaint(); |
| + OnAfterUserAction(); |
| + if (touch_selection_controller_) |
| + event->SetHandled(); |
| + } else if (switches::IsTouchDragDropEnabled()) { |
| + initiating_drag_ = true; |
| + touch_selection_controller_.reset(); |
| + } else { |
| + if (!touch_selection_controller_) |
| + CreateTouchSelectionControllerAndNotifyIt(); |
| + if (touch_selection_controller_) |
| + event->SetHandled(); |
| + } |
| + return; |
| + case ui::ET_GESTURE_LONG_TAP: |
| + if (!touch_selection_controller_) |
| + CreateTouchSelectionControllerAndNotifyIt(); |
| + |
| + // If touch selection is enabled, the context menu on long tap will be |
| + // shown by the |touch_selection_controller_|, hence we mark the event |
| + // handled so views does not try to show context menu on it. |
| + if (touch_selection_controller_) |
| + event->SetHandled(); |
| + break; |
| + default: |
| + return; |
| } |
| } |
| -const char* Textfield::GetClassName() const { |
| - return kViewClassName; |
| +bool Textfield::GetDropFormats( |
| + int* formats, |
| + std::set<OSExchangeData::CustomFormat>* custom_formats) { |
| + if (!enabled() || read_only()) |
| + return false; |
| + // TODO(msw): Can we support URL, FILENAME, etc.? |
| + *formats = ui::OSExchangeData::STRING; |
| + if (controller_) |
| + controller_->AppendDropFormats(formats, custom_formats); |
| + return true; |
| +} |
| + |
| +bool Textfield::CanDrop(const OSExchangeData& data) { |
| + int formats; |
| + std::set<OSExchangeData::CustomFormat> custom_formats; |
| + GetDropFormats(&formats, &custom_formats); |
| + return enabled() && !read_only() && |
| + data.HasAnyFormat(formats, custom_formats); |
| +} |
| + |
| +int Textfield::OnDragUpdated(const ui::DropTargetEvent& event) { |
| + DCHECK(CanDrop(event.data())); |
| + gfx::RenderText* render_text = GetRenderText(); |
| + const gfx::Range& selection = render_text->selection(); |
| + drop_cursor_position_ = render_text->FindCursorPosition(event.location()); |
| + bool in_selection = !selection.is_empty() && |
| + selection.Contains(gfx::Range(drop_cursor_position_.caret_pos())); |
| + is_drop_cursor_visible_ = !in_selection; |
| + // TODO(msw): Pan over text when the user drags to the visible text edge. |
| + OnCaretBoundsChanged(); |
| + SchedulePaint(); |
| + |
| + if (initiating_drag_) { |
| + if (in_selection) |
| + return ui::DragDropTypes::DRAG_NONE; |
| + return event.IsControlDown() ? ui::DragDropTypes::DRAG_COPY : |
| + ui::DragDropTypes::DRAG_MOVE; |
| + } |
| + return ui::DragDropTypes::DRAG_COPY | ui::DragDropTypes::DRAG_MOVE; |
| +} |
| + |
| +void Textfield::OnDragExited() { |
| + is_drop_cursor_visible_ = false; |
| + SchedulePaint(); |
| +} |
| + |
| +int Textfield::OnPerformDrop(const ui::DropTargetEvent& event) { |
| + DCHECK(CanDrop(event.data())); |
| + is_drop_cursor_visible_ = false; |
| + |
| + if (controller_) { |
| + int drag_operation = controller_->OnDrop(event.data()); |
| + if (drag_operation != ui::DragDropTypes::DRAG_NONE) |
| + return drag_operation; |
| + } |
| + |
| + gfx::RenderText* render_text = GetRenderText(); |
| + DCHECK(!initiating_drag_ || |
| + !render_text->IsPointInSelection(event.location())); |
| + OnBeforeUserAction(); |
| + skip_input_method_cancel_composition_ = true; |
| + |
| + gfx::SelectionModel drop_destination_model = |
| + render_text->FindCursorPosition(event.location()); |
| + base::string16 new_text; |
| + event.data().GetString(&new_text); |
| + new_text = GetTextForDisplay(new_text); |
| + |
| + // Delete the current selection for a drag and drop within this view. |
| + const bool move = initiating_drag_ && !event.IsControlDown() && |
| + event.source_operations() & ui::DragDropTypes::DRAG_MOVE; |
| + if (move) { |
| + // Adjust the drop destination if it is on or after the current selection. |
| + size_t pos = drop_destination_model.caret_pos(); |
| + pos -= render_text->selection().Intersect(gfx::Range(0, pos)).length(); |
| + model_->DeleteSelectionAndInsertTextAt(new_text, pos); |
| + } else { |
| + model_->MoveCursorTo(drop_destination_model); |
| + // Drop always inserts text even if the textfield is not in insert mode. |
| + model_->InsertText(new_text); |
| + } |
| + skip_input_method_cancel_composition_ = false; |
| + UpdateAfterChange(true, true); |
| + OnAfterUserAction(); |
| + return move ? ui::DragDropTypes::DRAG_MOVE : ui::DragDropTypes::DRAG_COPY; |
| +} |
| + |
| +void Textfield::OnDragDone() { |
| + initiating_drag_ = false; |
| + is_drop_cursor_visible_ = false; |
| +} |
| + |
| +void Textfield::OnBoundsChanged(const gfx::Rect& previous_bounds) { |
| + GetRenderText()->SetDisplayRect(GetContentsBounds()); |
| + OnCaretBoundsChanged(); |
| +} |
| + |
| +//////////////////////////////////////////////////////////////////////////////// |
| +// Textfield, TextfieldViewsModel::Delegate overrides: |
| + |
| +void Textfield::OnCompositionTextConfirmedOrCleared() { |
| + if (!skip_input_method_cancel_composition_) |
| + GetInputMethod()->CancelComposition(this); |
| +} |
| + |
| +//////////////////////////////////////////////////////////////////////////////// |
| +// Textfield, ContextMenuController overrides: |
| + |
| +void Textfield::ShowContextMenuForView( |
| + View* source, |
| + const gfx::Point& point, |
| + ui::MenuSourceType source_type) { |
| + UpdateContextMenu(); |
| + if (context_menu_runner_->RunMenuAt(GetWidget(), NULL, |
| + gfx::Rect(point, gfx::Size()), views::MenuItemView::TOPLEFT, |
| + source_type, |
| + MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU) == |
| + MenuRunner::MENU_DELETED) |
| + return; |
| +} |
| + |
| +//////////////////////////////////////////////////////////////////////////////// |
| +// Textfield, views::DragController overrides: |
| + |
| +void Textfield::WriteDragDataForView(views::View* sender, |
| + const gfx::Point& press_pt, |
| + OSExchangeData* data) { |
| + DCHECK_NE(ui::DragDropTypes::DRAG_NONE, |
| + GetDragOperationsForView(sender, press_pt)); |
| + data->SetString(model_->GetSelectedText()); |
| + scoped_ptr<gfx::Canvas> canvas( |
| + views::GetCanvasForDragImage(GetWidget(), size())); |
| + GetRenderText()->DrawSelectedTextForDrag(canvas.get()); |
| + drag_utils::SetDragImageOnDataObject(*canvas, size(), |
| + press_pt.OffsetFromOrigin(), |
| + data); |
| + if (controller_) |
| + controller_->OnWriteDragData(data); |
| +} |
| + |
| +int Textfield::GetDragOperationsForView(views::View* sender, |
| + const gfx::Point& p) { |
| + int drag_operations = ui::DragDropTypes::DRAG_COPY; |
| + if (!enabled() || IsObscured() || !GetRenderText()->IsPointInSelection(p)) |
| + drag_operations = ui::DragDropTypes::DRAG_NONE; |
| + else if (sender == this && !read_only()) |
| + drag_operations = |
| + ui::DragDropTypes::DRAG_MOVE | ui::DragDropTypes::DRAG_COPY; |
| + if (controller_) |
| + controller_->OnGetDragOperationsForTextfield(&drag_operations); |
| + return drag_operations; |
| +} |
| + |
| +bool Textfield::CanStartDragForView(View* sender, |
| + const gfx::Point& press_pt, |
| + const gfx::Point& p) { |
| + return initiating_drag_ && GetRenderText()->IsPointInSelection(press_pt); |
| +} |
| + |
| +//////////////////////////////////////////////////////////////////////////////// |
| +// Textfield, ui::TouchEditable overrides: |
| + |
| +void Textfield::SelectRect(const gfx::Point& start, const gfx::Point& end) { |
| + if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE) |
| + return; |
| + |
| + gfx::SelectionModel start_caret = GetRenderText()->FindCursorPosition(start); |
| + gfx::SelectionModel end_caret = GetRenderText()->FindCursorPosition(end); |
| + gfx::SelectionModel selection( |
| + gfx::Range(start_caret.caret_pos(), end_caret.caret_pos()), |
| + end_caret.caret_affinity()); |
| + |
| + OnBeforeUserAction(); |
| + model_->SelectSelectionModel(selection); |
| + OnCaretBoundsChanged(); |
| + SchedulePaint(); |
| + OnAfterUserAction(); |
| +} |
| + |
| +void Textfield::MoveCaretTo(const gfx::Point& point) { |
| + SelectRect(point, point); |
| +} |
| + |
| +void Textfield::GetSelectionEndPoints(gfx::Rect* p1, gfx::Rect* p2) { |
| + gfx::RenderText* render_text = GetRenderText(); |
| + const gfx::SelectionModel& sel = render_text->selection_model(); |
| + gfx::SelectionModel start_sel = |
| + render_text->GetSelectionModelForSelectionStart(); |
| + *p1 = render_text->GetCursorBounds(start_sel, true); |
| + *p2 = render_text->GetCursorBounds(sel, true); |
| +} |
| + |
| +gfx::Rect Textfield::GetBounds() { |
| + return bounds(); |
| +} |
| + |
| +gfx::NativeView Textfield::GetNativeView() const { |
| + return GetWidget()->GetNativeView(); |
| +} |
| + |
| +void Textfield::ConvertPointToScreen(gfx::Point* point) { |
| + View::ConvertPointToScreen(this, point); |
| +} |
| + |
| +void Textfield::ConvertPointFromScreen(gfx::Point* point) { |
| + View::ConvertPointFromScreen(this, point); |
| +} |
| + |
| +bool Textfield::DrawsHandles() { |
| + return false; |
| +} |
| + |
| +void Textfield::OpenContextMenu(const gfx::Point& anchor) { |
| + touch_selection_controller_.reset(); |
| + ShowContextMenu(anchor, ui::MENU_SOURCE_TOUCH_EDIT_MENU); |
| +} |
| + |
| +//////////////////////////////////////////////////////////////////////////////// |
| +// Textfield, ui::SimpleMenuModel::Delegate overrides: |
| + |
| +bool Textfield::IsCommandIdChecked(int command_id) const { |
| + return true; |
| +} |
| + |
| +bool Textfield::IsCommandIdEnabled(int command_id) const { |
| + if (controller_ && controller_->HandlesCommand(command_id)) |
| + return controller_->IsCommandIdEnabled(command_id); |
| + |
| + bool editable = !read_only(); |
| + base::string16 result; |
| + switch (command_id) { |
| + case IDS_APP_UNDO: |
| + return editable && model_->CanUndo(); |
| + case IDS_APP_CUT: |
| + return editable && model_->HasSelection() && !IsObscured(); |
| + case IDS_APP_COPY: |
| + return model_->HasSelection() && !IsObscured(); |
| + case IDS_APP_PASTE: |
| + ui::Clipboard::GetForCurrentThread()->ReadText( |
| + ui::CLIPBOARD_TYPE_COPY_PASTE, &result); |
| + return editable && !result.empty(); |
| + case IDS_APP_DELETE: |
| + return editable && model_->HasSelection(); |
| + case IDS_APP_SELECT_ALL: |
| + return !text().empty(); |
| + default: |
| + return controller_->IsCommandIdEnabled(command_id); |
| + } |
| +} |
| + |
| +bool Textfield::GetAcceleratorForCommandId(int command_id, |
| + ui::Accelerator* accelerator) { |
| + return false; |
| +} |
| + |
| +bool Textfield::IsItemForCommandIdDynamic(int command_id) const { |
| + return controller_ && controller_->IsItemForCommandIdDynamic(command_id); |
| +} |
| + |
| +base::string16 Textfield::GetLabelForCommandId(int command_id) const { |
| + return controller_ ? controller_->GetLabelForCommandId(command_id) : |
| + base::string16(); |
| +} |
| + |
| +void Textfield::ExecuteCommand(int command_id, int event_flags) { |
| + touch_selection_controller_.reset(); |
| + if (!IsCommandIdEnabled(command_id)) |
| + return; |
| + |
| + if (controller_ && controller_->HandlesCommand(command_id)) { |
| + controller_->ExecuteCommand(command_id, 0); |
| + } else { |
| + bool text_changed = false; |
| + switch (command_id) { |
| + case IDS_APP_UNDO: |
| + OnBeforeUserAction(); |
| + text_changed = model_->Undo(); |
| + UpdateAfterChange(text_changed, text_changed); |
| + OnAfterUserAction(); |
| + break; |
| + case IDS_APP_CUT: |
| + OnBeforeUserAction(); |
| + text_changed = Cut(); |
| + UpdateAfterChange(text_changed, text_changed); |
| + OnAfterUserAction(); |
| + break; |
| + case IDS_APP_COPY: |
| + OnBeforeUserAction(); |
| + Copy(); |
| + OnAfterUserAction(); |
| + break; |
| + case IDS_APP_PASTE: |
| + OnBeforeUserAction(); |
| + text_changed = Paste(); |
| + UpdateAfterChange(text_changed, text_changed); |
| + OnAfterUserAction(); |
| + break; |
| + case IDS_APP_DELETE: |
| + OnBeforeUserAction(); |
| + text_changed = model_->Delete(); |
| + UpdateAfterChange(text_changed, text_changed); |
| + OnAfterUserAction(); |
| + break; |
| + case IDS_APP_SELECT_ALL: |
| + OnBeforeUserAction(); |
| + SelectAll(false); |
| + UpdateAfterChange(false, true); |
| + OnAfterUserAction(); |
| + break; |
| + default: |
| + controller_->ExecuteCommand(command_id, 0); |
| + break; |
| + } |
| + } |
| +} |
| + |
| +//////////////////////////////////////////////////////////////////////////////// |
| +// Textfield, ui::TextInputClient overrides: |
| + |
| +void Textfield::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 Textfield::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 Textfield::ClearCompositionText() { |
| + if (!model_->HasCompositionText()) |
| + return; |
| + |
| + OnBeforeUserAction(); |
| + skip_input_method_cancel_composition_ = true; |
| + model_->CancelCompositionText(); |
| + skip_input_method_cancel_composition_ = false; |
| + UpdateAfterChange(true, true); |
| + OnAfterUserAction(); |
| +} |
| + |
| +void Textfield::InsertText(const base::string16& new_text) { |
| + // TODO(suzhe): Filter invalid characters. |
| + if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE || new_text.empty()) |
| + return; |
| + |
| + OnBeforeUserAction(); |
| + skip_input_method_cancel_composition_ = true; |
| + if (GetRenderText()->insert_mode()) |
| + model_->InsertText(GetTextForDisplay(new_text)); |
| + else |
| + model_->ReplaceText(GetTextForDisplay(new_text)); |
| + skip_input_method_cancel_composition_ = false; |
| + UpdateAfterChange(true, true); |
| + OnAfterUserAction(); |
| +} |
| + |
| +void Textfield::InsertChar(base::char16 ch, int flags) { |
| + // Filter out all control characters, including tab and new line characters, |
| + // and all characters with Alt modifier. But allow characters with the AltGr |
| + // modifier. On Windows AltGr is represented by Alt+Ctrl, and on Linux it's a |
| + // different flag that we don't care about. |
| + const bool should_insert_char = ((ch >= 0x20 && ch < 0x7F) || ch > 0x9F) && |
| + (flags & ~(ui::EF_SHIFT_DOWN | ui::EF_CAPS_LOCK_DOWN)) != ui::EF_ALT_DOWN; |
| + if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE || !should_insert_char) |
| + return; |
| + |
| + OnBeforeUserAction(); |
| + skip_input_method_cancel_composition_ = true; |
| + if (GetRenderText()->insert_mode()) |
| + model_->InsertChar(ch); |
| + else |
| + model_->ReplaceChar(ch); |
| + skip_input_method_cancel_composition_ = false; |
| + |
| + model_->SetText(GetTextForDisplay(text())); |
| + |
| + UpdateAfterChange(true, true); |
| + OnAfterUserAction(); |
| + |
| + if (IsObscured() && obscured_reveal_duration_ != base::TimeDelta()) { |
| + const size_t change_offset = model_->GetCursorPosition(); |
| + DCHECK_GT(change_offset, 0u); |
| + RevealObscuredChar(change_offset - 1, obscured_reveal_duration_); |
| + } |
| +} |
| + |
| +gfx::NativeWindow Textfield::GetAttachedWindow() const { |
| + // Imagine the following hierarchy. |
| + // [NativeWidget A] - FocusManager |
| + // [View] |
| + // [NativeWidget B] |
| + // [View] |
| + // [View X] |
| + // An important thing is that [NativeWidget A] owns Win32 input focus even |
| + // when [View X] is logically focused by FocusManager. As a result, an Win32 |
| + // IME may want to interact with the native view of [NativeWidget A] rather |
| + // than that of [NativeWidget B]. This is why we need to call |
| + // GetTopLevelWidget() here. |
| + return GetWidget()->GetTopLevelWidget()->GetNativeView(); |
| +} |
| + |
| +ui::TextInputType Textfield::GetTextInputType() const { |
| + if (read_only() || !enabled()) |
| + return ui::TEXT_INPUT_TYPE_NONE; |
| + return text_input_type_; |
| +} |
| + |
| +ui::TextInputMode Textfield::GetTextInputMode() const { |
| + return ui::TEXT_INPUT_MODE_DEFAULT; |
| +} |
| + |
| +bool Textfield::CanComposeInline() const { |
| + return true; |
| +} |
| + |
| +gfx::Rect Textfield::GetCaretBounds() const { |
| + gfx::Rect rect = GetRenderText()->GetUpdatedCursorBounds(); |
| + ConvertRectToScreen(this, &rect); |
| + return rect; |
| +} |
| + |
| +bool Textfield::GetCompositionCharacterBounds(uint32 index, |
| + gfx::Rect* rect) const { |
| + DCHECK(rect); |
| + if (!HasCompositionText()) |
| + return false; |
| + gfx::RenderText* render_text = GetRenderText(); |
| + const gfx::Range& composition_range = render_text->GetCompositionRange(); |
| + DCHECK(!composition_range.is_empty()); |
| + |
| + size_t text_index = composition_range.start() + index; |
| + if (composition_range.end() <= text_index) |
| + return false; |
| + if (!render_text->IsCursorablePosition(text_index)) { |
| + text_index = render_text->IndexOfAdjacentGrapheme( |
| + text_index, gfx::CURSOR_BACKWARD); |
| + } |
| + if (text_index < composition_range.start()) |
| + return false; |
| + const gfx::SelectionModel caret(text_index, gfx::CURSOR_BACKWARD); |
| + *rect = render_text->GetCursorBounds(caret, false); |
| + ConvertRectToScreen(this, rect); |
| + return true; |
| +} |
| + |
| +bool Textfield::HasCompositionText() const { |
| + return model_->HasCompositionText(); |
| +} |
| + |
| +bool Textfield::GetTextRange(gfx::Range* range) const { |
| + if (!ImeEditingAllowed()) |
| + return false; |
| + |
| + model_->GetTextRange(range); |
| + return true; |
| +} |
| + |
| +bool Textfield::GetCompositionTextRange(gfx::Range* range) const { |
| + if (!ImeEditingAllowed()) |
| + return false; |
| + |
| + model_->GetCompositionTextRange(range); |
| + return true; |
| +} |
| + |
| +bool Textfield::GetSelectionRange(gfx::Range* range) const { |
| + if (!ImeEditingAllowed()) |
| + return false; |
| + *range = GetRenderText()->selection(); |
| + return true; |
| +} |
| + |
| +bool Textfield::SetSelectionRange(const gfx::Range& range) { |
| + if (!ImeEditingAllowed() || !range.IsValid()) |
| + return false; |
| + |
| + OnBeforeUserAction(); |
| + SelectRange(range); |
| + OnAfterUserAction(); |
| + return true; |
| +} |
| + |
| +bool Textfield::DeleteRange(const gfx::Range& range) { |
| + if (!ImeEditingAllowed() || range.is_empty()) |
| + return false; |
| + |
| + OnBeforeUserAction(); |
| + model_->SelectRange(range); |
| + if (model_->HasSelection()) { |
| + model_->DeleteSelection(); |
| + UpdateAfterChange(true, true); |
| + } |
| + OnAfterUserAction(); |
| + return true; |
| +} |
| + |
| +bool Textfield::GetTextFromRange(const gfx::Range& range, |
| + base::string16* range_text) const { |
| + if (!ImeEditingAllowed() || !range.IsValid()) |
| + return false; |
| + |
| + gfx::Range text_range; |
| + if (!GetTextRange(&text_range) || !text_range.Contains(range)) |
| + return false; |
| + |
| + *range_text = model_->GetTextFromRange(range); |
| + return true; |
| +} |
| + |
| +void Textfield::OnInputMethodChanged() {} |
| + |
| +bool Textfield::ChangeTextDirectionAndLayoutAlignment( |
| + base::i18n::TextDirection direction) { |
| + // Restore text directionality mode when the indicated direction matches the |
| + // current forced mode; otherwise, force the mode indicated. This helps users |
| + // manage BiDi text layout without getting stuck in forced LTR or RTL modes. |
| + const gfx::DirectionalityMode mode = direction == base::i18n::RIGHT_TO_LEFT ? |
| + gfx::DIRECTIONALITY_FORCE_RTL : gfx::DIRECTIONALITY_FORCE_LTR; |
| + if (mode == GetRenderText()->directionality_mode()) |
| + GetRenderText()->SetDirectionalityMode(gfx::DIRECTIONALITY_FROM_TEXT); |
| + else |
| + GetRenderText()->SetDirectionalityMode(mode); |
| + SchedulePaint(); |
| + return true; |
| +} |
| + |
| +void Textfield::ExtendSelectionAndDelete(size_t before, size_t after) { |
| + gfx::Range range = GetRenderText()->selection(); |
| + DCHECK_GE(range.start(), before); |
| + |
| + range.set_start(range.start() - before); |
| + range.set_end(range.end() + after); |
| + gfx::Range text_range; |
| + if (GetTextRange(&text_range) && text_range.Contains(range)) |
| + DeleteRange(range); |
| +} |
| + |
| +void Textfield::EnsureCaretInRect(const gfx::Rect& rect) {} |
| + |
| +void Textfield::OnCandidateWindowShown() {} |
| + |
| +void Textfield::OnCandidateWindowUpdated() {} |
| + |
| +void Textfield::OnCandidateWindowHidden() {} |
| + |
| +//////////////////////////////////////////////////////////////////////////////// |
| +// Textfield, protected: |
| + |
| +gfx::RenderText* Textfield::GetRenderText() const { |
| + return model_->render_text(); |
| } |
| //////////////////////////////////////////////////////////////////////////////// |
| // Textfield, private: |
| -gfx::Insets Textfield::GetTextInsets() const { |
| - gfx::Insets insets = GetInsets(); |
| - if (draw_border_ && textfield_view_) |
| - insets += textfield_view_->GetInsets(); |
| - return insets; |
| +base::string16 Textfield::GetTextForDisplay(const base::string16& raw) { |
| + return style_ & Textfield::STYLE_LOWERCASE ? base::i18n::ToLower(raw) : raw; |
| } |
| void Textfield::AccessibilitySetValue(const base::string16& new_value) { |
| @@ -556,4 +1403,219 @@ void Textfield::AccessibilitySetValue(const base::string16& new_value) { |
| } |
| } |
| +void Textfield::UpdateHorizontalMargins() { |
| + int left, right; |
| + if (!GetHorizontalMargins(&left, &right)) |
| + return; |
| + gfx::Insets inset = GetInsets(); |
| + text_border_->SetInsets(inset.top(), left, inset.bottom(), right); |
| + OnBoundsChanged(GetBounds()); |
| +} |
| + |
| +void Textfield::UpdateBackgroundColor() { |
| + const SkColor color = GetBackgroundColor(); |
| + set_background(Background::CreateSolidBackground(color)); |
| + GetRenderText()->set_background_is_transparent(SkColorGetA(color) != 0xFF); |
| + SchedulePaint(); |
| +} |
| + |
| +void Textfield::UpdateColorsFromTheme(const ui::NativeTheme* theme) { |
| + gfx::RenderText* render_text = GetRenderText(); |
| + render_text->SetColor(GetTextColor()); |
| + UpdateBackgroundColor(); |
| + render_text->set_cursor_color(GetTextColor()); |
| + render_text->set_selection_color(theme->GetSystemColor( |
| + ui::NativeTheme::kColorId_TextfieldSelectionColor)); |
| + render_text->set_selection_background_focused_color(theme->GetSystemColor( |
| + ui::NativeTheme::kColorId_TextfieldSelectionBackgroundFocused)); |
| +} |
| + |
| +void Textfield::UpdateAfterChange(bool text_changed, bool cursor_changed) { |
| + if (text_changed) { |
| + if (controller_) |
| + controller_->ContentsChanged(this, text()); |
| + NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_TEXT_CHANGED, true); |
| + } |
| + if (cursor_changed) { |
| + is_cursor_visible_ = true; |
| + RepaintCursor(); |
| + if (!text_changed) { |
| + // TEXT_CHANGED implies SELECTION_CHANGED, so we only need to fire |
| + // this if only the selection changed. |
| + NotifyAccessibilityEvent( |
| + ui::AccessibilityTypes::EVENT_SELECTION_CHANGED, true); |
| + } |
| + } |
| + if (text_changed || cursor_changed) { |
| + OnCaretBoundsChanged(); |
| + SchedulePaint(); |
| + } |
| +} |
| + |
| +void Textfield::UpdateCursor() { |
| + const size_t caret_blink_ms = Textfield::GetCaretBlinkMs(); |
| + is_cursor_visible_ = !is_cursor_visible_ || (caret_blink_ms == 0); |
| + RepaintCursor(); |
| + if (caret_blink_ms != 0) { |
| + base::MessageLoop::current()->PostDelayedTask( |
| + FROM_HERE, |
| + base::Bind(&Textfield::UpdateCursor, |
|
sky
2014/01/09 21:43:11
Seems like a repeating timer would be less code.
msw
2014/01/10 20:34:55
Done. I also reset the timer in UpdateAfterChange,
|
| + cursor_weak_ptr_factory_.GetWeakPtr()), |
| + base::TimeDelta::FromMilliseconds(caret_blink_ms)); |
| + } |
| +} |
| + |
| +void Textfield::RepaintCursor() { |
| + gfx::Rect r(GetRenderText()->GetUpdatedCursorBounds()); |
| + r.Inset(-1, -1, -1, -1); |
| + SchedulePaintInRect(r); |
| +} |
| + |
| +void Textfield::PaintTextAndCursor(gfx::Canvas* canvas) { |
| + TRACE_EVENT0("views", "Textfield::PaintTextAndCursor"); |
| + canvas->Save(); |
| + gfx::RenderText* render_text = GetRenderText(); |
| + render_text->set_cursor_visible(!is_drop_cursor_visible_ && |
| + is_cursor_visible_ && !model_->HasSelection()); |
| + // Draw the text, cursor, and selection. |
| + render_text->Draw(canvas); |
| + |
| + // Draw the detached drop cursor that marks where the text will be dropped. |
| + if (is_drop_cursor_visible_) |
| + render_text->DrawCursor(canvas, drop_cursor_position_); |
| + |
| + // Draw placeholder text if needed. |
| + if (text().empty() && !GetPlaceholderText().empty()) { |
| + canvas->DrawStringRect(GetPlaceholderText(), GetFontList(), |
| + placeholder_text_color(), render_text->display_rect()); |
| + } |
| + canvas->Restore(); |
| +} |
| + |
| +bool Textfield::MoveCursorTo(const gfx::Point& point, bool select) { |
| + if (!model_->MoveCursorTo(point, select)) |
| + return false; |
| + OnCaretBoundsChanged(); |
| + return true; |
| +} |
| + |
| +void Textfield::OnCaretBoundsChanged() { |
| + if (GetInputMethod()) |
| + GetInputMethod()->OnCaretBoundsChanged(this); |
| + if (touch_selection_controller_) |
| + touch_selection_controller_->SelectionChanged(); |
| +} |
| + |
| +void Textfield::OnBeforeUserAction() { |
| + if (controller_) |
| + controller_->OnBeforeUserAction(this); |
| +} |
| + |
| +void Textfield::OnAfterUserAction() { |
| + if (controller_) |
| + controller_->OnAfterUserAction(this); |
| +} |
| + |
| +bool Textfield::Cut() { |
| + if (!read_only() && !IsObscured() && model_->Cut()) { |
| + if (controller_) |
| + controller_->OnAfterCutOrCopy(); |
| + return true; |
| + } |
| + return false; |
| +} |
| + |
| +bool Textfield::Copy() { |
| + if (!IsObscured() && model_->Copy()) { |
| + if (controller_) |
| + controller_->OnAfterCutOrCopy(); |
| + return true; |
| + } |
| + return false; |
| +} |
| + |
| +bool Textfield::Paste() { |
| + if (read_only()) |
| + return false; |
| + |
| + const base::string16 original_text = text(); |
| + if (model_->Paste()) { |
| + // As Paste is handled in model_->Paste(), the RenderText may contain |
| + // upper case characters. This is not consistent with other places |
| + // which keeps RenderText only containing lower case characters. |
| + base::string16 new_text = GetTextForDisplay(text()); |
| + model_->SetText(new_text); |
| + if (controller_) |
| + controller_->OnAfterPaste(); |
| + return true; |
| + } |
| + return false; |
| +} |
| + |
| +void Textfield::UpdateContextMenu() { |
| + if (!context_menu_contents_.get()) { |
| + context_menu_contents_.reset(new ui::SimpleMenuModel(this)); |
| + context_menu_contents_->AddItemWithStringId(IDS_APP_UNDO, IDS_APP_UNDO); |
| + context_menu_contents_->AddSeparator(ui::NORMAL_SEPARATOR); |
| + context_menu_contents_->AddItemWithStringId(IDS_APP_CUT, IDS_APP_CUT); |
| + context_menu_contents_->AddItemWithStringId(IDS_APP_COPY, IDS_APP_COPY); |
| + context_menu_contents_->AddItemWithStringId(IDS_APP_PASTE, IDS_APP_PASTE); |
| + context_menu_contents_->AddItemWithStringId(IDS_APP_DELETE, IDS_APP_DELETE); |
| + context_menu_contents_->AddSeparator(ui::NORMAL_SEPARATOR); |
| + context_menu_contents_->AddItemWithStringId(IDS_APP_SELECT_ALL, |
| + IDS_APP_SELECT_ALL); |
| + if (controller_) |
| + controller_->UpdateContextMenu(context_menu_contents_.get()); |
| + context_menu_delegate_.reset( |
| + new views::MenuModelAdapter(context_menu_contents_.get())); |
| + context_menu_runner_.reset( |
| + new MenuRunner(new views::MenuItemView(context_menu_delegate_.get()))); |
| + } |
| + |
| + context_menu_delegate_->BuildMenu(context_menu_runner_->GetMenu()); |
| +} |
| + |
| +void Textfield::TrackMouseClicks(const ui::MouseEvent& event) { |
| + if (event.IsOnlyLeftMouseButton()) { |
| + base::TimeDelta time_delta = event.time_stamp() - last_click_time_; |
| + if (time_delta.InMilliseconds() <= GetDoubleClickInterval() && |
| + !ExceededDragThreshold(event.location() - last_click_location_)) { |
| + // Upon clicking after a triple click, the count should go back to double |
| + // click and alternate between double and triple. This assignment maps |
| + // 0 to 1, 1 to 2, 2 to 1. |
| + aggregated_clicks_ = (aggregated_clicks_ % 2) + 1; |
| + } else { |
| + aggregated_clicks_ = 0; |
| + } |
| + last_click_time_ = event.time_stamp(); |
| + last_click_location_ = event.location(); |
| + } |
| +} |
| + |
| +bool Textfield::ImeEditingAllowed() const { |
| + // Disallow input method editing of password fields. |
| + ui::TextInputType t = GetTextInputType(); |
| + return (t != ui::TEXT_INPUT_TYPE_NONE && t != ui::TEXT_INPUT_TYPE_PASSWORD); |
| +} |
| + |
| +void Textfield::RevealObscuredChar(int index, const base::TimeDelta& duration) { |
| + GetRenderText()->SetObscuredRevealIndex(index); |
| + SchedulePaint(); |
| + |
| + if (index != -1) { |
| + obscured_reveal_timer_.Start(FROM_HERE, duration, |
| + base::Bind(&Textfield::RevealObscuredChar, base::Unretained(this), |
| + -1, base::TimeDelta())); |
| + } |
| +} |
| + |
| +void Textfield::CreateTouchSelectionControllerAndNotifyIt() { |
| + if (!touch_selection_controller_) { |
| + touch_selection_controller_.reset( |
| + ui::TouchSelectionController::create(this)); |
| + } |
| + if (touch_selection_controller_) |
| + touch_selection_controller_->SelectionChanged(); |
| +} |
| + |
| } // namespace views |