// 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 #include "base/debug/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/ui_base_switches_util.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/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), 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), 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(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::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()->GetTextDirection(); } 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 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. if ((event.location().x() > 0 && event.location().x() < size().width()) || GetDragSelectionDelay() == 0) { drag_selection_timer_.Stop(); SelectThroughLastDragLocation(); } else if (!drag_selection_timer_.IsRunning()) { 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) { // Since HandleKeyEvent() might destroy |this|, get a weak pointer and verify // it isn't null before proceeding. base::WeakPtr 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 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 const int command = GetCommandForKeyEvent(event, HasSelection()); if (!handled && IsCommandIdEnabled(command)) { ExecuteCommand(command); handled = true; } return handled; } ui::TextInputClient* Textfield::GetTextInputClient() { return read_only_ ? NULL : 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 (!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; } } 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 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]+ 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* 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 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 = 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 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(canvas.get(), views::CullSet()); 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(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 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) { 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; 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(); 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; } 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->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) {} void Textfield::OnCandidateWindowShown() {} void Textfield::OnCandidateWindowUpdated() {} void Textfield::OnCandidateWindowHidden() {} bool Textfield::IsEditingCommandEnabled(int command_id) { return IsCommandIdEnabled(command_id); } void Textfield::ExecuteEditingCommand(int command_id) { ExecuteCommand(command_id); } //////////////////////////////////////////////////////////////////////////////// // Textfield, protected: 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)); GetRenderText()->set_background_is_transparent(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::TouchSelectionController::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(); if (!selection_clipboard_text.empty()) { OnBeforeUserAction(); gfx::Range range = GetSelectionModel().selection(); gfx::LogicalCursorDirection affinity = GetSelectionModel().caret_affinity(); const gfx::SelectionModel mouse = GetRenderText()->FindCursorPosition(event.location()); model_->MoveCursorTo(mouse); model_->InsertText(selection_clipboard_text); // Update the new selection range as needed. if (range.GetMin() >= mouse.caret_pos()) { const size_t length = selection_clipboard_text.length(); range = gfx::Range(range.start() + length, range.end() + length); } model_->MoveCursorTo(gfx::SelectionModel(range, affinity)); UpdateAfterChange(true, true); OnAfterUserAction(); } } } // namespace views