| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/views/controls/textfield/textfield.h" |
| |
| #include <string> |
| |
| #include "base/trace_event/trace_event.h" |
| #include "ui/accessibility/ax_view_state.h" |
| #include "ui/base/clipboard/scoped_clipboard_writer.h" |
| #include "ui/base/cursor/cursor.h" |
| #include "ui/base/dragdrop/drag_drop_types.h" |
| #include "ui/base/dragdrop/drag_utils.h" |
| #include "ui/base/touch/selection_bound.h" |
| #include "ui/base/ui_base_switches_util.h" |
| #include "ui/compositor/paint_context.h" |
| #include "ui/compositor/scoped_animation_duration_scale_mode.h" |
| #include "ui/events/event.h" |
| #include "ui/events/keycodes/keyboard_codes.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/display.h" |
| #include "ui/gfx/geometry/insets.h" |
| #include "ui/gfx/screen.h" |
| #include "ui/native_theme/native_theme.h" |
| #include "ui/strings/grit/ui_strings.h" |
| #include "ui/views/background.h" |
| #include "ui/views/controls/focusable_border.h" |
| #include "ui/views/controls/label.h" |
| #include "ui/views/controls/menu/menu_runner.h" |
| #include "ui/views/controls/native/native_view_host.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/native_cursor.h" |
| #include "ui/views/painter.h" |
| #include "ui/views/views_delegate.h" |
| #include "ui/views/widget/widget.h" |
| |
| #if defined(OS_WIN) |
| #include "base/win/win_util.h" |
| #endif |
| |
| #if defined(OS_LINUX) && !defined(OS_CHROMEOS) |
| #include "base/strings/utf_string_conversions.h" |
| #include "ui/events/linux/text_edit_command_auralinux.h" |
| #include "ui/events/linux/text_edit_key_bindings_delegate_auralinux.h" |
| #endif |
| |
| namespace views { |
| |
| namespace { |
| |
| // Default placeholder text color. |
| const SkColor kDefaultPlaceholderTextColor = SK_ColorLTGRAY; |
| |
| const int kNoCommand = 0; |
| |
| void ConvertRectToScreen(const View* src, gfx::Rect* r) { |
| DCHECK(src); |
| |
| gfx::Point new_origin = r->origin(); |
| View::ConvertPointToScreen(src, &new_origin); |
| r->set_origin(new_origin); |
| } |
| |
| // Get the drag selection timer delay, respecting animation scaling for testing. |
| int GetDragSelectionDelay() { |
| switch (ui::ScopedAnimationDurationScaleMode::duration_scale_mode()) { |
| case ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION: return 100; |
| case ui::ScopedAnimationDurationScaleMode::FAST_DURATION: return 25; |
| case ui::ScopedAnimationDurationScaleMode::SLOW_DURATION: return 400; |
| case ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION: return 1; |
| case ui::ScopedAnimationDurationScaleMode::ZERO_DURATION: return 0; |
| } |
| return 100; |
| } |
| |
| // Get the default command for a given key |event| and selection state. |
| int GetCommandForKeyEvent(const ui::KeyEvent& event, bool has_selection) { |
| if (event.type() != ui::ET_KEY_PRESSED || event.IsUnicodeKeyCode()) |
| return kNoCommand; |
| |
| const bool shift = event.IsShiftDown(); |
| const bool control = event.IsControlDown(); |
| const bool alt = event.IsAltDown() || event.IsAltGrDown(); |
| switch (event.key_code()) { |
| case ui::VKEY_Z: |
| if (control && !shift && !alt) |
| return IDS_APP_UNDO; |
| return (control && shift && !alt) ? IDS_APP_REDO : kNoCommand; |
| case ui::VKEY_Y: |
| return (control && !alt) ? IDS_APP_REDO : kNoCommand; |
| case ui::VKEY_A: |
| return (control && !alt) ? IDS_APP_SELECT_ALL : kNoCommand; |
| case ui::VKEY_X: |
| return (control && !alt) ? IDS_APP_CUT : kNoCommand; |
| case ui::VKEY_C: |
| return (control && !alt) ? IDS_APP_COPY : kNoCommand; |
| case ui::VKEY_V: |
| return (control && !alt) ? IDS_APP_PASTE : kNoCommand; |
| case ui::VKEY_RIGHT: |
| // Ignore alt+right, which may be a browser navigation shortcut. |
| if (alt) |
| return kNoCommand; |
| if (!shift) |
| return control ? IDS_MOVE_WORD_RIGHT : IDS_MOVE_RIGHT; |
| return control ? IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION : |
| IDS_MOVE_RIGHT_AND_MODIFY_SELECTION; |
| case ui::VKEY_LEFT: |
| // Ignore alt+left, which may be a browser navigation shortcut. |
| if (alt) |
| return kNoCommand; |
| if (!shift) |
| return control ? IDS_MOVE_WORD_LEFT : IDS_MOVE_LEFT; |
| return control ? IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION : |
| IDS_MOVE_LEFT_AND_MODIFY_SELECTION; |
| case ui::VKEY_HOME: |
| return shift ? IDS_MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION : |
| IDS_MOVE_TO_BEGINNING_OF_LINE; |
| case ui::VKEY_END: |
| return shift ? IDS_MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION : |
| IDS_MOVE_TO_END_OF_LINE; |
| case ui::VKEY_BACK: |
| if (!control || has_selection) |
| return IDS_DELETE_BACKWARD; |
| #if defined(OS_LINUX) |
| // Only erase by line break on Linux and ChromeOS. |
| if (shift) |
| return IDS_DELETE_TO_BEGINNING_OF_LINE; |
| #endif |
| return IDS_DELETE_WORD_BACKWARD; |
| case ui::VKEY_DELETE: |
| if (!control || has_selection) |
| return (shift && has_selection) ? IDS_APP_CUT : IDS_DELETE_FORWARD; |
| #if defined(OS_LINUX) |
| // Only erase by line break on Linux and ChromeOS. |
| if (shift) |
| return IDS_DELETE_TO_END_OF_LINE; |
| #endif |
| return IDS_DELETE_WORD_FORWARD; |
| case ui::VKEY_INSERT: |
| if (control && !shift) |
| return IDS_APP_COPY; |
| return (shift && !control) ? IDS_APP_PASTE : kNoCommand; |
| default: |
| return kNoCommand; |
| } |
| } |
| |
| #if defined(OS_LINUX) && !defined(OS_CHROMEOS) |
| // Convert a custom text edit |command| to the equivalent views command ID. |
| int GetViewsCommand(const ui::TextEditCommandAuraLinux& command, bool rtl) { |
| const bool select = command.extend_selection(); |
| switch (command.command_id()) { |
| case ui::TextEditCommandAuraLinux::COPY: |
| return IDS_APP_COPY; |
| case ui::TextEditCommandAuraLinux::CUT: |
| return IDS_APP_CUT; |
| case ui::TextEditCommandAuraLinux::DELETE_BACKWARD: |
| return IDS_DELETE_BACKWARD; |
| case ui::TextEditCommandAuraLinux::DELETE_FORWARD: |
| return IDS_DELETE_FORWARD; |
| case ui::TextEditCommandAuraLinux::DELETE_TO_BEGINING_OF_LINE: |
| case ui::TextEditCommandAuraLinux::DELETE_TO_BEGINING_OF_PARAGRAPH: |
| return IDS_DELETE_TO_BEGINNING_OF_LINE; |
| case ui::TextEditCommandAuraLinux::DELETE_TO_END_OF_LINE: |
| case ui::TextEditCommandAuraLinux::DELETE_TO_END_OF_PARAGRAPH: |
| return IDS_DELETE_TO_END_OF_LINE; |
| case ui::TextEditCommandAuraLinux::DELETE_WORD_BACKWARD: |
| return IDS_DELETE_WORD_BACKWARD; |
| case ui::TextEditCommandAuraLinux::DELETE_WORD_FORWARD: |
| return IDS_DELETE_WORD_FORWARD; |
| case ui::TextEditCommandAuraLinux::INSERT_TEXT: |
| return kNoCommand; |
| case ui::TextEditCommandAuraLinux::MOVE_BACKWARD: |
| if (rtl) |
| return select ? IDS_MOVE_RIGHT_AND_MODIFY_SELECTION : IDS_MOVE_RIGHT; |
| return select ? IDS_MOVE_LEFT_AND_MODIFY_SELECTION : IDS_MOVE_LEFT; |
| case ui::TextEditCommandAuraLinux::MOVE_DOWN: |
| return IDS_MOVE_DOWN; |
| case ui::TextEditCommandAuraLinux::MOVE_FORWARD: |
| if (rtl) |
| return select ? IDS_MOVE_LEFT_AND_MODIFY_SELECTION : IDS_MOVE_LEFT; |
| return select ? IDS_MOVE_RIGHT_AND_MODIFY_SELECTION : IDS_MOVE_RIGHT; |
| case ui::TextEditCommandAuraLinux::MOVE_LEFT: |
| return select ? IDS_MOVE_LEFT_AND_MODIFY_SELECTION : IDS_MOVE_LEFT; |
| case ui::TextEditCommandAuraLinux::MOVE_PAGE_DOWN: |
| case ui::TextEditCommandAuraLinux::MOVE_PAGE_UP: |
| return kNoCommand; |
| case ui::TextEditCommandAuraLinux::MOVE_RIGHT: |
| return select ? IDS_MOVE_RIGHT_AND_MODIFY_SELECTION : IDS_MOVE_RIGHT; |
| case ui::TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_DOCUMENT: |
| case ui::TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_LINE: |
| case ui::TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_PARAGRAPH: |
| return select ? IDS_MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION : |
| IDS_MOVE_TO_BEGINNING_OF_LINE; |
| case ui::TextEditCommandAuraLinux::MOVE_TO_END_OF_DOCUMENT: |
| case ui::TextEditCommandAuraLinux::MOVE_TO_END_OF_LINE: |
| case ui::TextEditCommandAuraLinux::MOVE_TO_END_OF_PARAGRAPH: |
| return select ? IDS_MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION : |
| IDS_MOVE_TO_END_OF_LINE; |
| case ui::TextEditCommandAuraLinux::MOVE_UP: |
| return IDS_MOVE_UP; |
| case ui::TextEditCommandAuraLinux::MOVE_WORD_BACKWARD: |
| if (rtl) { |
| return select ? IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION : |
| IDS_MOVE_WORD_RIGHT; |
| } |
| return select ? IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION : |
| IDS_MOVE_WORD_LEFT; |
| case ui::TextEditCommandAuraLinux::MOVE_WORD_FORWARD: |
| if (rtl) { |
| return select ? IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION : |
| IDS_MOVE_WORD_LEFT; |
| } |
| return select ? IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION : |
| IDS_MOVE_WORD_RIGHT; |
| case ui::TextEditCommandAuraLinux::MOVE_WORD_LEFT: |
| return select ? IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION : |
| IDS_MOVE_WORD_LEFT; |
| case ui::TextEditCommandAuraLinux::MOVE_WORD_RIGHT: |
| return select ? IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION : |
| IDS_MOVE_WORD_RIGHT; |
| case ui::TextEditCommandAuraLinux::PASTE: |
| return IDS_APP_PASTE; |
| case ui::TextEditCommandAuraLinux::SELECT_ALL: |
| return IDS_APP_SELECT_ALL; |
| case ui::TextEditCommandAuraLinux::SET_MARK: |
| case ui::TextEditCommandAuraLinux::UNSELECT: |
| case ui::TextEditCommandAuraLinux::INVALID_COMMAND: |
| return kNoCommand; |
| } |
| return kNoCommand; |
| } |
| #endif |
| |
| } // namespace |
| |
| // static |
| const char Textfield::kViewClassName[] = "Textfield"; |
| const int Textfield::kTextPadding = 3; |
| |
| // static |
| size_t Textfield::GetCaretBlinkMs() { |
| static const size_t default_value = 500; |
| #if defined(OS_WIN) |
| static const size_t system_value = ::GetCaretBlinkTime(); |
| if (system_value != 0) |
| return (system_value == INFINITE) ? 0 : system_value; |
| #endif |
| return default_value; |
| } |
| |
| Textfield::Textfield() |
| : model_(new TextfieldModel(this)), |
| controller_(NULL), |
| scheduled_edit_command_(kNoCommand), |
| read_only_(false), |
| default_width_in_chars_(0), |
| use_default_text_color_(true), |
| use_default_background_color_(true), |
| use_default_selection_text_color_(true), |
| use_default_selection_background_color_(true), |
| text_color_(SK_ColorBLACK), |
| background_color_(SK_ColorWHITE), |
| selection_text_color_(SK_ColorWHITE), |
| selection_background_color_(SK_ColorBLUE), |
| placeholder_text_color_(kDefaultPlaceholderTextColor), |
| text_input_type_(ui::TEXT_INPUT_TYPE_TEXT), |
| text_input_flags_(0), |
| performing_user_action_(false), |
| skip_input_method_cancel_composition_(false), |
| cursor_visible_(false), |
| drop_cursor_visible_(false), |
| initiating_drag_(false), |
| aggregated_clicks_(0), |
| drag_start_display_offset_(0), |
| touch_handles_hidden_due_to_scroll_(false), |
| weak_ptr_factory_(this) { |
| set_context_menu_controller(this); |
| set_drag_controller(this); |
| SetBorder(scoped_ptr<Border>(new FocusableBorder())); |
| SetFocusable(true); |
| |
| if (ViewsDelegate::views_delegate) { |
| password_reveal_duration_ = ViewsDelegate::views_delegate-> |
| GetDefaultTextfieldObscuredRevealDuration(); |
| } |
| } |
| |
| Textfield::~Textfield() {} |
| |
| void Textfield::SetReadOnly(bool read_only) { |
| // Update read-only without changing the focusable state (or active, etc.). |
| read_only_ = read_only; |
| if (GetInputMethod()) |
| GetInputMethod()->OnTextInputTypeChanged(this); |
| SetColor(GetTextColor()); |
| UpdateBackgroundColor(); |
| } |
| |
| void Textfield::SetTextInputType(ui::TextInputType type) { |
| GetRenderText()->SetObscured(type == ui::TEXT_INPUT_TYPE_PASSWORD); |
| text_input_type_ = type; |
| OnCaretBoundsChanged(); |
| if (GetInputMethod()) |
| GetInputMethod()->OnTextInputTypeChanged(this); |
| SchedulePaint(); |
| } |
| |
| void Textfield::SetTextInputFlags(int flags) { |
| text_input_flags_ = flags; |
| } |
| |
| void Textfield::SetText(const base::string16& new_text) { |
| model_->SetText(new_text); |
| OnCaretBoundsChanged(); |
| SchedulePaint(); |
| NotifyAccessibilityEvent(ui::AX_EVENT_TEXT_CHANGED, true); |
| } |
| |
| void Textfield::AppendText(const base::string16& new_text) { |
| if (new_text.empty()) |
| return; |
| model_->Append(new_text); |
| OnCaretBoundsChanged(); |
| SchedulePaint(); |
| } |
| |
| 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 GetRenderText()->GetDisplayTextDirection(); |
| } |
| |
| base::string16 Textfield::GetSelectedText() const { |
| return model_->GetSelectedText(); |
| } |
| |
| void Textfield::SelectAll(bool reversed) { |
| model_->SelectAll(reversed); |
| UpdateSelectionClipboard(); |
| UpdateAfterChange(false, true); |
| } |
| |
| void Textfield::SelectWordAt(const gfx::Point& point) { |
| model_->MoveCursorTo(point, false); |
| model_->SelectWord(); |
| UpdateAfterChange(false, true); |
| } |
| |
| void Textfield::ClearSelection() { |
| model_->ClearSelection(); |
| UpdateAfterChange(false, true); |
| } |
| |
| bool Textfield::HasSelection() const { |
| return !GetSelectedRange().is_empty(); |
| } |
| |
| SkColor Textfield::GetTextColor() const { |
| if (!use_default_text_color_) |
| return text_color_; |
| |
| return GetNativeTheme()->GetSystemColor(read_only() ? |
| ui::NativeTheme::kColorId_TextfieldReadOnlyColor : |
| ui::NativeTheme::kColorId_TextfieldDefaultColor); |
| } |
| |
| void Textfield::SetTextColor(SkColor color) { |
| text_color_ = color; |
| use_default_text_color_ = false; |
| SetColor(color); |
| } |
| |
| void Textfield::UseDefaultTextColor() { |
| use_default_text_color_ = true; |
| SetColor(GetTextColor()); |
| } |
| |
| SkColor Textfield::GetBackgroundColor() const { |
| if (!use_default_background_color_) |
| return background_color_; |
| |
| return GetNativeTheme()->GetSystemColor(read_only() ? |
| ui::NativeTheme::kColorId_TextfieldReadOnlyBackground : |
| ui::NativeTheme::kColorId_TextfieldDefaultBackground); |
| } |
| |
| void Textfield::SetBackgroundColor(SkColor color) { |
| background_color_ = color; |
| use_default_background_color_ = false; |
| UpdateBackgroundColor(); |
| } |
| |
| void Textfield::UseDefaultBackgroundColor() { |
| use_default_background_color_ = true; |
| UpdateBackgroundColor(); |
| } |
| |
| SkColor Textfield::GetSelectionTextColor() const { |
| return use_default_selection_text_color_ ? |
| GetNativeTheme()->GetSystemColor( |
| ui::NativeTheme::kColorId_TextfieldSelectionColor) : |
| selection_text_color_; |
| } |
| |
| void Textfield::SetSelectionTextColor(SkColor color) { |
| selection_text_color_ = color; |
| use_default_selection_text_color_ = false; |
| GetRenderText()->set_selection_color(GetSelectionTextColor()); |
| SchedulePaint(); |
| } |
| |
| void Textfield::UseDefaultSelectionTextColor() { |
| use_default_selection_text_color_ = true; |
| GetRenderText()->set_selection_color(GetSelectionTextColor()); |
| SchedulePaint(); |
| } |
| |
| void Textfield::SetShadows(const gfx::ShadowValues& shadows) { |
| GetRenderText()->set_shadows(shadows); |
| SchedulePaint(); |
| } |
| |
| SkColor Textfield::GetSelectionBackgroundColor() const { |
| return use_default_selection_background_color_ ? |
| GetNativeTheme()->GetSystemColor( |
| ui::NativeTheme::kColorId_TextfieldSelectionBackgroundFocused) : |
| selection_background_color_; |
| } |
| |
| void Textfield::SetSelectionBackgroundColor(SkColor color) { |
| selection_background_color_ = color; |
| use_default_selection_background_color_ = false; |
| GetRenderText()->set_selection_background_focused_color( |
| GetSelectionBackgroundColor()); |
| SchedulePaint(); |
| } |
| |
| void Textfield::UseDefaultSelectionBackgroundColor() { |
| use_default_selection_background_color_ = true; |
| GetRenderText()->set_selection_background_focused_color( |
| GetSelectionBackgroundColor()); |
| SchedulePaint(); |
| } |
| |
| bool Textfield::GetCursorEnabled() const { |
| return GetRenderText()->cursor_enabled(); |
| } |
| |
| void Textfield::SetCursorEnabled(bool enabled) { |
| GetRenderText()->SetCursorEnabled(enabled); |
| } |
| |
| const gfx::FontList& Textfield::GetFontList() const { |
| return GetRenderText()->font_list(); |
| } |
| |
| void Textfield::SetFontList(const gfx::FontList& font_list) { |
| GetRenderText()->SetFontList(font_list); |
| OnCaretBoundsChanged(); |
| PreferredSizeChanged(); |
| } |
| |
| base::string16 Textfield::GetPlaceholderText() const { |
| return placeholder_text_; |
| } |
| |
| gfx::HorizontalAlignment Textfield::GetHorizontalAlignment() const { |
| return GetRenderText()->horizontal_alignment(); |
| } |
| |
| void Textfield::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) { |
| GetRenderText()->SetHorizontalAlignment(alignment); |
| } |
| |
| void Textfield::ShowImeIfNeeded() { |
| if (enabled() && !read_only()) |
| GetInputMethod()->ShowImeIfNeeded(); |
| } |
| |
| bool Textfield::IsIMEComposing() const { |
| return model_->HasCompositionText(); |
| } |
| |
| const gfx::Range& Textfield::GetSelectedRange() const { |
| return GetRenderText()->selection(); |
| } |
| |
| void Textfield::SelectRange(const gfx::Range& range) { |
| model_->SelectRange(range); |
| UpdateAfterChange(false, true); |
| } |
| |
| const gfx::SelectionModel& Textfield::GetSelectionModel() const { |
| return GetRenderText()->selection_model(); |
| } |
| |
| void Textfield::SelectSelectionModel(const gfx::SelectionModel& sel) { |
| model_->SelectSelectionModel(sel); |
| UpdateAfterChange(false, true); |
| } |
| |
| size_t Textfield::GetCursorPosition() const { |
| return model_->GetCursorPosition(); |
| } |
| |
| void Textfield::SetColor(SkColor value) { |
| GetRenderText()->SetColor(value); |
| SchedulePaint(); |
| } |
| |
| void Textfield::ApplyColor(SkColor value, const gfx::Range& range) { |
| GetRenderText()->ApplyColor(value, range); |
| SchedulePaint(); |
| } |
| |
| void Textfield::SetStyle(gfx::TextStyle style, bool value) { |
| GetRenderText()->SetStyle(style, value); |
| SchedulePaint(); |
| } |
| |
| void Textfield::ApplyStyle(gfx::TextStyle style, |
| bool value, |
| const gfx::Range& range) { |
| GetRenderText()->ApplyStyle(style, value, range); |
| SchedulePaint(); |
| } |
| |
| void Textfield::ClearEditHistory() { |
| model_->ClearEditHistory(); |
| } |
| |
| void Textfield::SetAccessibleName(const base::string16& name) { |
| accessible_name_ = name; |
| } |
| |
| void Textfield::ExecuteCommand(int command_id) { |
| ExecuteCommand(command_id, ui::EF_NONE); |
| } |
| |
| void Textfield::SetFocusPainter(scoped_ptr<Painter> focus_painter) { |
| focus_painter_ = focus_painter.Pass(); |
| } |
| |
| bool Textfield::HasTextBeingDragged() { |
| return initiating_drag_; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Textfield, View overrides: |
| |
| gfx::Insets Textfield::GetInsets() const { |
| gfx::Insets insets = View::GetInsets(); |
| insets += gfx::Insets(kTextPadding, kTextPadding, kTextPadding, kTextPadding); |
| return insets; |
| } |
| |
| int Textfield::GetBaseline() const { |
| return GetInsets().top() + GetRenderText()->GetBaseline(); |
| } |
| |
| gfx::Size Textfield::GetPreferredSize() const { |
| const gfx::Insets& insets = GetInsets(); |
| return gfx::Size(GetFontList().GetExpectedTextWidth(default_width_in_chars_) + |
| insets.width(), GetFontList().GetHeight() + insets.height()); |
| } |
| |
| const char* Textfield::GetClassName() const { |
| return kViewClassName; |
| } |
| |
| 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); |
| return text_cursor ? GetNativeIBeamCursor() : gfx::kNullCursor; |
| } |
| |
| bool Textfield::OnMousePressed(const ui::MouseEvent& event) { |
| TrackMouseClicks(event); |
| |
| if (!controller_ || !controller_->HandleMouseEvent(this, event)) { |
| if (event.IsOnlyLeftMouseButton() || event.IsOnlyRightMouseButton()) { |
| RequestFocus(); |
| ShowImeIfNeeded(); |
| } |
| |
| if (event.IsOnlyLeftMouseButton()) { |
| OnBeforeUserAction(); |
| initiating_drag_ = false; |
| switch (aggregated_clicks_) { |
| case 0: |
| if (GetRenderText()->IsPointInSelection(event.location())) |
| initiating_drag_ = true; |
| else |
| MoveCursorTo(event.location(), event.IsShiftDown()); |
| break; |
| case 1: |
| SelectWordAt(event.location()); |
| double_click_word_ = GetRenderText()->selection(); |
| break; |
| case 2: |
| SelectAll(false); |
| break; |
| default: |
| NOTREACHED(); |
| } |
| OnAfterUserAction(); |
| } |
| |
| #if defined(OS_LINUX) && !defined(OS_CHROMEOS) |
| if (event.IsOnlyMiddleMouseButton()) { |
| if (GetRenderText()->IsPointInSelection(event.location())) { |
| OnBeforeUserAction(); |
| ClearSelection(); |
| ui::ScopedClipboardWriter( |
| ui::CLIPBOARD_TYPE_SELECTION).WriteText(base::string16()); |
| OnAfterUserAction(); |
| } else if (!read_only()) { |
| PasteSelectionClipboard(event); |
| } |
| } |
| #endif |
| } |
| |
| return true; |
| } |
| |
| bool Textfield::OnMouseDragged(const ui::MouseEvent& event) { |
| last_drag_location_ = event.location(); |
| |
| // 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(last_drag_location_ - last_click_location_)) { |
| return true; |
| } |
| |
| // A timer is used to continuously scroll while selecting beyond side edges. |
| const int x = event.location().x(); |
| if ((x >= 0 && x <= width()) || GetDragSelectionDelay() == 0) { |
| drag_selection_timer_.Stop(); |
| SelectThroughLastDragLocation(); |
| } else if (!drag_selection_timer_.IsRunning()) { |
| // Select through the edge of the visible text, then start the scroll timer. |
| last_drag_location_.set_x(std::min(std::max(0, x), width())); |
| SelectThroughLastDragLocation(); |
| drag_selection_timer_.Start( |
| FROM_HERE, base::TimeDelta::FromMilliseconds(GetDragSelectionDelay()), |
| this, &Textfield::SelectThroughLastDragLocation); |
| } |
| |
| return true; |
| } |
| |
| void Textfield::OnMouseReleased(const ui::MouseEvent& event) { |
| OnBeforeUserAction(); |
| drag_selection_timer_.Stop(); |
| // Cancel suspected drag initiations, the user was clicking in the selection. |
| if (initiating_drag_) |
| MoveCursorTo(event.location(), false); |
| initiating_drag_ = false; |
| UpdateSelectionClipboard(); |
| OnAfterUserAction(); |
| } |
| |
| bool Textfield::OnKeyPressed(const ui::KeyEvent& event) { |
| int edit_command = scheduled_edit_command_; |
| scheduled_edit_command_ = kNoCommand; |
| |
| // Since HandleKeyEvent() might destroy |this|, get a weak pointer and verify |
| // it isn't null before proceeding. |
| base::WeakPtr<Textfield> textfield(weak_ptr_factory_.GetWeakPtr()); |
| |
| bool handled = controller_ && controller_->HandleKeyEvent(this, event); |
| |
| if (!textfield) |
| return handled; |
| |
| #if defined(OS_LINUX) && !defined(OS_CHROMEOS) |
| ui::TextEditKeyBindingsDelegateAuraLinux* delegate = |
| ui::GetTextEditKeyBindingsDelegate(); |
| std::vector<ui::TextEditCommandAuraLinux> commands; |
| if (!handled && delegate && delegate->MatchEvent(event, &commands)) { |
| const bool rtl = GetTextDirection() == base::i18n::RIGHT_TO_LEFT; |
| for (size_t i = 0; i < commands.size(); ++i) { |
| const int command = GetViewsCommand(commands[i], rtl); |
| if (IsCommandIdEnabled(command)) { |
| ExecuteCommand(command); |
| handled = true; |
| } |
| } |
| return handled; |
| } |
| #endif |
| |
| if (edit_command == kNoCommand) |
| edit_command = GetCommandForKeyEvent(event, HasSelection()); |
| |
| if (!handled && IsCommandIdEnabled(edit_command)) { |
| ExecuteCommand(edit_command); |
| handled = true; |
| } |
| return handled; |
| } |
| |
| ui::TextInputClient* Textfield::GetTextInputClient() { |
| return this; |
| } |
| |
| void Textfield::OnGestureEvent(ui::GestureEvent* event) { |
| switch (event->type()) { |
| case ui::ET_GESTURE_TAP_DOWN: |
| RequestFocus(); |
| ShowImeIfNeeded(); |
| event->SetHandled(); |
| break; |
| case ui::ET_GESTURE_TAP: |
| if (event->details().tap_count() == 1) { |
| // If tap is on the selection and touch handles are not present, handles |
| // should be shown without changing selection. Otherwise, cursor should |
| // be moved to the tap location. |
| if (touch_selection_controller_ || |
| !GetRenderText()->IsPointInSelection(event->location())) { |
| OnBeforeUserAction(); |
| MoveCursorTo(event->location(), false); |
| OnAfterUserAction(); |
| } |
| } else if (event->details().tap_count() == 2) { |
| OnBeforeUserAction(); |
| SelectWordAt(event->location()); |
| OnAfterUserAction(); |
| } else { |
| OnBeforeUserAction(); |
| SelectAll(false); |
| OnAfterUserAction(); |
| } |
| CreateTouchSelectionControllerAndNotifyIt(); |
| #if defined(OS_WIN) |
| if (!read_only()) |
| base::win::DisplayVirtualKeyboard(); |
| #endif |
| event->SetHandled(); |
| break; |
| case ui::ET_GESTURE_LONG_PRESS: |
| if (!GetRenderText()->IsPointInSelection(event->location())) { |
| // If long-press happens outside selection, select word and try to |
| // activate touch selection. |
| OnBeforeUserAction(); |
| SelectWordAt(event->location()); |
| OnAfterUserAction(); |
| CreateTouchSelectionControllerAndNotifyIt(); |
| // If touch selection activated successfully, mark event as handled so |
| // that the regular context menu is not shown. |
| if (touch_selection_controller_) |
| event->SetHandled(); |
| } else { |
| // If long-press happens on the selection, deactivate touch selection |
| // and try to initiate drag-drop. If drag-drop is not enabled, context |
| // menu will be shown. Event is not marked as handled to let Views |
| // handle drag-drop or context menu. |
| DestroyTouchSelection(); |
| initiating_drag_ = switches::IsTouchDragDropEnabled(); |
| } |
| break; |
| case ui::ET_GESTURE_LONG_TAP: |
| // 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; |
| case ui::ET_GESTURE_SCROLL_BEGIN: |
| touch_handles_hidden_due_to_scroll_ = touch_selection_controller_ != NULL; |
| DestroyTouchSelection(); |
| drag_start_location_ = event->location(); |
| drag_start_display_offset_ = |
| GetRenderText()->GetUpdatedDisplayOffset().x(); |
| event->SetHandled(); |
| break; |
| case ui::ET_GESTURE_SCROLL_UPDATE: { |
| int new_offset = drag_start_display_offset_ + event->location().x() - |
| drag_start_location_.x(); |
| GetRenderText()->SetDisplayOffset(new_offset); |
| SchedulePaint(); |
| event->SetHandled(); |
| break; |
| } |
| case ui::ET_GESTURE_SCROLL_END: |
| case ui::ET_SCROLL_FLING_START: |
| if (touch_handles_hidden_due_to_scroll_) { |
| CreateTouchSelectionControllerAndNotifyIt(); |
| touch_handles_hidden_due_to_scroll_ = false; |
| } |
| event->SetHandled(); |
| break; |
| default: |
| return; |
| } |
| } |
| |
| // This function is called by BrowserView to execute clipboard commands. |
| bool Textfield::AcceleratorPressed(const ui::Accelerator& accelerator) { |
| ui::KeyEvent event(accelerator.type(), accelerator.key_code(), |
| accelerator.modifiers()); |
| ExecuteCommand(GetCommandForKeyEvent(event, HasSelection())); |
| return true; |
| } |
| |
| void Textfield::AboutToRequestFocusFromTabTraversal(bool reverse) { |
| SelectAll(false); |
| } |
| |
| bool Textfield::SkipDefaultKeyEventProcessing(const ui::KeyEvent& event) { |
| #if defined(OS_LINUX) && !defined(OS_CHROMEOS) |
| // Skip any accelerator handling that conflicts with custom keybindings. |
| ui::TextEditKeyBindingsDelegateAuraLinux* delegate = |
| ui::GetTextEditKeyBindingsDelegate(); |
| std::vector<ui::TextEditCommandAuraLinux> commands; |
| if (delegate && delegate->MatchEvent(event, &commands)) { |
| const bool rtl = GetTextDirection() == base::i18n::RIGHT_TO_LEFT; |
| for (size_t i = 0; i < commands.size(); ++i) |
| if (IsCommandIdEnabled(GetViewsCommand(commands[i], rtl))) |
| return true; |
| } |
| #endif |
| |
| // Skip backspace accelerator handling; editable textfields handle this key. |
| // Also skip processing Windows [Alt]+<num-pad digit> Unicode alt-codes. |
| const bool is_backspace = event.key_code() == ui::VKEY_BACK; |
| return (is_backspace && !read_only()) || event.IsUnicodeKeyCode(); |
| } |
| |
| 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())); |
| 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() { |
| drop_cursor_visible_ = false; |
| SchedulePaint(); |
| } |
| |
| int Textfield::OnPerformDrop(const ui::DropTargetEvent& event) { |
| DCHECK(CanDrop(event.data())); |
| 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); |
| |
| // 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; |
| drop_cursor_visible_ = false; |
| } |
| |
| void Textfield::GetAccessibleState(ui::AXViewState* state) { |
| state->role = ui::AX_ROLE_TEXT_FIELD; |
| state->name = accessible_name_; |
| if (read_only()) |
| state->AddStateFlag(ui::AX_STATE_READ_ONLY); |
| if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD) { |
| state->AddStateFlag(ui::AX_STATE_PROTECTED); |
| state->value = base::string16(text().size(), '*'); |
| } else { |
| state->value = text(); |
| } |
| const gfx::Range range = GetSelectedRange(); |
| state->selection_start = range.start(); |
| state->selection_end = range.end(); |
| |
| if (!read_only()) { |
| state->set_value_callback = |
| base::Bind(&Textfield::AccessibilitySetValue, |
| weak_ptr_factory_.GetWeakPtr()); |
| } |
| } |
| |
| void Textfield::OnBoundsChanged(const gfx::Rect& previous_bounds) { |
| // Textfield insets include a reasonable amount of whitespace on all sides of |
| // the default font list. Fallback fonts with larger heights may paint over |
| // the vertical whitespace as needed. Alternate solutions involve undesirable |
| // behavior like changing the default font size, shrinking some fallback fonts |
| // beyond their legibility, or enlarging controls dynamically with content. |
| gfx::Rect bounds = GetContentsBounds(); |
| // GetContentsBounds() does not actually use the local GetInsets() override. |
| bounds.Inset(gfx::Insets(0, kTextPadding, 0, kTextPadding)); |
| GetRenderText()->SetDisplayRect(bounds); |
| OnCaretBoundsChanged(); |
| } |
| |
| bool Textfield::GetNeedsNotificationWhenVisibleBoundsChange() const { |
| return true; |
| } |
| |
| void Textfield::OnVisibleBoundsChanged() { |
| if (touch_selection_controller_) |
| touch_selection_controller_->SelectionChanged(); |
| } |
| |
| void Textfield::OnEnabledChanged() { |
| View::OnEnabledChanged(); |
| if (GetInputMethod()) |
| GetInputMethod()->OnTextInputTypeChanged(this); |
| SchedulePaint(); |
| } |
| |
| void Textfield::OnPaint(gfx::Canvas* canvas) { |
| OnPaintBackground(canvas); |
| PaintTextAndCursor(canvas); |
| OnPaintBorder(canvas); |
| } |
| |
| void Textfield::OnFocus() { |
| GetRenderText()->set_focused(true); |
| cursor_visible_ = true; |
| SchedulePaint(); |
| GetInputMethod()->OnFocus(); |
| OnCaretBoundsChanged(); |
| |
| const size_t caret_blink_ms = Textfield::GetCaretBlinkMs(); |
| if (caret_blink_ms != 0) { |
| cursor_repaint_timer_.Start(FROM_HERE, |
| base::TimeDelta::FromMilliseconds(caret_blink_ms), this, |
| &Textfield::UpdateCursor); |
| } |
| |
| View::OnFocus(); |
| SchedulePaint(); |
| } |
| |
| void Textfield::OnBlur() { |
| GetRenderText()->set_focused(false); |
| GetInputMethod()->OnBlur(); |
| cursor_repaint_timer_.Stop(); |
| if (cursor_visible_) { |
| cursor_visible_ = false; |
| RepaintCursor(); |
| } |
| |
| DestroyTouchSelection(); |
| |
| // Border typically draws focus indicator. |
| SchedulePaint(); |
| } |
| |
| gfx::Point Textfield::GetKeyboardContextMenuLocation() { |
| return GetCaretBounds().bottom_right(); |
| } |
| |
| void Textfield::OnNativeThemeChanged(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(GetSelectionTextColor()); |
| render_text->set_selection_background_focused_color( |
| GetSelectionBackgroundColor()); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Textfield, TextfieldModel::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(); |
| ignore_result(context_menu_runner_->RunMenuAt(GetWidget(), |
| NULL, |
| gfx::Rect(point, gfx::Size()), |
| MENU_ANCHOR_TOPLEFT, |
| source_type)); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Textfield, DragController overrides: |
| |
| void Textfield::WriteDragDataForView(View* sender, |
| const gfx::Point& press_pt, |
| OSExchangeData* data) { |
| const base::string16& selected_text(GetSelectedText()); |
| data->SetString(selected_text); |
| Label label(selected_text, GetFontList()); |
| label.SetBackgroundColor(GetBackgroundColor()); |
| label.SetSubpixelRenderingEnabled(false); |
| gfx::Size size(label.GetPreferredSize()); |
| gfx::NativeView native_view = GetWidget()->GetNativeView(); |
| gfx::Display display = gfx::Screen::GetScreenFor(native_view)-> |
| GetDisplayNearestWindow(native_view); |
| size.SetToMin(gfx::Size(display.size().width(), height())); |
| label.SetBoundsRect(gfx::Rect(size)); |
| scoped_ptr<gfx::Canvas> canvas( |
| GetCanvasForDragImage(GetWidget(), label.size())); |
| label.SetEnabledColor(GetTextColor()); |
| #if defined(OS_LINUX) && !defined(OS_CHROMEOS) |
| // Desktop Linux Aura does not yet support transparency in drag images. |
| canvas->DrawColor(GetBackgroundColor()); |
| #endif |
| label.Paint(ui::PaintContext(canvas.get())); |
| const gfx::Vector2d kOffset(-15, 0); |
| drag_utils::SetDragImageOnDataObject(*canvas, kOffset, data); |
| if (controller_) |
| controller_->OnWriteDragData(data); |
| } |
| |
| int Textfield::GetDragOperationsForView(View* sender, const gfx::Point& p) { |
| int drag_operations = ui::DragDropTypes::DRAG_COPY; |
| if (!enabled() || text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD || |
| !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(); |
| SelectSelectionModel(selection); |
| OnAfterUserAction(); |
| } |
| |
| void Textfield::MoveCaretTo(const gfx::Point& point) { |
| SelectRect(point, point); |
| } |
| |
| void Textfield::GetSelectionEndPoints(ui::SelectionBound* anchor, |
| ui::SelectionBound* focus) { |
| gfx::RenderText* render_text = GetRenderText(); |
| const gfx::SelectionModel& sel = render_text->selection_model(); |
| gfx::SelectionModel start_sel = |
| render_text->GetSelectionModelForSelectionStart(); |
| gfx::Rect r1 = render_text->GetCursorBounds(start_sel, true); |
| gfx::Rect r2 = render_text->GetCursorBounds(sel, true); |
| |
| anchor->SetEdge(r1.origin(), r1.bottom_left()); |
| focus->SetEdge(r2.origin(), r2.bottom_left()); |
| |
| // Determine the SelectionBound's type for focus and anchor. |
| // TODO(mfomitchev): Ideally we should have different logical directions for |
| // start and end to support proper handle direction for mixed LTR/RTL text. |
| const bool ltr = GetTextDirection() != base::i18n::RIGHT_TO_LEFT; |
| size_t anchor_position_index = sel.selection().start(); |
| size_t focus_position_index = sel.selection().end(); |
| |
| if (anchor_position_index == focus_position_index) { |
| anchor->set_type(ui::SelectionBound::CENTER); |
| focus->set_type(ui::SelectionBound::CENTER); |
| } else if ((ltr && anchor_position_index < focus_position_index) || |
| (!ltr && anchor_position_index > focus_position_index)) { |
| anchor->set_type(ui::SelectionBound::LEFT); |
| focus->set_type(ui::SelectionBound::RIGHT); |
| } else { |
| anchor->set_type(ui::SelectionBound::RIGHT); |
| focus->set_type(ui::SelectionBound::LEFT); |
| } |
| } |
| |
| gfx::Rect Textfield::GetBounds() { |
| return GetLocalBounds(); |
| } |
| |
| 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) { |
| DestroyTouchSelection(); |
| ShowContextMenu(anchor, ui::MENU_SOURCE_TOUCH_EDIT_MENU); |
| } |
| |
| void Textfield::DestroyTouchSelection() { |
| touch_selection_controller_.reset(); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Textfield, ui::SimpleMenuModel::Delegate overrides: |
| |
| bool Textfield::IsCommandIdChecked(int command_id) const { |
| return true; |
| } |
| |
| bool Textfield::IsCommandIdEnabled(int command_id) const { |
| base::string16 result; |
| bool editable = !read_only(); |
| bool readable = text_input_type_ != ui::TEXT_INPUT_TYPE_PASSWORD; |
| switch (command_id) { |
| case IDS_APP_UNDO: |
| return editable && model_->CanUndo(); |
| case IDS_APP_REDO: |
| return editable && model_->CanRedo(); |
| case IDS_APP_CUT: |
| return editable && readable && model_->HasSelection(); |
| case IDS_APP_COPY: |
| return readable && model_->HasSelection(); |
| 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(); |
| case IDS_DELETE_FORWARD: |
| case IDS_DELETE_BACKWARD: |
| case IDS_DELETE_TO_BEGINNING_OF_LINE: |
| case IDS_DELETE_TO_END_OF_LINE: |
| case IDS_DELETE_WORD_BACKWARD: |
| case IDS_DELETE_WORD_FORWARD: |
| return editable; |
| case IDS_MOVE_LEFT: |
| case IDS_MOVE_LEFT_AND_MODIFY_SELECTION: |
| case IDS_MOVE_RIGHT: |
| case IDS_MOVE_RIGHT_AND_MODIFY_SELECTION: |
| case IDS_MOVE_WORD_LEFT: |
| case IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION: |
| case IDS_MOVE_WORD_RIGHT: |
| case IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION: |
| case IDS_MOVE_TO_BEGINNING_OF_LINE: |
| case IDS_MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION: |
| case IDS_MOVE_TO_END_OF_LINE: |
| case IDS_MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool Textfield::GetAcceleratorForCommandId(int command_id, |
| ui::Accelerator* accelerator) { |
| switch (command_id) { |
| case IDS_APP_UNDO: |
| *accelerator = ui::Accelerator(ui::VKEY_Z, ui::EF_CONTROL_DOWN); |
| return true; |
| |
| case IDS_APP_CUT: |
| *accelerator = ui::Accelerator(ui::VKEY_X, ui::EF_CONTROL_DOWN); |
| return true; |
| |
| case IDS_APP_COPY: |
| *accelerator = ui::Accelerator(ui::VKEY_C, ui::EF_CONTROL_DOWN); |
| return true; |
| |
| case IDS_APP_PASTE: |
| *accelerator = ui::Accelerator(ui::VKEY_V, ui::EF_CONTROL_DOWN); |
| return true; |
| |
| case IDS_APP_SELECT_ALL: |
| *accelerator = ui::Accelerator(ui::VKEY_A, ui::EF_CONTROL_DOWN); |
| return true; |
| |
| default: |
| return false; |
| } |
| } |
| |
| void Textfield::ExecuteCommand(int command_id, int event_flags) { |
| DestroyTouchSelection(); |
| if (!IsCommandIdEnabled(command_id)) |
| return; |
| |
| bool text_changed = false; |
| bool cursor_changed = false; |
| bool rtl = GetTextDirection() == base::i18n::RIGHT_TO_LEFT; |
| gfx::VisualCursorDirection begin = rtl ? gfx::CURSOR_RIGHT : gfx::CURSOR_LEFT; |
| gfx::VisualCursorDirection end = rtl ? gfx::CURSOR_LEFT : gfx::CURSOR_RIGHT; |
| gfx::SelectionModel selection_model = GetSelectionModel(); |
| |
| OnBeforeUserAction(); |
| switch (command_id) { |
| case IDS_APP_UNDO: |
| text_changed = cursor_changed = model_->Undo(); |
| break; |
| case IDS_APP_REDO: |
| text_changed = cursor_changed = model_->Redo(); |
| break; |
| case IDS_APP_CUT: |
| text_changed = cursor_changed = Cut(); |
| break; |
| case IDS_APP_COPY: |
| Copy(); |
| break; |
| case IDS_APP_PASTE: |
| text_changed = cursor_changed = Paste(); |
| break; |
| case IDS_APP_DELETE: |
| text_changed = cursor_changed = model_->Delete(); |
| break; |
| case IDS_APP_SELECT_ALL: |
| SelectAll(false); |
| break; |
| case IDS_DELETE_BACKWARD: |
| text_changed = cursor_changed = model_->Backspace(); |
| break; |
| case IDS_DELETE_FORWARD: |
| text_changed = cursor_changed = model_->Delete(); |
| break; |
| case IDS_DELETE_TO_END_OF_LINE: |
| model_->MoveCursor(gfx::LINE_BREAK, end, true); |
| text_changed = cursor_changed = model_->Delete(); |
| break; |
| case IDS_DELETE_TO_BEGINNING_OF_LINE: |
| model_->MoveCursor(gfx::LINE_BREAK, begin, true); |
| text_changed = cursor_changed = model_->Backspace(); |
| break; |
| case IDS_DELETE_WORD_BACKWARD: |
| model_->MoveCursor(gfx::WORD_BREAK, begin, true); |
| text_changed = cursor_changed = model_->Backspace(); |
| break; |
| case IDS_DELETE_WORD_FORWARD: |
| model_->MoveCursor(gfx::WORD_BREAK, end, true); |
| text_changed = cursor_changed = model_->Delete(); |
| break; |
| case IDS_MOVE_LEFT: |
| model_->MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_LEFT, false); |
| break; |
| case IDS_MOVE_LEFT_AND_MODIFY_SELECTION: |
| model_->MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_LEFT, true); |
| break; |
| case IDS_MOVE_RIGHT: |
| model_->MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT, false); |
| break; |
| case IDS_MOVE_RIGHT_AND_MODIFY_SELECTION: |
| model_->MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT, true); |
| break; |
| case IDS_MOVE_WORD_LEFT: |
| model_->MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_LEFT, false); |
| break; |
| case IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION: |
| model_->MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_LEFT, true); |
| break; |
| case IDS_MOVE_WORD_RIGHT: |
| model_->MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT, false); |
| break; |
| case IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION: |
| model_->MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT, true); |
| break; |
| case IDS_MOVE_TO_BEGINNING_OF_LINE: |
| model_->MoveCursor(gfx::LINE_BREAK, begin, false); |
| break; |
| case IDS_MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION: |
| model_->MoveCursor(gfx::LINE_BREAK, begin, true); |
| break; |
| case IDS_MOVE_TO_END_OF_LINE: |
| model_->MoveCursor(gfx::LINE_BREAK, end, false); |
| break; |
| case IDS_MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION: |
| model_->MoveCursor(gfx::LINE_BREAK, end, true); |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| |
| cursor_changed |= GetSelectionModel() != selection_model; |
| if (cursor_changed) |
| UpdateSelectionClipboard(); |
| UpdateAfterChange(text_changed, cursor_changed); |
| OnAfterUserAction(); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // 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(new_text); |
| else |
| model_->ReplaceText(new_text); |
| skip_input_method_cancel_composition_ = false; |
| UpdateAfterChange(true, true); |
| OnAfterUserAction(); |
| } |
| |
| void Textfield::InsertChar(base::char16 ch, int flags) { |
| const int kControlModifierMask = ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN | |
| ui::EF_COMMAND_DOWN | ui::EF_ALTGR_DOWN | |
| ui::EF_MOD3_DOWN; |
| |
| // 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 & kControlModifierMask) != ui::EF_ALT_DOWN; |
| if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE || !should_insert_char) |
| return; |
| |
| DoInsertChar(ch); |
| |
| if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD && |
| password_reveal_duration_ != base::TimeDelta()) { |
| const size_t change_offset = model_->GetCursorPosition(); |
| DCHECK_GT(change_offset, 0u); |
| RevealPasswordChar(change_offset - 1); |
| } |
| } |
| |
| 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()->GetNativeWindow(); |
| } |
| |
| 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; |
| } |
| |
| int Textfield::GetTextInputFlags() const { |
| return text_input_flags_; |
| } |
| |
| 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::Range composition_range; |
| model_->GetCompositionTextRange(&composition_range); |
| DCHECK(!composition_range.is_empty()); |
| |
| size_t text_index = composition_range.start() + index; |
| if (composition_range.end() <= text_index) |
| return false; |
| gfx::RenderText* render_text = GetRenderText(); |
| if (!render_text->IsValidCursorIndex(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) {} |
| |
| bool Textfield::IsEditCommandEnabled(int command_id) { |
| return IsCommandIdEnabled(command_id); |
| } |
| |
| void Textfield::SetEditCommandForNextKeyEvent(int command_id) { |
| DCHECK_EQ(kNoCommand, scheduled_edit_command_); |
| scheduled_edit_command_ = command_id; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Textfield, protected: |
| |
| void Textfield::DoInsertChar(base::char16 ch) { |
| OnBeforeUserAction(); |
| skip_input_method_cancel_composition_ = true; |
| if (GetRenderText()->insert_mode()) |
| model_->InsertChar(ch); |
| else |
| model_->ReplaceChar(ch); |
| skip_input_method_cancel_composition_ = false; |
| |
| UpdateAfterChange(true, true); |
| OnAfterUserAction(); |
| } |
| |
| gfx::RenderText* Textfield::GetRenderText() const { |
| return model_->render_text(); |
| } |
| |
| base::string16 Textfield::GetSelectionClipboardText() const { |
| base::string16 selection_clipboard_text; |
| ui::Clipboard::GetForCurrentThread()->ReadText( |
| ui::CLIPBOARD_TYPE_SELECTION, &selection_clipboard_text); |
| return selection_clipboard_text; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Textfield, private: |
| |
| void Textfield::AccessibilitySetValue(const base::string16& new_value) { |
| if (!read_only()) { |
| SetText(new_value); |
| ClearSelection(); |
| } |
| } |
| |
| void Textfield::UpdateBackgroundColor() { |
| const SkColor color = GetBackgroundColor(); |
| set_background(Background::CreateSolidBackground(color)); |
| // Disable subpixel rendering when the background color is transparent |
| // because it draws incorrect colors around the glyphs in that case. |
| // See crbug.com/115198 |
| GetRenderText()->set_subpixel_rendering_suppressed( |
| SkColorGetA(color) != 0xFF); |
| SchedulePaint(); |
| } |
| |
| void Textfield::UpdateAfterChange(bool text_changed, bool cursor_changed) { |
| if (text_changed) { |
| if (controller_) |
| controller_->ContentsChanged(this, text()); |
| NotifyAccessibilityEvent(ui::AX_EVENT_TEXT_CHANGED, true); |
| } |
| if (cursor_changed) { |
| cursor_visible_ = true; |
| RepaintCursor(); |
| if (cursor_repaint_timer_.IsRunning()) |
| cursor_repaint_timer_.Reset(); |
| if (!text_changed) { |
| // TEXT_CHANGED implies TEXT_SELECTION_CHANGED, so we only need to fire |
| // this if only the selection changed. |
| NotifyAccessibilityEvent(ui::AX_EVENT_TEXT_SELECTION_CHANGED, true); |
| } |
| } |
| if (text_changed || cursor_changed) { |
| OnCaretBoundsChanged(); |
| SchedulePaint(); |
| } |
| } |
| |
| void Textfield::UpdateCursor() { |
| const size_t caret_blink_ms = Textfield::GetCaretBlinkMs(); |
| cursor_visible_ = !cursor_visible_ || (caret_blink_ms == 0); |
| RepaintCursor(); |
| } |
| |
| 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(); |
| |
| // Draw placeholder text if needed. |
| gfx::RenderText* render_text = GetRenderText(); |
| if (text().empty() && !GetPlaceholderText().empty()) { |
| canvas->DrawStringRect(GetPlaceholderText(), GetFontList(), |
| placeholder_text_color(), render_text->display_rect()); |
| } |
| |
| // Draw the text, cursor, and selection. |
| render_text->set_cursor_visible(cursor_visible_ && !drop_cursor_visible_ && |
| !HasSelection()); |
| render_text->Draw(canvas); |
| |
| // Draw the detached drop cursor that marks where the text will be dropped. |
| if (drop_cursor_visible_) |
| render_text->DrawCursor(canvas, drop_cursor_position_); |
| |
| canvas->Restore(); |
| } |
| |
| void Textfield::MoveCursorTo(const gfx::Point& point, bool select) { |
| if (model_->MoveCursorTo(point, select)) |
| UpdateAfterChange(false, true); |
| } |
| |
| void Textfield::SelectThroughLastDragLocation() { |
| OnBeforeUserAction(); |
| model_->MoveCursorTo(last_drag_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); |
| } |
| UpdateAfterChange(false, true); |
| OnAfterUserAction(); |
| } |
| |
| void Textfield::OnCaretBoundsChanged() { |
| if (GetInputMethod()) |
| GetInputMethod()->OnCaretBoundsChanged(this); |
| if (touch_selection_controller_) |
| touch_selection_controller_->SelectionChanged(); |
| } |
| |
| void Textfield::OnBeforeUserAction() { |
| DCHECK(!performing_user_action_); |
| performing_user_action_ = true; |
| if (controller_) |
| controller_->OnBeforeUserAction(this); |
| } |
| |
| void Textfield::OnAfterUserAction() { |
| if (controller_) |
| controller_->OnAfterUserAction(this); |
| DCHECK(performing_user_action_); |
| performing_user_action_ = false; |
| } |
| |
| bool Textfield::Cut() { |
| if (!read_only() && text_input_type_ != ui::TEXT_INPUT_TYPE_PASSWORD && |
| model_->Cut()) { |
| if (controller_) |
| controller_->OnAfterCutOrCopy(ui::CLIPBOARD_TYPE_COPY_PASTE); |
| return true; |
| } |
| return false; |
| } |
| |
| bool Textfield::Copy() { |
| if (text_input_type_ != ui::TEXT_INPUT_TYPE_PASSWORD && model_->Copy()) { |
| if (controller_) |
| controller_->OnAfterCutOrCopy(ui::CLIPBOARD_TYPE_COPY_PASTE); |
| return true; |
| } |
| return false; |
| } |
| |
| bool Textfield::Paste() { |
| if (!read_only() && model_->Paste()) { |
| 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_runner_.reset( |
| new MenuRunner(context_menu_contents_.get(), |
| MenuRunner::HAS_MNEMONICS | MenuRunner::CONTEXT_MENU)); |
| } |
| |
| 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::RevealPasswordChar(int index) { |
| GetRenderText()->SetObscuredRevealIndex(index); |
| SchedulePaint(); |
| |
| if (index != -1) { |
| password_reveal_timer_.Start(FROM_HERE, password_reveal_duration_, |
| base::Bind(&Textfield::RevealPasswordChar, |
| weak_ptr_factory_.GetWeakPtr(), -1)); |
| } |
| } |
| |
| void Textfield::CreateTouchSelectionControllerAndNotifyIt() { |
| if (!HasFocus()) |
| return; |
| |
| if (!touch_selection_controller_) { |
| touch_selection_controller_.reset( |
| ui::TouchEditingControllerDeprecated::Create(this)); |
| } |
| if (touch_selection_controller_) |
| touch_selection_controller_->SelectionChanged(); |
| } |
| |
| void Textfield::UpdateSelectionClipboard() const { |
| #if defined(OS_LINUX) && !defined(OS_CHROMEOS) |
| if (performing_user_action_ && HasSelection()) { |
| ui::ScopedClipboardWriter( |
| ui::CLIPBOARD_TYPE_SELECTION).WriteText(GetSelectedText()); |
| if (controller_) |
| controller_->OnAfterCutOrCopy(ui::CLIPBOARD_TYPE_SELECTION); |
| } |
| #endif |
| } |
| |
| void Textfield::PasteSelectionClipboard(const ui::MouseEvent& event) { |
| DCHECK(event.IsOnlyMiddleMouseButton()); |
| DCHECK(!read_only()); |
| base::string16 selection_clipboard_text = GetSelectionClipboardText(); |
| OnBeforeUserAction(); |
| const gfx::SelectionModel mouse = |
| GetRenderText()->FindCursorPosition(event.location()); |
| if (!HasFocus()) |
| RequestFocus(); |
| model_->MoveCursorTo(mouse); |
| if (!selection_clipboard_text.empty()) { |
| model_->InsertText(selection_clipboard_text); |
| UpdateAfterChange(true, true); |
| } |
| OnAfterUserAction(); |
| } |
| |
| } // namespace views |