#include "ui/views/controls/textfield/textfield.h"
#include <algorithm>
#include <set>
#include <string>
#include <utility>
#include "base/auto_reset.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.h"
#include "base/ranges/algorithm.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/clipboard/clipboard.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "ui/base/cursor/cursor.h"
#include "ui/base/data_transfer_policy/data_transfer_endpoint.h"
#include "ui/base/default_style.h"
#include "ui/base/dragdrop/drag_drop_types.h"
#include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
#include "ui/base/ime/constants.h"
#include "ui/base/ime/input_method.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/ui_base_features.h"
#include "ui/base/ui_base_switches.h"
#include "ui/base/ui_base_switches_util.h"
#include "ui/color/color_id.h"
#include "ui/color/color_provider.h"
#include "ui/compositor/canvas_painter.h"
#include "ui/compositor/compositor.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_tree_owner.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/gesture_event_details.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/selection_bound.h"
#include "ui/strings/grit/ui_strings.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/background.h"
#include "ui/views/controls/focus_ring.h"
#include "ui/views/controls/focusable_border.h"
#include "ui/views/controls/highlight_path_generator.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/controls/textfield/textfield_controller.h"
#include "ui/views/controls/views_text_services_context_menu.h"
#include "ui/views/drag_utils.h"
#include "ui/views/layout/layout_provider.h"
#include "ui/views/painter.h"
#include "ui/views/style/platform_style.h"
#include "ui/views/views_delegate.h"
#include "ui/views/views_features.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/coordinate_conversion.h"
#if BUILDFLAG(IS_WIN)
#include "base/win/win_util.h"
#endif
#if BUILDFLAG(IS_LINUX)
#include "ui/base/ime/linux/text_edit_command_auralinux.h"
#include "ui/linux/linux_ui.h"
#endif
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ui/aura/window.h"
#include "ui/wm/core/ime_util_chromeos.h"
#endif
#if BUILDFLAG(IS_MAC)
#include "ui/base/cocoa/defaults_utils.h"
#include "ui/base/cocoa/secure_password_input.h"
#endif
#if BUILDFLAG(IS_OZONE)
#include "ui/ozone/public/ozone_platform.h"
#include "ui/ozone/public/platform_gl_egl_utility.h"
#endif
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ui/base/ime/ash/extension_ime_util.h"
#include "ui/base/ime/ash/input_method_manager.h"
#endif
namespace views {
namespace {
using ::ui::mojom::DragOperation;
enum TextfieldPropertyKey {
kTextfieldText = 1,
kTextfieldTextColor,
kTextfieldSelectionTextColor,
kTextfieldBackgroundColor,
kTextfieldSelectionBackgroundColor,
kTextfieldCursorEnabled,
kTextfieldHorizontalAlignment,
kTextfieldSelectedRange,
};
ui::TextEditCommand GetTextEditCommandFromMenuCommand(int command_id,
bool has_selection) {
switch (command_id) {
case Textfield::kUndo:
return ui::TextEditCommand::UNDO;
case Textfield::kCut:
return ui::TextEditCommand::CUT;
case Textfield::kCopy:
return ui::TextEditCommand::COPY;
case Textfield::kPaste:
return ui::TextEditCommand::PASTE;
case Textfield::kDelete:
if (has_selection)
return ui::TextEditCommand::DELETE_FORWARD;
break;
case Textfield::kSelectAll:
return ui::TextEditCommand::SELECT_ALL;
case Textfield::kSelectWord:
return ui::TextEditCommand::SELECT_WORD;
}
return ui::TextEditCommand::INVALID_COMMAND;
}
base::TimeDelta GetPasswordRevealDuration(const ui::KeyEvent& event) {
auto* properties = event.properties();
bool from_vk =
properties && properties->find(ui::kPropertyFromVK) != properties->end();
if (from_vk) {
std::vector<uint8_t> fromVKPropertyArray =
properties->find(ui::kPropertyFromVK)->second;
DCHECK_GT(fromVKPropertyArray.size(), ui::kPropertyFromVKIsMirroringIndex);
uint8_t is_mirroring =
fromVKPropertyArray[ui::kPropertyFromVKIsMirroringIndex];
if (!is_mirroring)
return base::Seconds(1);
}
return base::TimeDelta();
}
bool IsControlKeyModifier(int flags) {
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
return flags & ui::EF_CONTROL_DOWN;
#else
return false;
#endif
}
bool IsValidCharToInsert(const char16_t& ch) {
return (ch >= 0x20 && ch < 0x7F) || ch > 0x9F;
}
bool CanUseTransparentBackgroundForDragImage() {
#if BUILDFLAG(IS_OZONE)
const auto* const egl_utility =
ui::OzonePlatform::GetInstance()->GetPlatformGLEGLUtility();
return egl_utility ? egl_utility->IsTransparentBackgroundSupported() : false;
#else
return true;
#endif
}
#if BUILDFLAG(IS_MAC)
const float kAlmostTransparent = 1.0 / 255.0;
const float kOpaque = 1.0;
#endif
}
base::TimeDelta Textfield::GetCaretBlinkInterval() {
#if BUILDFLAG(IS_WIN)
static const size_t system_value = ::GetCaretBlinkTime();
if (system_value != 0) {
return (system_value == INFINITE) ? base::TimeDelta()
: base::Milliseconds(system_value);
}
#elif BUILDFLAG(IS_MAC)
absl::optional<base::TimeDelta> system_value(
ui::TextInsertionCaretBlinkPeriodFromDefaults());
if (system_value)
return *system_value;
#endif
return base::Milliseconds(500);
}
const gfx::FontList& Textfield::GetDefaultFontList() {
ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
return rb.GetFontListWithDelta(ui::kLabelFontSizeDelta);
}
Textfield::Textfield()
: model_(new TextfieldModel(this)),
placeholder_text_draw_flags_(gfx::Canvas::DefaultCanvasTextAlignment()),
selection_controller_(this) {
set_context_menu_controller(this);
set_drag_controller(this);
auto cursor_view = std::make_unique<View>();
cursor_view->SetPaintToLayer(ui::LAYER_SOLID_COLOR);
cursor_view->GetViewAccessibility().OverrideIsIgnored(true);
cursor_view_ = AddChildView(std::move(cursor_view));
GetRenderText()->SetFontList(GetDefaultFontList());
UpdateBorder();
SetFocusBehavior(FocusBehavior::ALWAYS);
views::InstallRoundRectHighlightPathGenerator(this, gfx::Insets(),
GetCornerRadius());
FocusRing::Install(this);
FocusRing::Get(this)->SetOutsetFocusRingDisabled(true);
#if !BUILDFLAG(IS_MAC)
AddAccelerator(ui::Accelerator(ui::VKEY_X, ui::EF_CONTROL_DOWN));
AddAccelerator(ui::Accelerator(ui::VKEY_C, ui::EF_CONTROL_DOWN));
AddAccelerator(ui::Accelerator(ui::VKEY_V, ui::EF_CONTROL_DOWN));
#endif
SetAccessibilityProperties(ax::mojom::Role::kTextField);
GetViewAccessibility().OverrideIsLeaf(true);
}
Textfield::~Textfield() {
if (GetInputMethod()) {
DCHECK(this != GetInputMethod()->GetTextInputClient());
}
}
void Textfield::SetController(TextfieldController* controller) {
controller_ = controller;
}
bool Textfield::GetReadOnly() const {
return read_only_;
}
void Textfield::SetReadOnly(bool read_only) {
if (read_only_ == read_only)
return;
read_only_ = read_only;
if (GetInputMethod())
GetInputMethod()->OnTextInputTypeChanged(this);
if (GetWidget()) {
SetColor(GetTextColor());
UpdateBackgroundColor();
}
OnPropertyChanged(&read_only_, kPropertyEffectsPaint);
}
void Textfield::SetTextInputType(ui::TextInputType type) {
if (text_input_type_ == type)
return;
GetRenderText()->SetObscured(type == ui::TEXT_INPUT_TYPE_PASSWORD);
text_input_type_ = type;
if (GetInputMethod())
GetInputMethod()->OnTextInputTypeChanged(this);
OnCaretBoundsChanged();
OnPropertyChanged(&text_input_type_, kPropertyEffectsPaint);
}
void Textfield::SetTextInputFlags(int flags) {
if (text_input_flags_ == flags)
return;
text_input_flags_ = flags;
OnPropertyChanged(&text_input_flags_, kPropertyEffectsNone);
}
const std::u16string& Textfield::GetText() const {
return model_->text();
}
void Textfield::SetText(const std::u16string& new_text) {
SetTextWithoutCaretBoundsChangeNotification(new_text, new_text.length());
UpdateAfterChange(TextChangeType::kNone, true);
}
void Textfield::SetTextWithoutCaretBoundsChangeNotification(
const std::u16string& text,
size_t cursor_position) {
model_->SetText(text, cursor_position);
UpdateAfterChange(TextChangeType::kInternal, false, false);
}
void Textfield::Scroll(const std::vector<size_t>& positions) {
for (const auto position : positions) {
model_->MoveCursorTo(position);
GetRenderText()->GetUpdatedCursorBounds();
}
}
void Textfield::AppendText(const std::u16string& new_text) {
if (new_text.empty())
return;
model_->Append(new_text);
UpdateAfterChange(TextChangeType::kInternal, false);
}
void Textfield::InsertOrReplaceText(const std::u16string& new_text) {
if (new_text.empty())
return;
model_->InsertText(new_text);
UpdateAfterChange(TextChangeType::kUserTriggered, true);
}
std::u16string Textfield::GetSelectedText() const {
return model_->GetSelectedText();
}
void Textfield::SelectAll(bool reversed) {
model_->SelectAll(reversed);
if (HasSelection() && performing_user_action_)
UpdateSelectionClipboard();
UpdateAfterChange(TextChangeType::kNone, true);
}
void Textfield::SelectWord() {
model_->SelectWord();
if (HasSelection() && performing_user_action_) {
UpdateSelectionClipboard();
}
UpdateAfterChange(TextChangeType::kNone, true);
}
void Textfield::SelectWordAt(const gfx::Point& point) {
model_->MoveCursorTo(point, false);
model_->SelectWord();
UpdateAfterChange(TextChangeType::kNone, true);
}
void Textfield::ClearSelection() {
model_->ClearSelection();
UpdateAfterChange(TextChangeType::kNone, true);
}
bool Textfield::HasSelection(bool primary_only) const {
return model_->HasSelection(primary_only);
}
SkColor Textfield::GetTextColor() const {
return text_color_.value_or(GetColorProvider()->GetColor(
style::GetColorId(style::CONTEXT_TEXTFIELD, GetTextStyle())));
}
void Textfield::SetTextColor(SkColor color) {
text_color_ = color;
if (GetWidget())
SetColor(color);
}
SkColor Textfield::GetBackgroundColor() const {
return background_color_.value_or(GetColorProvider()->GetColor(
GetReadOnly() || !GetEnabled() ? ui::kColorTextfieldBackgroundDisabled
: ui::kColorTextfieldBackground));
}
void Textfield::SetBackgroundColor(SkColor color) {
background_color_ = color;
if (GetWidget())
UpdateBackgroundColor();
}
SkColor Textfield::GetSelectionTextColor() const {
return selection_text_color_.value_or(
GetColorProvider()->GetColor(ui::kColorTextfieldSelectionForeground));
}
void Textfield::SetSelectionTextColor(SkColor color) {
selection_text_color_ = color;
UpdateSelectionTextColor();
}
SkColor Textfield::GetSelectionBackgroundColor() const {
return selection_background_color_.value_or(
GetColorProvider()->GetColor(ui::kColorTextfieldSelectionBackground));
}
void Textfield::SetSelectionBackgroundColor(SkColor color) {
selection_background_color_ = color;
UpdateSelectionBackgroundColor();
}
bool Textfield::GetCursorEnabled() const {
return GetRenderText()->cursor_enabled();
}
void Textfield::SetCursorEnabled(bool enabled) {
if (GetRenderText()->cursor_enabled() == enabled)
return;
GetRenderText()->SetCursorEnabled(enabled);
UpdateAfterChange(TextChangeType::kNone, true, false);
OnPropertyChanged(&model_ + kTextfieldCursorEnabled, kPropertyEffectsPaint);
}
const gfx::FontList& Textfield::GetFontList() const {
return GetRenderText()->font_list();
}
void Textfield::SetFontList(const gfx::FontList& font_list) {
GetRenderText()->SetFontList(font_list);
OnCaretBoundsChanged();
PreferredSizeChanged();
}
void Textfield::SetDefaultWidthInChars(int default_width) {
DCHECK_GE(default_width, 0);
default_width_in_chars_ = default_width;
}
void Textfield::SetMinimumWidthInChars(int minimum_width) {
DCHECK_GE(minimum_width, -1);
minimum_width_in_chars_ = minimum_width;
}
std::u16string Textfield::GetPlaceholderText() const {
return placeholder_text_;
}
void Textfield::SetPlaceholderText(const std::u16string& text) {
if (placeholder_text_ == text)
return;
placeholder_text_ = text;
OnPropertyChanged(&placeholder_text_, kPropertyEffectsPaint);
}
gfx::HorizontalAlignment Textfield::GetHorizontalAlignment() const {
return GetRenderText()->horizontal_alignment();
}
void Textfield::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) {
GetRenderText()->SetHorizontalAlignment(alignment);
OnPropertyChanged(&model_ + kTextfieldHorizontalAlignment,
kPropertyEffectsNone);
}
void Textfield::ShowVirtualKeyboardIfEnabled() {
if (GetEnabled() && !GetReadOnly() && GetInputMethod())
GetInputMethod()->SetVirtualKeyboardVisibilityIfEnabled(true);
}
bool Textfield::IsIMEComposing() const {
return model_->HasCompositionText();
}
const gfx::Range& Textfield::GetSelectedRange() const {
return GetRenderText()->selection();
}
void Textfield::SetSelectedRange(const gfx::Range& range) {
model_->SelectRange(range);
UpdateAfterChange(TextChangeType::kNone, true);
OnPropertyChanged(&model_ + kTextfieldSelectedRange, kPropertyEffectsPaint);
}
void Textfield::AddSecondarySelectedRange(const gfx::Range& range) {
model_->SelectRange(range, false);
OnPropertyChanged(&model_ + kTextfieldSelectedRange, kPropertyEffectsPaint);
}
const gfx::SelectionModel& Textfield::GetSelectionModel() const {
return GetRenderText()->selection_model();
}
void Textfield::SelectSelectionModel(const gfx::SelectionModel& sel) {
model_->SelectSelectionModel(sel);
UpdateAfterChange(TextChangeType::kNone, true);
}
size_t Textfield::GetCursorPosition() const {
return model_->GetCursorPosition();
}
void Textfield::SetColor(SkColor value) {
GetRenderText()->SetColor(value);
cursor_view_->layer()->SetColor(value);
OnPropertyChanged(&model_ + kTextfieldTextColor, kPropertyEffectsPaint);
}
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();
}
bool Textfield::GetInvalid() const {
return invalid_;
}
void Textfield::SetInvalid(bool invalid) {
if (invalid == invalid_)
return;
invalid_ = invalid;
UpdateBorder();
if (FocusRing::Get(this))
FocusRing::Get(this)->SetInvalid(invalid);
OnPropertyChanged(&invalid_, kPropertyEffectsNone);
}
void Textfield::ClearEditHistory() {
model_->ClearEditHistory();
}
void Textfield::SetObscuredGlyphSpacing(int spacing) {
GetRenderText()->SetObscuredGlyphSpacing(spacing);
}
void Textfield::SetExtraInsets(const gfx::Insets& insets) {
extra_insets_ = insets;
UpdateBorder();
}
void Textfield::FitToLocalBounds() {
gfx::Rect bounds = GetLocalBounds();
const gfx::Insets insets = GetInsets();
if (GetRenderText()->multiline()) {
bounds.Inset(insets);
} else {
bounds.Inset(gfx::Insets::TLBR(0, insets.left(), 0, insets.right()));
}
bounds.set_x(GetMirroredXForRect(bounds));
GetRenderText()->SetDisplayRect(bounds);
UpdateAfterChange(TextChangeType::kNone, true);
}
int Textfield::GetBaseline() const {
return GetInsets().top() + GetRenderText()->GetBaseline();
}
gfx::Size Textfield::CalculatePreferredSize() const {
DCHECK_GE(default_width_in_chars_, minimum_width_in_chars_);
return gfx::Size(
CharsToDips(default_width_in_chars_),
LayoutProvider::GetControlHeightForFont(style::CONTEXT_TEXTFIELD,
GetTextStyle(), GetFontList()));
}
gfx::Size Textfield::GetMinimumSize() const {
DCHECK_LE(minimum_width_in_chars_, default_width_in_chars_);
gfx::Size minimum_size = View::GetMinimumSize();
if (minimum_width_in_chars_ >= 0)
minimum_size.set_width(CharsToDips(minimum_width_in_chars_));
return minimum_size;
}
void Textfield::SetBorder(std::unique_ptr<Border> b) {
FocusRing::Remove(this);
View::SetBorder(std::move(b));
}
ui::Cursor Textfield::GetCursor(const ui::MouseEvent& event) {
bool platform_arrow = PlatformStyle::kTextfieldUsesDragCursorWhenDraggable;
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 || !platform_arrow);
return text_cursor ? ui::mojom::CursorType::kIBeam : ui::Cursor();
}
bool Textfield::OnMousePressed(const ui::MouseEvent& event) {
const bool had_focus = HasFocus();
bool handled = controller_ && controller_->HandleMouseEvent(this, event);
if (!had_focus && HasFocus())
focus_reason_ = ui::TextInputClient::FOCUS_REASON_OTHER;
if (!handled &&
(event.IsOnlyLeftMouseButton() || event.IsOnlyRightMouseButton())) {
if (!had_focus)
RequestFocusWithPointer(ui::EventPointerType::kMouse);
#if !BUILDFLAG(IS_WIN)
ShowVirtualKeyboardIfEnabled();
#endif
}
if (ui::Clipboard::IsSupportedClipboardBuffer(
ui::ClipboardBuffer::kSelection)) {
if (!handled && !had_focus && event.IsOnlyMiddleMouseButton())
RequestFocusWithPointer(ui::EventPointerType::kMouse);
}
return selection_controller_.OnMousePressed(
event, handled,
had_focus
? SelectionController::InitialFocusStateOnMousePress::kFocused
: SelectionController::InitialFocusStateOnMousePress::kUnFocused);
}
bool Textfield::OnMouseDragged(const ui::MouseEvent& event) {
return selection_controller_.OnMouseDragged(event);
}
void Textfield::OnMouseReleased(const ui::MouseEvent& event) {
if (controller_)
controller_->HandleMouseEvent(this, event);
selection_controller_.OnMouseReleased(event);
}
void Textfield::OnMouseCaptureLost() {
selection_controller_.OnMouseCaptureLost();
}
bool Textfield::OnMouseWheel(const ui::MouseWheelEvent& event) {
return controller_ && controller_->HandleMouseEvent(this, event);
}
WordLookupClient* Textfield::GetWordLookupClient() {
return this;
}
bool Textfield::OnKeyPressed(const ui::KeyEvent& event) {
if (PreHandleKeyPressed(event))
return true;
ui::TextEditCommand edit_command = scheduled_text_edit_command_;
scheduled_text_edit_command_ = ui::TextEditCommand::INVALID_COMMAND;
base::WeakPtr<Textfield> textfield(weak_ptr_factory_.GetWeakPtr());
bool handled = controller_ && controller_->HandleKeyEvent(this, event);
if (!textfield)
return handled;
#if BUILDFLAG(IS_LINUX)
auto* linux_ui = ui::LinuxUi::instance();
std::vector<ui::TextEditCommandAuraLinux> commands;
if (!handled && linux_ui &&
linux_ui->GetTextEditCommandsForEvent(event, &commands)) {
for (const auto& command : commands) {
if (IsTextEditCommandEnabled(command.command())) {
ExecuteTextEditCommand(command.command());
handled = true;
}
}
return handled;
}
#endif
base::AutoReset<bool> show_rejection_ui(&show_rejection_ui_if_any_, true);
if (edit_command == ui::TextEditCommand::INVALID_COMMAND)
edit_command = GetCommandForKeyEvent(event);
if (!handled && IsTextEditCommandEnabled(edit_command)) {
ExecuteTextEditCommand(edit_command);
handled = true;
}
return handled;
}
bool Textfield::OnKeyReleased(const ui::KeyEvent& event) {
return controller_ && controller_->HandleKeyEvent(this, event);
}
void Textfield::OnGestureEvent(ui::GestureEvent* event) {
switch (event->type()) {
case ui::ET_GESTURE_TAP: {
RequestFocusForGesture(event->details());
if (controller_ && controller_->HandleGestureEvent(this, *event)) {
selection_dragging_state_ = SelectionDraggingState::kNone;
event->SetHandled();
return;
}
const size_t tap_pos =
GetRenderText()->FindCursorPosition(event->location()).caret_pos();
const bool should_toggle_menu = event->details().tap_count() == 1 &&
GetSelectedRange() == gfx::Range(tap_pos);
if (selection_dragging_state_ != SelectionDraggingState::kNone) {
selection_dragging_state_ = SelectionDraggingState::kNone;
} else if (event->details().tap_count() == 1) {
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 (touch_selection_controller_ && should_toggle_menu) {
touch_selection_controller_->ToggleQuickMenu();
}
event->SetHandled();
break;
}
case ui::ET_GESTURE_TAP_DOWN: {
if (::features::IsTouchTextEditingRedesignEnabled() && HasFocus()) {
if (event->details().tap_down_count() == 2) {
OnBeforeUserAction();
SelectWordAt(event->location());
OnAfterUserAction();
} else if (event->details().tap_down_count() == 3) {
OnBeforeUserAction();
SelectAll(false);
OnAfterUserAction();
} else {
break;
}
DestroyTouchSelection();
selection_dragging_state_ =
SelectionDraggingState::kDraggingSelectionExtent;
event->SetHandled();
}
break;
}
case ui::ET_GESTURE_LONG_PRESS:
if (!GetRenderText()->IsPointInSelection(event->location())) {
OnBeforeUserAction();
SelectWordAt(event->location());
OnAfterUserAction();
CreateTouchSelectionControllerAndNotifyIt();
if (::features::IsTouchTextEditingRedesignEnabled()) {
selection_dragging_state_ =
SelectionDraggingState::kDraggingSelectionExtent;
}
if (touch_selection_controller_)
event->SetHandled();
} else {
DestroyTouchSelection();
initiating_drag_ = switches::IsTouchDragDropEnabled();
}
break;
case ui::ET_GESTURE_LONG_TAP:
selection_dragging_state_ = SelectionDraggingState::kNone;
if (touch_selection_controller_)
event->SetHandled();
break;
case ui::ET_GESTURE_SCROLL_BEGIN:
if (HasFocus()) {
if (::features::IsTouchTextEditingRedesignEnabled()) {
MaybeStartSelectionDragging(event);
}
if (selection_dragging_state_ == SelectionDraggingState::kNone) {
drag_start_location_x_ = event->location().x();
drag_start_display_offset_ =
GetRenderText()->GetUpdatedDisplayOffset().x();
show_touch_handles_after_scroll_ =
touch_selection_controller_ != nullptr;
} else {
show_touch_handles_after_scroll_ = true;
}
DestroyTouchSelection();
event->SetHandled();
}
break;
case ui::ET_GESTURE_SCROLL_UPDATE:
if (HasFocus()) {
if (selection_dragging_state_ != SelectionDraggingState::kNone &&
event->details().touch_points() > 1) {
selection_dragging_state_ = SelectionDraggingState::kNone;
drag_start_location_x_ = event->location().x();
drag_start_display_offset_ =
GetRenderText()->GetUpdatedDisplayOffset().x();
show_touch_handles_after_scroll_ = true;
}
switch (selection_dragging_state_) {
case SelectionDraggingState::kDraggingSelectionExtent:
MoveRangeSelectionExtent(event->location() +
selection_dragging_offset_);
break;
case SelectionDraggingState::kDraggingCursor:
MoveCursorTo(event->location(), false);
break;
case SelectionDraggingState::kNone: {
int new_display_offset = drag_start_display_offset_ +
event->location().x() -
drag_start_location_x_;
GetRenderText()->SetDisplayOffset(new_display_offset);
SchedulePaint();
break;
}
}
event->SetHandled();
}
break;
case ui::ET_GESTURE_SCROLL_END:
case ui::ET_SCROLL_FLING_START:
if (HasFocus()) {
if (show_touch_handles_after_scroll_) {
CreateTouchSelectionControllerAndNotifyIt();
show_touch_handles_after_scroll_ = false;
}
selection_dragging_state_ = SelectionDraggingState::kNone;
event->SetHandled();
}
break;
case ui::ET_GESTURE_END:
selection_dragging_state_ = SelectionDraggingState::kNone;
break;
default:
return;
}
}
bool Textfield::AcceleratorPressed(const ui::Accelerator& accelerator) {
ui::KeyEvent event(
accelerator.key_state() == ui::Accelerator::KeyState::PRESSED
? ui::ET_KEY_PRESSED
: ui::ET_KEY_RELEASED,
accelerator.key_code(), accelerator.modifiers());
ExecuteTextEditCommand(GetCommandForKeyEvent(event));
return true;
}
bool Textfield::CanHandleAccelerators() const {
return GetRenderText()->focused() && View::CanHandleAccelerators();
}
void Textfield::AboutToRequestFocusFromTabTraversal(bool reverse) {
SelectAll(false);
}
bool Textfield::SkipDefaultKeyEventProcessing(const ui::KeyEvent& event) {
#if BUILDFLAG(IS_LINUX)
auto* linux_ui = ui::LinuxUi::instance();
std::vector<ui::TextEditCommandAuraLinux> commands;
if (linux_ui && linux_ui->GetTextEditCommandsForEvent(event, &commands)) {
const auto is_enabled = [this](const auto& command) {
return IsTextEditCommandEnabled(command.command());
};
if (base::ranges::any_of(commands, is_enabled))
return true;
}
#endif
const bool is_backspace = event.key_code() == ui::VKEY_BACK;
return (is_backspace && !GetReadOnly()) || event.IsUnicodeKeyCode();
}
bool Textfield::GetDropFormats(
int* formats,
std::set<ui::ClipboardFormatType>* format_types) {
if (!GetEnabled() || GetReadOnly())
return false;
*formats = ui::OSExchangeData::STRING;
if (controller_)
controller_->AppendDropFormats(formats, format_types);
return true;
}
bool Textfield::CanDrop(const OSExchangeData& data) {
int formats;
std::set<ui::ClipboardFormatType> format_types;
GetDropFormats(&formats, &format_types);
return GetEnabled() && !GetReadOnly() &&
data.HasAnyFormat(formats, format_types);
}
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;
OnCaretBoundsChanged();
SchedulePaint();
StopBlinkingCursor();
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;
if (ShouldBlinkCursor())
StartBlinkingCursor();
SchedulePaint();
}
views::View::DropCallback Textfield::GetDropCallback(
const ui::DropTargetEvent& event) {
DCHECK(CanDrop(event.data()));
drop_cursor_visible_ = false;
if (controller_) {
auto cb = controller_->CreateDropCallback(event);
if (!cb.is_null())
return cb;
}
DCHECK(!initiating_drag_ ||
!GetRenderText()->IsPointInSelection(event.location()));
return base::BindOnce(&Textfield::DropDraggedText,
drop_weak_ptr_factory_.GetWeakPtr());
}
void Textfield::OnDragDone() {
initiating_drag_ = false;
drop_cursor_visible_ = false;
}
void Textfield::GetAccessibleNodeData(ui::AXNodeData* node_data) {
View::GetAccessibleNodeData(node_data);
node_data->AddState(ax::mojom::State::kEditable);
if (GetEnabled()) {
node_data->SetDefaultActionVerb(ax::mojom::DefaultActionVerb::kActivate);
if (GetReadOnly())
node_data->SetRestriction(ax::mojom::Restriction::kReadOnly);
}
if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD) {
node_data->AddState(ax::mojom::State::kProtected);
node_data->SetValue(std::u16string(
GetText().size(), gfx::RenderText::kPasswordReplacementChar));
} else {
node_data->SetValue(GetText());
}
node_data->AddStringAttribute(ax::mojom::StringAttribute::kPlaceholder,
base::UTF16ToUTF8(GetPlaceholderText()));
const gfx::Range range = GetSelectedRange();
node_data->AddIntAttribute(ax::mojom::IntAttribute::kTextSelStart,
base::checked_cast<int32_t>(range.start()));
node_data->AddIntAttribute(ax::mojom::IntAttribute::kTextSelEnd,
base::checked_cast<int32_t>(range.end()));
}
bool Textfield::HandleAccessibleAction(const ui::AXActionData& action_data) {
if (action_data.action == ax::mojom::Action::kSetSelection) {
DCHECK_EQ(action_data.anchor_node_id, action_data.focus_node_id);
const gfx::Range range(static_cast<size_t>(action_data.anchor_offset),
static_cast<size_t>(action_data.focus_offset));
return SetEditableSelectionRange(range);
}
if (GetReadOnly())
return View::HandleAccessibleAction(action_data);
if (action_data.action == ax::mojom::Action::kSetValue) {
SetText(base::UTF8ToUTF16(action_data.value));
ClearSelection();
return true;
} else if (action_data.action == ax::mojom::Action::kReplaceSelectedText) {
InsertOrReplaceText(base::UTF8ToUTF16(action_data.value));
ClearSelection();
return true;
}
return View::HandleAccessibleAction(action_data);
}
void Textfield::OnBoundsChanged(const gfx::Rect& previous_bounds) {
FitToLocalBounds();
}
bool Textfield::GetNeedsNotificationWhenVisibleBoundsChange() const {
return true;
}
void Textfield::OnVisibleBoundsChanged() {
if (touch_selection_controller_)
touch_selection_controller_->SelectionChanged();
}
void Textfield::OnPaint(gfx::Canvas* canvas) {
OnPaintBackground(canvas);
PaintTextAndCursor(canvas);
OnPaintBorder(canvas);
}
void Textfield::OnFocus() {
if (focus_reason_ == ui::TextInputClient::FOCUS_REASON_NONE)
focus_reason_ = ui::TextInputClient::FOCUS_REASON_OTHER;
#if BUILDFLAG(IS_MAC)
if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD)
password_input_enabler_ =
std::make_unique<ui::ScopedPasswordInputEnabler>();
#endif
GetRenderText()->set_focused(true);
if (GetInputMethod())
GetInputMethod()->SetFocusedTextInputClient(this);
UpdateAfterChange(TextChangeType::kNone, true);
View::OnFocus();
}
void Textfield::OnBlur() {
focus_reason_ = ui::TextInputClient::FOCUS_REASON_NONE;
gfx::RenderText* render_text = GetRenderText();
render_text->set_focused(false);
if (GetInputMethod()) {
GetInputMethod()->DetachTextInputClient(this);
#if BUILDFLAG(IS_CHROMEOS_ASH)
wm::RestoreWindowBoundsOnClientFocusLost(
GetNativeView()->GetToplevelWindow());
#endif
}
StopBlinkingCursor();
cursor_view_->SetVisible(false);
DestroyTouchSelection();
SchedulePaint();
View::OnBlur();
#if BUILDFLAG(IS_MAC)
password_input_enabler_.reset();
#endif
}
gfx::Point Textfield::GetKeyboardContextMenuLocation() {
return GetCaretBounds().bottom_right();
}
void Textfield::OnThemeChanged() {
View::OnThemeChanged();
gfx::RenderText* render_text = GetRenderText();
SetColor(GetTextColor());
UpdateBackgroundColor();
render_text->set_selection_color(GetSelectionTextColor());
render_text->set_selection_background_focused_color(
GetSelectionBackgroundColor());
cursor_view_->layer()->SetColor(GetTextColor());
}
void Textfield::OnCompositionTextConfirmedOrCleared() {
if (!skip_input_method_cancel_composition_)
GetInputMethod()->CancelComposition(this);
}
void Textfield::OnTextChanged() {
OnPropertyChanged(&model_ + kTextfieldText, kPropertyEffectsPaint);
drop_weak_ptr_factory_.InvalidateWeakPtrs();
}
void Textfield::ShowContextMenuForViewImpl(View* source,
const gfx::Point& point,
ui::MenuSourceType source_type) {
UpdateContextMenu();
context_menu_runner_->RunMenuAt(GetWidget(), nullptr,
gfx::Rect(point, gfx::Size()),
MenuAnchorPosition::kTopLeft, source_type);
}
void Textfield::WriteDragDataForView(View* sender,
const gfx::Point& press_pt,
OSExchangeData* data) {
const std::u16string& 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();
display::Display display =
display::Screen::GetScreen()->GetDisplayNearestView(native_view);
size.SetToMin(gfx::Size(display.size().width(), height()));
label.SetBoundsRect(gfx::Rect(size));
label.SetEnabledColor(GetTextColor());
SkBitmap bitmap;
float raster_scale = ScaleFactorForDragFromWidget(GetWidget());
SkColor color = CanUseTransparentBackgroundForDragImage()
? SK_ColorTRANSPARENT
: GetBackgroundColor();
label.Paint(PaintInfo::CreateRootPaintInfo(
ui::CanvasPainter(&bitmap, label.size(), raster_scale, color,
GetWidget()->GetCompositor()->is_pixel_canvas())
.context(),
label.size()));
constexpr gfx::Vector2d kOffset(-15, 0);
gfx::ImageSkia image = gfx::ImageSkia::CreateFromBitmap(bitmap, raster_scale);
data->provider().SetDragImage(image, kOffset);
if (controller_)
controller_->OnWriteDragData(data);
}
int Textfield::GetDragOperationsForView(View* sender, const gfx::Point& p) {
int drag_operations = ui::DragDropTypes::DRAG_COPY;
if (!GetEnabled() || text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD ||
!GetRenderText()->IsPointInSelection(p)) {
drag_operations = ui::DragDropTypes::DRAG_NONE;
} else if (sender == this && !GetReadOnly()) {
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);
}
bool Textfield::GetWordLookupDataAtPoint(const gfx::Point& point,
gfx::DecoratedText* decorated_word,
gfx::Point* baseline_point) {
return GetRenderText()->GetWordLookupDataAtPoint(point, decorated_word,
baseline_point);
}
bool Textfield::GetWordLookupDataFromSelection(
gfx::DecoratedText* decorated_text,
gfx::Point* baseline_point) {
return GetRenderText()->GetLookupDataForRange(GetRenderText()->selection(),
decorated_text, baseline_point);
}
bool Textfield::HasTextBeingDragged() const {
return initiating_drag_;
}
void Textfield::MoveCaret(const gfx::Point& position) {
SelectBetweenCoordinates(position, position);
}
void Textfield::MoveRangeSelectionExtent(const gfx::Point& extent) {
if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE) {
return;
}
gfx::SelectionModel new_extent_caret =
GetRenderText()->FindCursorPosition(extent);
size_t new_extent_pos = new_extent_caret.caret_pos();
size_t extent_pos = extent_caret_.caret_pos();
if (new_extent_pos == extent_pos) {
return;
}
gfx::SelectionModel base_caret =
GetRenderText()->GetSelectionModelForSelectionStart();
size_t base_pos = base_caret.caret_pos();
size_t end_pos = new_extent_pos;
gfx::LogicalCursorDirection cursor_direction =
new_extent_pos > base_pos ? gfx::CURSOR_FORWARD : gfx::CURSOR_BACKWARD;
bool selection_shrinking = cursor_direction == gfx::CURSOR_FORWARD
? new_extent_pos < extent_pos
: new_extent_pos > extent_pos;
gfx::Range word_range =
GetRenderText()->ExpandRangeToWordBoundary(gfx::Range(extent_pos));
bool extent_moved_past_next_word_boundary =
(cursor_direction == gfx::CURSOR_BACKWARD &&
new_extent_pos <= word_range.start()) ||
(cursor_direction == gfx::CURSOR_FORWARD &&
new_extent_pos >= word_range.end());
if (::features::IsTouchTextEditingRedesignEnabled()) {
if (selection_shrinking) {
break_type_ = gfx::CHARACTER_BREAK;
} else if (extent_moved_past_next_word_boundary) {
break_type_ = gfx::WORD_BREAK;
}
}
if (break_type_ == gfx::WORD_BREAK) {
gfx::Range new_word_range =
GetRenderText()->ExpandRangeToWordBoundary(gfx::Range(new_extent_pos));
DCHECK(new_extent_pos >= new_word_range.start() &&
new_extent_pos <= new_word_range.end());
end_pos = new_extent_pos - new_word_range.start() <
new_word_range.end() - new_extent_pos
? new_word_range.start()
: new_word_range.end();
}
gfx::SelectionModel selection(gfx::Range(base_pos, end_pos),
cursor_direction);
OnBeforeUserAction();
SelectSelectionModel(selection);
OnAfterUserAction();
extent_caret_ = new_extent_caret;
}
void Textfield::SelectBetweenCoordinates(const gfx::Point& base,
const gfx::Point& extent) {
if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE) {
return;
}
gfx::SelectionModel base_caret = GetRenderText()->FindCursorPosition(base);
gfx::SelectionModel extent_caret =
GetRenderText()->FindCursorPosition(extent);
gfx::SelectionModel selection(
gfx::Range(base_caret.caret_pos(), extent_caret.caret_pos()),
extent_caret.caret_affinity());
OnBeforeUserAction();
SelectSelectionModel(selection);
OnAfterUserAction();
extent_caret_ = extent_caret;
break_type_ = gfx::CHARACTER_BREAK;
}
void Textfield::GetSelectionEndPoints(gfx::SelectionBound* anchor,
gfx::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(gfx::PointF(r1.origin()), gfx::PointF(r1.bottom_left()));
focus->SetEdge(gfx::PointF(r2.origin()), gfx::PointF(r2.bottom_left()));
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(gfx::SelectionBound::CENTER);
focus->set_type(gfx::SelectionBound::CENTER);
} else if ((ltr && anchor_position_index < focus_position_index) ||
(!ltr && anchor_position_index > focus_position_index)) {
anchor->set_type(gfx::SelectionBound::LEFT);
focus->set_type(gfx::SelectionBound::RIGHT);
} else {
anchor->set_type(gfx::SelectionBound::RIGHT);
focus->set_type(gfx::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);
}
void Textfield::OpenContextMenu(const gfx::Point& anchor) {
DestroyTouchSelection();
ShowContextMenu(anchor, ui::MENU_SOURCE_TOUCH_EDIT_MENU);
}
void Textfield::DestroyTouchSelection() {
touch_selection_controller_.reset();
}
bool Textfield::IsCommandIdChecked(int command_id) const {
if (text_services_context_menu_ &&
text_services_context_menu_->SupportsCommand(command_id)) {
return text_services_context_menu_->IsCommandIdChecked(command_id);
}
return true;
}
bool Textfield::IsCommandIdEnabled(int command_id) const {
if (text_services_context_menu_ &&
text_services_context_menu_->SupportsCommand(command_id)) {
return text_services_context_menu_->IsCommandIdEnabled(command_id);
}
return IsTextEditCommandEnabled(
GetTextEditCommandFromMenuCommand(command_id, HasSelection()));
}
bool Textfield::GetAcceleratorForCommandId(int command_id,
ui::Accelerator* accelerator) const {
switch (command_id) {
case kUndo:
*accelerator = ui::Accelerator(ui::VKEY_Z, ui::EF_PLATFORM_ACCELERATOR);
return true;
case kCut:
*accelerator = ui::Accelerator(ui::VKEY_X, ui::EF_PLATFORM_ACCELERATOR);
return true;
case kCopy:
*accelerator = ui::Accelerator(ui::VKEY_C, ui::EF_PLATFORM_ACCELERATOR);
return true;
case kPaste:
*accelerator = ui::Accelerator(ui::VKEY_V, ui::EF_PLATFORM_ACCELERATOR);
return true;
case kSelectAll:
*accelerator = ui::Accelerator(ui::VKEY_A, ui::EF_PLATFORM_ACCELERATOR);
return true;
default:
return text_services_context_menu_->GetAcceleratorForCommandId(
command_id, accelerator);
}
}
void Textfield::ExecuteCommand(int command_id, int event_flags) {
if (text_services_context_menu_ &&
text_services_context_menu_->SupportsCommand(command_id)) {
text_services_context_menu_->ExecuteCommand(command_id, event_flags);
return;
}
Textfield::ExecuteTextEditCommand(
GetTextEditCommandFromMenuCommand(command_id, HasSelection()));
if (::features::IsTouchTextEditingRedesignEnabled() &&
(event_flags & ui::EF_FROM_TOUCH) &&
(command_id == Textfield::kSelectAll ||
command_id == Textfield::kSelectWord)) {
CreateTouchSelectionControllerAndNotifyIt();
}
}
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(TextChangeType::kUserTriggered, true);
OnAfterUserAction();
}
size_t Textfield::ConfirmCompositionText(bool keep_selection) {
if (keep_selection) {
NOTIMPLEMENTED_LOG_ONCE();
}
if (!model_->HasCompositionText())
return 0;
OnBeforeUserAction();
skip_input_method_cancel_composition_ = true;
const size_t confirmed_text_length = model_->ConfirmCompositionText();
skip_input_method_cancel_composition_ = false;
UpdateAfterChange(TextChangeType::kUserTriggered, true);
OnAfterUserAction();
return confirmed_text_length;
}
void Textfield::ClearCompositionText() {
if (!model_->HasCompositionText())
return;
OnBeforeUserAction();
skip_input_method_cancel_composition_ = true;
model_->CancelCompositionText();
skip_input_method_cancel_composition_ = false;
UpdateAfterChange(TextChangeType::kUserTriggered, true);
OnAfterUserAction();
}
void Textfield::InsertText(const std::u16string& new_text,
InsertTextCursorBehavior cursor_behavior) {
std::u16string filtered_new_text;
base::ranges::copy_if(new_text, std::back_inserter(filtered_new_text),
IsValidCharToInsert);
if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE ||
filtered_new_text.empty())
return;
OnBeforeUserAction();
skip_input_method_cancel_composition_ = true;
model_->InsertText(filtered_new_text);
skip_input_method_cancel_composition_ = false;
UpdateAfterChange(TextChangeType::kUserTriggered, true);
OnAfterUserAction();
}
void Textfield::InsertChar(const ui::KeyEvent& event) {
if (GetReadOnly()) {
OnEditFailed();
return;
}
const char16_t ch = event.GetCharacter();
const bool should_insert_char = IsValidCharToInsert(ch) &&
!ui::IsSystemKeyModifier(event.flags()) &&
!IsControlKeyModifier(event.flags());
if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE || !should_insert_char)
return;
DoInsertChar(ch);
if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD) {
password_char_reveal_index_ = absl::nullopt;
base::TimeDelta duration = GetPasswordRevealDuration(event);
if (!duration.is_zero()) {
const size_t change_offset = model_->GetCursorPosition();
DCHECK_GT(change_offset, 0u);
RevealPasswordChar(change_offset - 1, duration);
}
}
}
ui::TextInputType Textfield::GetTextInputType() const {
if (GetReadOnly() || !GetEnabled())
return ui::TEXT_INPUT_TYPE_NONE;
return text_input_type_;
}
ui::TextInputMode Textfield::GetTextInputMode() const {
return ui::TEXT_INPUT_MODE_DEFAULT;
}
base::i18n::TextDirection Textfield::GetTextDirection() const {
return GetRenderText()->GetDisplayTextDirection();
}
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;
}
gfx::Rect Textfield::GetSelectionBoundingBox() const {
NOTIMPLEMENTED_LOG_ONCE();
return gfx::Rect();
}
bool Textfield::GetCompositionCharacterBounds(size_t 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();
}
ui::TextInputClient::FocusReason Textfield::GetFocusReason() const {
return focus_reason_;
}
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::GetEditableSelectionRange(gfx::Range* range) const {
if (!ImeEditingAllowed())
return false;
*range = GetRenderText()->selection();
return true;
}
bool Textfield::SetEditableSelectionRange(const gfx::Range& range) {
if (!ImeEditingAllowed() || !range.IsValid())
return false;
OnBeforeUserAction();
SetSelectedRange(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(TextChangeType::kUserTriggered, true);
}
OnAfterUserAction();
return true;
}
bool Textfield::GetTextFromRange(const gfx::Range& range,
std::u16string* range_text) const {
if (!ImeEditingAllowed() || !range.IsValid())
return false;
gfx::Range text_range;
if (!GetTextRange(&text_range) || !range.IsBoundedBy(text_range))
return false;
*range_text = model_->GetTextFromRange(range);
return true;
}
void Textfield::OnInputMethodChanged() {}
bool Textfield::ChangeTextDirectionAndLayoutAlignment(
base::i18n::TextDirection direction) {
DCHECK_NE(direction, base::i18n::UNKNOWN_DIRECTION);
const bool default_rtl = direction == base::i18n::RIGHT_TO_LEFT;
const auto new_mode = default_rtl ? gfx::DIRECTIONALITY_FORCE_RTL
: gfx::DIRECTIONALITY_FORCE_LTR;
auto* render_text = GetRenderText();
const bool modes_match = new_mode == render_text->directionality_mode();
render_text->SetDirectionalityMode(modes_match ? gfx::DIRECTIONALITY_FROM_TEXT
: new_mode);
bool dir_from_text =
modes_match && GetHorizontalAlignment() == gfx::ALIGN_TO_HEAD;
if (!dir_from_text && GetHorizontalAlignment() != gfx::ALIGN_CENTER)
SetHorizontalAlignment(default_rtl ? gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT);
SchedulePaint();
return true;
}
void Textfield::ExtendSelectionAndDelete(size_t before, size_t after) {
gfx::Range range = GetRenderText()->selection();
if (range.start() < before)
return;
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::EnsureCaretNotInRect(const gfx::Rect& rect_in_screen) {
#if BUILDFLAG(IS_CHROMEOS_ASH)
aura::Window* top_level_window = GetNativeView()->GetToplevelWindow();
wm::EnsureWindowNotInRect(top_level_window, rect_in_screen);
#endif
}
bool Textfield::IsTextEditCommandEnabled(ui::TextEditCommand command) const {
std::u16string result;
bool editable = !GetReadOnly();
bool readable = text_input_type_ != ui::TEXT_INPUT_TYPE_PASSWORD;
switch (command) {
case ui::TextEditCommand::DELETE_BACKWARD:
case ui::TextEditCommand::DELETE_FORWARD:
case ui::TextEditCommand::DELETE_TO_BEGINNING_OF_LINE:
case ui::TextEditCommand::DELETE_TO_BEGINNING_OF_PARAGRAPH:
case ui::TextEditCommand::DELETE_TO_END_OF_LINE:
case ui::TextEditCommand::DELETE_TO_END_OF_PARAGRAPH:
case ui::TextEditCommand::DELETE_WORD_BACKWARD:
case ui::TextEditCommand::DELETE_WORD_FORWARD:
return editable;
case ui::TextEditCommand::MOVE_BACKWARD:
case ui::TextEditCommand::MOVE_BACKWARD_AND_MODIFY_SELECTION:
case ui::TextEditCommand::MOVE_FORWARD:
case ui::TextEditCommand::MOVE_FORWARD_AND_MODIFY_SELECTION:
case ui::TextEditCommand::MOVE_LEFT:
case ui::TextEditCommand::MOVE_LEFT_AND_MODIFY_SELECTION:
case ui::TextEditCommand::MOVE_RIGHT:
case ui::TextEditCommand::MOVE_RIGHT_AND_MODIFY_SELECTION:
case ui::TextEditCommand::MOVE_TO_BEGINNING_OF_DOCUMENT:
case ui::TextEditCommand::
MOVE_TO_BEGINNING_OF_DOCUMENT_AND_MODIFY_SELECTION:
case ui::TextEditCommand::MOVE_TO_BEGINNING_OF_LINE:
case ui::TextEditCommand::MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION:
case ui::TextEditCommand::MOVE_TO_BEGINNING_OF_PARAGRAPH:
case ui::TextEditCommand::
MOVE_TO_BEGINNING_OF_PARAGRAPH_AND_MODIFY_SELECTION:
case ui::TextEditCommand::MOVE_TO_END_OF_DOCUMENT:
case ui::TextEditCommand::MOVE_TO_END_OF_DOCUMENT_AND_MODIFY_SELECTION:
case ui::TextEditCommand::MOVE_TO_END_OF_LINE:
case ui::TextEditCommand::MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION:
case ui::TextEditCommand::MOVE_TO_END_OF_PARAGRAPH:
case ui::TextEditCommand::MOVE_TO_END_OF_PARAGRAPH_AND_MODIFY_SELECTION:
case ui::TextEditCommand::MOVE_PARAGRAPH_FORWARD_AND_MODIFY_SELECTION:
case ui::TextEditCommand::MOVE_PARAGRAPH_BACKWARD_AND_MODIFY_SELECTION:
case ui::TextEditCommand::MOVE_WORD_BACKWARD:
case ui::TextEditCommand::MOVE_WORD_BACKWARD_AND_MODIFY_SELECTION:
case ui::TextEditCommand::MOVE_WORD_FORWARD:
case ui::TextEditCommand::MOVE_WORD_FORWARD_AND_MODIFY_SELECTION:
case ui::TextEditCommand::MOVE_WORD_LEFT:
case ui::TextEditCommand::MOVE_WORD_LEFT_AND_MODIFY_SELECTION:
case ui::TextEditCommand::MOVE_WORD_RIGHT:
case ui::TextEditCommand::MOVE_WORD_RIGHT_AND_MODIFY_SELECTION:
return true;
case ui::TextEditCommand::UNDO:
return editable && model_->CanUndo();
case ui::TextEditCommand::REDO:
return editable && model_->CanRedo();
case ui::TextEditCommand::CUT:
return editable && readable && HasSelection();
case ui::TextEditCommand::COPY:
return readable && HasSelection();
case ui::TextEditCommand::PASTE: {
ui::DataTransferEndpoint data_dst(ui::EndpointType::kDefault,
show_rejection_ui_if_any_);
ui::Clipboard::GetForCurrentThread()->ReadText(
ui::ClipboardBuffer::kCopyPaste, &data_dst, &result);
}
return editable && !result.empty();
case ui::TextEditCommand::SELECT_ALL:
return !GetText().empty() &&
GetSelectedRange().length() != GetText().length();
case ui::TextEditCommand::SELECT_WORD:
return readable && !GetText().empty() && !HasSelection();
case ui::TextEditCommand::TRANSPOSE:
return editable && !HasSelection() && !model_->HasCompositionText();
case ui::TextEditCommand::YANK:
return editable;
case ui::TextEditCommand::MOVE_DOWN:
case ui::TextEditCommand::MOVE_DOWN_AND_MODIFY_SELECTION:
case ui::TextEditCommand::MOVE_PAGE_DOWN:
case ui::TextEditCommand::MOVE_PAGE_DOWN_AND_MODIFY_SELECTION:
case ui::TextEditCommand::MOVE_PAGE_UP:
case ui::TextEditCommand::MOVE_PAGE_UP_AND_MODIFY_SELECTION:
case ui::TextEditCommand::MOVE_UP:
case ui::TextEditCommand::MOVE_UP_AND_MODIFY_SELECTION:
case ui::TextEditCommand::SCROLL_TO_BEGINNING_OF_DOCUMENT:
case ui::TextEditCommand::SCROLL_TO_END_OF_DOCUMENT:
case ui::TextEditCommand::SCROLL_PAGE_DOWN:
case ui::TextEditCommand::SCROLL_PAGE_UP:
#if BUILDFLAG(IS_MAC)
return true;
#else
return GetRenderText()->multiline();
#endif
case ui::TextEditCommand::INSERT_TEXT:
case ui::TextEditCommand::SET_MARK:
case ui::TextEditCommand::UNSELECT:
case ui::TextEditCommand::INVALID_COMMAND:
return false;
}
NOTREACHED_NORETURN();
}
void Textfield::SetTextEditCommandForNextKeyEvent(ui::TextEditCommand command) {
DCHECK_EQ(ui::TextEditCommand::INVALID_COMMAND, scheduled_text_edit_command_);
scheduled_text_edit_command_ = command;
}
ukm::SourceId Textfield::GetClientSourceForMetrics() const {
NOTIMPLEMENTED_LOG_ONCE();
return ukm::SourceId();
}
bool Textfield::ShouldDoLearning() {
if (should_do_learning_.has_value())
return should_do_learning_.value();
NOTIMPLEMENTED_LOG_ONCE();
DVLOG(1) << "This Textfield instance does not support ShouldDoLearning";
return false;
}
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
bool Textfield::SetCompositionFromExistingText(
const gfx::Range& range,
const std::vector<ui::ImeTextSpan>& ui_ime_text_spans) {
DCHECK(!model_->HasCompositionText());
OnBeforeUserAction();
model_->SetCompositionFromExistingText(range);
SchedulePaint();
OnAfterUserAction();
return true;
}
#endif
#if BUILDFLAG(IS_CHROMEOS)
gfx::Range Textfield::GetAutocorrectRange() const {
return model_->autocorrect_range();
}
gfx::Rect Textfield::GetAutocorrectCharacterBounds() const {
gfx::Range autocorrect_range = model_->autocorrect_range();
if (autocorrect_range.is_empty())
return gfx::Rect();
gfx::RenderText* render_text = GetRenderText();
const gfx::SelectionModel caret(autocorrect_range, gfx::CURSOR_BACKWARD);
gfx::Rect rect;
rect = render_text->GetCursorBounds(caret, false);
ConvertRectToScreen(this, &rect);
return rect;
}
bool Textfield::SetAutocorrectRange(const gfx::Range& range) {
if (!range.is_empty()) {
base::UmaHistogramEnumeration("InputMethod.Assistive.Autocorrect.Count",
TextInputClient::SubClass::kTextField);
#if BUILDFLAG(IS_CHROMEOS_ASH)
auto* input_method_manager = ash::input_method::InputMethodManager::Get();
if (input_method_manager &&
ash::extension_ime_util::IsExperimentalMultilingual(
input_method_manager->GetActiveIMEState()
->GetCurrentInputMethod()
.id())) {
base::UmaHistogramEnumeration(
"InputMethod.MultilingualExperiment.Autocorrect.Count",
TextInputClient::SubClass::kTextField);
}
#endif
}
return model_->SetAutocorrectRange(range);
}
bool Textfield::AddGrammarFragments(
const std::vector<ui::GrammarFragment>& fragments) {
if (!fragments.empty()) {
base::UmaHistogramEnumeration("InputMethod.Assistive.Grammar.Count",
TextInputClient::SubClass::kTextField);
}
NOTIMPLEMENTED_LOG_ONCE();
return false;
}
#endif
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS)
void Textfield::GetActiveTextInputControlLayoutBounds(
absl::optional<gfx::Rect>* control_bounds,
absl::optional<gfx::Rect>* selection_bounds) {
gfx::Rect origin = GetContentsBounds();
ConvertRectToScreen(this, &origin);
*control_bounds = origin;
}
#endif
#if BUILDFLAG(IS_WIN)
void Textfield::SetActiveCompositionForAccessibility(
const gfx::Range& range,
const std::u16string& active_composition_text,
bool is_composition_committed) {}
#endif
void Textfield::DoInsertChar(char16_t ch) {
OnBeforeUserAction();
skip_input_method_cancel_composition_ = true;
model_->InsertChar(ch);
skip_input_method_cancel_composition_ = false;
UpdateAfterChange(TextChangeType::kUserTriggered, true);
OnAfterUserAction();
}
gfx::RenderText* Textfield::GetRenderText() const {
return model_->render_text();
}
gfx::Point Textfield::GetLastClickRootLocation() const {
return selection_controller_.last_click_root_location();
}
std::u16string Textfield::GetSelectionClipboardText() const {
std::u16string selection_clipboard_text;
ui::Clipboard::GetForCurrentThread()->ReadText(
ui::ClipboardBuffer::kSelection, nullptr,
&selection_clipboard_text);
return selection_clipboard_text;
}
void Textfield::ExecuteTextEditCommand(ui::TextEditCommand command) {
DestroyTouchSelection();
if (!IsTextEditCommandEnabled(command))
return;
OnBeforeUserAction();
gfx::SelectionModel selection_model = GetSelectionModel();
auto [text_changed, cursor_changed] = DoExecuteTextEditCommand(command);
cursor_changed |= (GetSelectionModel() != selection_model);
if (cursor_changed && HasSelection())
UpdateSelectionClipboard();
UpdateAfterChange(
text_changed ? TextChangeType::kUserTriggered : TextChangeType::kNone,
cursor_changed);
OnAfterUserAction();
}
Textfield::EditCommandResult Textfield::DoExecuteTextEditCommand(
ui::TextEditCommand command) {
bool changed = false;
bool cursor_changed = false;
bool add_to_kill_buffer = false;
base::AutoReset<bool> show_rejection_ui(&show_rejection_ui_if_any_, true);
switch (command) {
case ui::TextEditCommand::DELETE_TO_BEGINNING_OF_LINE:
case ui::TextEditCommand::DELETE_TO_BEGINNING_OF_PARAGRAPH:
case ui::TextEditCommand::DELETE_TO_END_OF_LINE:
case ui::TextEditCommand::DELETE_TO_END_OF_PARAGRAPH:
add_to_kill_buffer = text_input_type_ != ui::TEXT_INPUT_TYPE_PASSWORD;
[[fallthrough]];
case ui::TextEditCommand::DELETE_WORD_BACKWARD:
case ui::TextEditCommand::DELETE_WORD_FORWARD:
if (HasSelection())
command = ui::TextEditCommand::DELETE_FORWARD;
break;
default:
break;
}
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;
switch (command) {
case ui::TextEditCommand::DELETE_BACKWARD:
changed = cursor_changed = model_->Backspace(add_to_kill_buffer);
break;
case ui::TextEditCommand::DELETE_FORWARD:
changed = cursor_changed = model_->Delete(add_to_kill_buffer);
break;
case ui::TextEditCommand::DELETE_TO_BEGINNING_OF_LINE:
model_->MoveCursor(gfx::LINE_BREAK, begin, gfx::SELECTION_RETAIN);
changed = cursor_changed = model_->Backspace(add_to_kill_buffer);
break;
case ui::TextEditCommand::DELETE_TO_BEGINNING_OF_PARAGRAPH:
model_->MoveCursor(gfx::FIELD_BREAK, begin, gfx::SELECTION_RETAIN);
changed = cursor_changed = model_->Backspace(add_to_kill_buffer);
break;
case ui::TextEditCommand::DELETE_TO_END_OF_LINE:
model_->MoveCursor(gfx::LINE_BREAK, end, gfx::SELECTION_RETAIN);
changed = cursor_changed = model_->Delete(add_to_kill_buffer);
break;
case ui::TextEditCommand::DELETE_TO_END_OF_PARAGRAPH:
model_->MoveCursor(gfx::FIELD_BREAK, end, gfx::SELECTION_RETAIN);
changed = cursor_changed = model_->Delete(add_to_kill_buffer);
break;
case ui::TextEditCommand::DELETE_WORD_BACKWARD:
model_->MoveCursor(gfx::WORD_BREAK, begin, gfx::SELECTION_RETAIN);
changed = cursor_changed = model_->Backspace(add_to_kill_buffer);
break;
case ui::TextEditCommand::DELETE_WORD_FORWARD:
model_->MoveCursor(gfx::WORD_BREAK, end, gfx::SELECTION_RETAIN);
changed = cursor_changed = model_->Delete(add_to_kill_buffer);
break;
case ui::TextEditCommand::MOVE_BACKWARD:
model_->MoveCursor(gfx::CHARACTER_BREAK, begin, gfx::SELECTION_NONE);
break;
case ui::TextEditCommand::MOVE_BACKWARD_AND_MODIFY_SELECTION:
model_->MoveCursor(gfx::CHARACTER_BREAK, begin, gfx::SELECTION_RETAIN);
break;
case ui::TextEditCommand::MOVE_FORWARD:
model_->MoveCursor(gfx::CHARACTER_BREAK, end, gfx::SELECTION_NONE);
break;
case ui::TextEditCommand::MOVE_FORWARD_AND_MODIFY_SELECTION:
model_->MoveCursor(gfx::CHARACTER_BREAK, end, gfx::SELECTION_RETAIN);
break;
case ui::TextEditCommand::MOVE_LEFT:
model_->MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_LEFT,
gfx::SELECTION_NONE);
break;
case ui::TextEditCommand::MOVE_LEFT_AND_MODIFY_SELECTION:
model_->MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_LEFT,
gfx::SELECTION_RETAIN);
break;
case ui::TextEditCommand::MOVE_RIGHT:
model_->MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_NONE);
break;
case ui::TextEditCommand::MOVE_RIGHT_AND_MODIFY_SELECTION:
model_->MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_RETAIN);
break;
case ui::TextEditCommand::MOVE_TO_BEGINNING_OF_LINE:
model_->MoveCursor(gfx::LINE_BREAK, begin, gfx::SELECTION_NONE);
break;
case ui::TextEditCommand::MOVE_TO_BEGINNING_OF_DOCUMENT:
case ui::TextEditCommand::MOVE_TO_BEGINNING_OF_PARAGRAPH:
case ui::TextEditCommand::MOVE_UP:
case ui::TextEditCommand::MOVE_PAGE_UP:
case ui::TextEditCommand::SCROLL_TO_BEGINNING_OF_DOCUMENT:
case ui::TextEditCommand::SCROLL_PAGE_UP:
model_->MoveCursor(gfx::FIELD_BREAK, begin, gfx::SELECTION_NONE);
break;
case ui::TextEditCommand::MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION:
model_->MoveCursor(gfx::LINE_BREAK, begin, kLineSelectionBehavior);
break;
case ui::TextEditCommand::
MOVE_TO_BEGINNING_OF_DOCUMENT_AND_MODIFY_SELECTION:
case ui::TextEditCommand::
MOVE_TO_BEGINNING_OF_PARAGRAPH_AND_MODIFY_SELECTION:
model_->MoveCursor(gfx::FIELD_BREAK, begin, kLineSelectionBehavior);
break;
case ui::TextEditCommand::MOVE_PAGE_UP_AND_MODIFY_SELECTION:
case ui::TextEditCommand::MOVE_UP_AND_MODIFY_SELECTION:
model_->MoveCursor(gfx::FIELD_BREAK, begin, gfx::SELECTION_RETAIN);
break;
case ui::TextEditCommand::MOVE_TO_END_OF_LINE:
model_->MoveCursor(gfx::LINE_BREAK, end, gfx::SELECTION_NONE);
break;
case ui::TextEditCommand::MOVE_TO_END_OF_DOCUMENT:
case ui::TextEditCommand::MOVE_TO_END_OF_PARAGRAPH:
case ui::TextEditCommand::MOVE_DOWN:
case ui::TextEditCommand::MOVE_PAGE_DOWN:
case ui::TextEditCommand::SCROLL_TO_END_OF_DOCUMENT:
case ui::TextEditCommand::SCROLL_PAGE_DOWN:
model_->MoveCursor(gfx::FIELD_BREAK, end, gfx::SELECTION_NONE);
break;
case ui::TextEditCommand::MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION:
model_->MoveCursor(gfx::LINE_BREAK, end, kLineSelectionBehavior);
break;
case ui::TextEditCommand::MOVE_TO_END_OF_DOCUMENT_AND_MODIFY_SELECTION:
case ui::TextEditCommand::MOVE_TO_END_OF_PARAGRAPH_AND_MODIFY_SELECTION:
model_->MoveCursor(gfx::FIELD_BREAK, end, kLineSelectionBehavior);
break;
case ui::TextEditCommand::MOVE_PAGE_DOWN_AND_MODIFY_SELECTION:
case ui::TextEditCommand::MOVE_DOWN_AND_MODIFY_SELECTION:
model_->MoveCursor(gfx::FIELD_BREAK, end, gfx::SELECTION_RETAIN);
break;
case ui::TextEditCommand::MOVE_PARAGRAPH_BACKWARD_AND_MODIFY_SELECTION:
model_->MoveCursor(gfx::FIELD_BREAK, begin,
kMoveParagraphSelectionBehavior);
break;
case ui::TextEditCommand::MOVE_PARAGRAPH_FORWARD_AND_MODIFY_SELECTION:
model_->MoveCursor(gfx::FIELD_BREAK, end,
kMoveParagraphSelectionBehavior);
break;
case ui::TextEditCommand::MOVE_WORD_BACKWARD:
model_->MoveCursor(gfx::WORD_BREAK, begin, gfx::SELECTION_NONE);
break;
case ui::TextEditCommand::MOVE_WORD_BACKWARD_AND_MODIFY_SELECTION:
model_->MoveCursor(gfx::WORD_BREAK, begin, kWordSelectionBehavior);
break;
case ui::TextEditCommand::MOVE_WORD_FORWARD:
model_->MoveCursor(gfx::WORD_BREAK, end, gfx::SELECTION_NONE);
break;
case ui::TextEditCommand::MOVE_WORD_FORWARD_AND_MODIFY_SELECTION:
model_->MoveCursor(gfx::WORD_BREAK, end, kWordSelectionBehavior);
break;
case ui::TextEditCommand::MOVE_WORD_LEFT:
model_->MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_LEFT,
gfx::SELECTION_NONE);
break;
case ui::TextEditCommand::MOVE_WORD_LEFT_AND_MODIFY_SELECTION:
model_->MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_LEFT,
kWordSelectionBehavior);
break;
case ui::TextEditCommand::MOVE_WORD_RIGHT:
model_->MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_NONE);
break;
case ui::TextEditCommand::MOVE_WORD_RIGHT_AND_MODIFY_SELECTION:
model_->MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT,
kWordSelectionBehavior);
break;
case ui::TextEditCommand::UNDO:
changed = cursor_changed = model_->Undo();
break;
case ui::TextEditCommand::REDO:
changed = cursor_changed = model_->Redo();
break;
case ui::TextEditCommand::CUT:
changed = cursor_changed = Cut();
break;
case ui::TextEditCommand::COPY:
Copy();
break;
case ui::TextEditCommand::PASTE:
changed = cursor_changed = Paste();
break;
case ui::TextEditCommand::SELECT_ALL:
SelectAll(false);
break;
case ui::TextEditCommand::SELECT_WORD:
SelectWord();
break;
case ui::TextEditCommand::TRANSPOSE:
changed = cursor_changed = model_->Transpose();
break;
case ui::TextEditCommand::YANK:
changed = cursor_changed = model_->Yank();
break;
case ui::TextEditCommand::INSERT_TEXT:
case ui::TextEditCommand::SET_MARK:
case ui::TextEditCommand::UNSELECT:
case ui::TextEditCommand::INVALID_COMMAND:
NOTREACHED_NORETURN();
}
return {changed, cursor_changed};
}
void Textfield::OffsetDoubleClickWord(size_t offset) {
selection_controller_.OffsetDoubleClickWord(offset);
}
bool Textfield::IsDropCursorForInsertion() const {
return true;
}
bool Textfield::ShouldShowPlaceholderText() const {
return GetText().empty() && !GetPlaceholderText().empty();
}
void Textfield::RequestFocusWithPointer(ui::EventPointerType pointer_type) {
if (HasFocus())
return;
switch (pointer_type) {
case ui::EventPointerType::kMouse:
focus_reason_ = ui::TextInputClient::FOCUS_REASON_MOUSE;
break;
case ui::EventPointerType::kPen:
focus_reason_ = ui::TextInputClient::FOCUS_REASON_PEN;
break;
case ui::EventPointerType::kTouch:
focus_reason_ = ui::TextInputClient::FOCUS_REASON_TOUCH;
break;
default:
focus_reason_ = ui::TextInputClient::FOCUS_REASON_OTHER;
break;
}
View::RequestFocus();
}
void Textfield::RequestFocusForGesture(const ui::GestureEventDetails& details) {
bool show_virtual_keyboard = true;
#if BUILDFLAG(IS_WIN)
show_virtual_keyboard =
details.primary_pointer_type() == ui::EventPointerType::kTouch ||
details.primary_pointer_type() == ui::EventPointerType::kPen;
#endif
RequestFocusWithPointer(details.primary_pointer_type());
if (show_virtual_keyboard)
ShowVirtualKeyboardIfEnabled();
}
base::CallbackListSubscription Textfield::AddTextChangedCallback(
views::PropertyChangedCallback callback) {
return AddPropertyChangedCallback(&model_ + kTextfieldText,
std::move(callback));
}
bool Textfield::PreHandleKeyPressed(const ui::KeyEvent& event) {
return false;
}
ui::TextEditCommand Textfield::GetCommandForKeyEvent(
const ui::KeyEvent& event) {
if (event.type() != ui::ET_KEY_PRESSED || event.IsUnicodeKeyCode())
return ui::TextEditCommand::INVALID_COMMAND;
const bool shift = event.IsShiftDown();
#if BUILDFLAG(IS_MAC)
const bool command = event.IsCommandDown();
#endif
const bool control = event.IsControlDown() || event.IsCommandDown();
const bool alt = event.IsAltDown() || event.IsAltGrDown();
switch (event.key_code()) {
case ui::VKEY_Z:
if (control && !shift && !alt)
return ui::TextEditCommand::UNDO;
return (control && shift && !alt) ? ui::TextEditCommand::REDO
: ui::TextEditCommand::INVALID_COMMAND;
case ui::VKEY_Y:
return (control && !alt) ? ui::TextEditCommand::REDO
: ui::TextEditCommand::INVALID_COMMAND;
case ui::VKEY_A:
return (control && !alt) ? ui::TextEditCommand::SELECT_ALL
: ui::TextEditCommand::INVALID_COMMAND;
case ui::VKEY_X:
return (control && !alt) ? ui::TextEditCommand::CUT
: ui::TextEditCommand::INVALID_COMMAND;
case ui::VKEY_C:
return (control && !alt) ? ui::TextEditCommand::COPY
: ui::TextEditCommand::INVALID_COMMAND;
case ui::VKEY_V:
return (control && !alt) ? ui::TextEditCommand::PASTE
: ui::TextEditCommand::INVALID_COMMAND;
case ui::VKEY_RIGHT:
if (alt)
return ui::TextEditCommand::INVALID_COMMAND;
if (!shift) {
return control ? ui::TextEditCommand::MOVE_WORD_RIGHT
: ui::TextEditCommand::MOVE_RIGHT;
}
return control ? ui::TextEditCommand::MOVE_WORD_RIGHT_AND_MODIFY_SELECTION
: ui::TextEditCommand::MOVE_RIGHT_AND_MODIFY_SELECTION;
case ui::VKEY_LEFT:
if (alt)
return ui::TextEditCommand::INVALID_COMMAND;
if (!shift) {
return control ? ui::TextEditCommand::MOVE_WORD_LEFT
: ui::TextEditCommand::MOVE_LEFT;
}
return control ? ui::TextEditCommand::MOVE_WORD_LEFT_AND_MODIFY_SELECTION
: ui::TextEditCommand::MOVE_LEFT_AND_MODIFY_SELECTION;
case ui::VKEY_HOME:
if (shift) {
return ui::TextEditCommand::
MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION;
}
#if BUILDFLAG(IS_MAC)
return ui::TextEditCommand::SCROLL_TO_BEGINNING_OF_DOCUMENT;
#else
return ui::TextEditCommand::MOVE_TO_BEGINNING_OF_LINE;
#endif
case ui::VKEY_END:
if (shift)
return ui::TextEditCommand::MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION;
#if BUILDFLAG(IS_MAC)
return ui::TextEditCommand::SCROLL_TO_END_OF_DOCUMENT;
#else
return ui::TextEditCommand::MOVE_TO_END_OF_LINE;
#endif
case ui::VKEY_UP:
#if BUILDFLAG(IS_MAC)
if (control && shift) {
return ui::TextEditCommand::
MOVE_PARAGRAPH_BACKWARD_AND_MODIFY_SELECTION;
}
if (command)
return ui::TextEditCommand::MOVE_TO_BEGINNING_OF_DOCUMENT;
return shift ? ui::TextEditCommand::
MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION
: ui::TextEditCommand::MOVE_UP;
#else
return shift ? ui::TextEditCommand::
MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION
: ui::TextEditCommand::INVALID_COMMAND;
#endif
case ui::VKEY_DOWN:
#if BUILDFLAG(IS_MAC)
if (control && shift) {
return ui::TextEditCommand::MOVE_PARAGRAPH_FORWARD_AND_MODIFY_SELECTION;
}
if (command)
return ui::TextEditCommand::MOVE_TO_END_OF_DOCUMENT;
return shift
? ui::TextEditCommand::MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION
: ui::TextEditCommand::MOVE_DOWN;
#else
return shift
? ui::TextEditCommand::MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION
: ui::TextEditCommand::INVALID_COMMAND;
#endif
case ui::VKEY_BACK:
if (!control) {
#if BUILDFLAG(IS_WIN)
if (alt)
return shift ? ui::TextEditCommand::REDO : ui::TextEditCommand::UNDO;
#endif
return ui::TextEditCommand::DELETE_BACKWARD;
}
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
if (shift)
return ui::TextEditCommand::DELETE_TO_BEGINNING_OF_LINE;
#endif
return ui::TextEditCommand::DELETE_WORD_BACKWARD;
case ui::VKEY_DELETE:
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
if (shift && control)
return ui::TextEditCommand::DELETE_TO_END_OF_LINE;
#endif
if (control)
return ui::TextEditCommand::DELETE_WORD_FORWARD;
return shift ? ui::TextEditCommand::CUT
: ui::TextEditCommand::DELETE_FORWARD;
case ui::VKEY_INSERT:
if (control && !shift)
return ui::TextEditCommand::COPY;
return (shift && !control) ? ui::TextEditCommand::PASTE
: ui::TextEditCommand::INVALID_COMMAND;
case ui::VKEY_PRIOR:
return control ? ui::TextEditCommand::SCROLL_PAGE_UP
: ui::TextEditCommand::INVALID_COMMAND;
case ui::VKEY_NEXT:
return control ? ui::TextEditCommand::SCROLL_PAGE_DOWN
: ui::TextEditCommand::INVALID_COMMAND;
default:
return ui::TextEditCommand::INVALID_COMMAND;
}
}
gfx::RenderText* Textfield::GetRenderTextForSelectionController() {
return GetRenderText();
}
bool Textfield::IsReadOnly() const {
return GetReadOnly();
}
bool Textfield::SupportsDrag() const {
return true;
}
void Textfield::SetTextBeingDragged(bool value) {
initiating_drag_ = value;
}
int Textfield::GetViewHeight() const {
return height();
}
int Textfield::GetViewWidth() const {
return width();
}
int Textfield::GetDragSelectionDelay() const {
if (ui::ScopedAnimationDurationScaleMode::duration_multiplier() ==
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION) {
return 1;
}
return ui::ScopedAnimationDurationScaleMode::duration_multiplier() * 100;
}
void Textfield::OnBeforePointerAction() {
OnBeforeUserAction();
if (model_->HasCompositionText())
model_->ConfirmCompositionText();
}
void Textfield::OnAfterPointerAction(bool text_changed,
bool selection_changed) {
OnAfterUserAction();
const auto text_change_type =
text_changed ? TextChangeType::kUserTriggered : TextChangeType::kNone;
UpdateAfterChange(text_change_type, selection_changed);
}
bool Textfield::PasteSelectionClipboard() {
DCHECK(performing_user_action_);
DCHECK(!GetReadOnly());
const std::u16string selection_clipboard_text = GetSelectionClipboardText();
if (selection_clipboard_text.empty())
return false;
model_->InsertText(selection_clipboard_text);
return true;
}
void Textfield::UpdateSelectionClipboard() {
if (ui::Clipboard::IsSupportedClipboardBuffer(
ui::ClipboardBuffer::kSelection)) {
if (text_input_type_ != ui::TEXT_INPUT_TYPE_PASSWORD) {
ui::ScopedClipboardWriter(ui::ClipboardBuffer::kSelection)
.WriteText(GetSelectedText());
if (controller_)
controller_->OnAfterCutOrCopy(ui::ClipboardBuffer::kSelection);
}
}
}
void Textfield::UpdateBackgroundColor() {
const SkColor color = GetBackgroundColor();
SetBackground(CreateBackgroundFromPainter(
Painter::CreateSolidRoundRectPainter(color, GetCornerRadius())));
GetRenderText()->set_subpixel_rendering_suppressed(SkColorGetA(color) !=
SK_AlphaOPAQUE);
OnPropertyChanged(&model_ + kTextfieldBackgroundColor, kPropertyEffectsPaint);
}
void Textfield::UpdateBorder() {
auto border = std::make_unique<views::FocusableBorder>();
const LayoutProvider* provider = LayoutProvider::Get();
border->SetColorId(ui::kColorTextfieldOutline);
border->SetInsets(gfx::Insets::TLBR(
extra_insets_.top() +
provider->GetDistanceMetric(DISTANCE_CONTROL_VERTICAL_TEXT_PADDING),
extra_insets_.left() + provider->GetDistanceMetric(
DISTANCE_TEXTFIELD_HORIZONTAL_TEXT_PADDING),
extra_insets_.bottom() +
provider->GetDistanceMetric(DISTANCE_CONTROL_VERTICAL_TEXT_PADDING),
extra_insets_.right() + provider->GetDistanceMetric(
DISTANCE_TEXTFIELD_HORIZONTAL_TEXT_PADDING)));
if (invalid_) {
border->SetColorId(ui::kColorTextfieldInvalidOutline);
} else if (!GetEnabled() || GetReadOnly()) {
border->SetColorId(ui::kColorTextfieldDisabledOutline);
}
border->SetCornerRadius(GetCornerRadius());
View::SetBorder(std::move(border));
}
void Textfield::UpdateSelectionTextColor() {
GetRenderText()->set_selection_color(GetSelectionTextColor());
OnPropertyChanged(&model_ + kTextfieldSelectionTextColor,
kPropertyEffectsPaint);
}
void Textfield::UpdateSelectionBackgroundColor() {
GetRenderText()->set_selection_background_focused_color(
GetSelectionBackgroundColor());
OnPropertyChanged(&model_ + kTextfieldSelectionBackgroundColor,
kPropertyEffectsPaint);
}
void Textfield::UpdateAfterChange(
TextChangeType text_change_type,
bool cursor_changed,
absl::optional<bool> notify_caret_bounds_changed) {
if (text_change_type != TextChangeType::kNone) {
if ((text_change_type == TextChangeType::kUserTriggered) && controller_)
controller_->ContentsChanged(this, GetText());
NotifyAccessibilityEvent(ax::mojom::Event::kValueChanged, true);
}
if (cursor_changed) {
UpdateCursorViewPosition();
UpdateCursorVisibility();
}
const bool anything_changed =
(text_change_type != TextChangeType::kNone) || cursor_changed;
if (notify_caret_bounds_changed.value_or(anything_changed))
OnCaretBoundsChanged();
if (anything_changed)
SchedulePaint();
}
void Textfield::UpdateCursorVisibility() {
cursor_view_->SetVisible(ShouldShowCursor());
#if BUILDFLAG(IS_MAC)
cursor_view_->layer()->SetOpacity(kOpaque);
#endif
if (ShouldBlinkCursor())
StartBlinkingCursor();
else
StopBlinkingCursor();
}
gfx::Rect Textfield::CalculateCursorViewBounds() const {
gfx::Rect location(GetRenderText()->GetUpdatedCursorBounds());
location.set_x(GetMirroredXForRect(location));
return location;
}
void Textfield::UpdateCursorViewPosition() {
cursor_view_->SetBoundsRect(CalculateCursorViewBounds());
}
int Textfield::GetTextStyle() const {
if (GetReadOnly() || !GetEnabled()) {
return style::STYLE_DISABLED;
} else if (GetInvalid()) {
return style::STYLE_INVALID;
} else {
return style::STYLE_PRIMARY;
}
}
void Textfield::PaintTextAndCursor(gfx::Canvas* canvas) {
TRACE_EVENT0("views", "Textfield::PaintTextAndCursor");
canvas->Save();
gfx::RenderText* render_text = GetRenderText();
if (ShouldShowPlaceholderText()) {
int placeholder_text_draw_flags = placeholder_text_draw_flags_;
if (SkColorGetA(GetBackgroundColor()) != SK_AlphaOPAQUE)
placeholder_text_draw_flags |= gfx::Canvas::NO_SUBPIXEL_RENDERING;
canvas->DrawStringRectWithFlags(
GetPlaceholderText(), placeholder_font_list_.value_or(GetFontList()),
placeholder_text_color_.value_or(
GetColorProvider()->GetColor(style::GetColorId(
style::CONTEXT_TEXTFIELD_PLACEHOLDER,
GetInvalid() ? style::STYLE_INVALID : style::STYLE_PRIMARY))),
render_text->display_rect(), placeholder_text_draw_flags);
}
const bool select_all = drop_cursor_visible_ && !IsDropCursorForInsertion();
render_text->Draw(canvas, select_all);
if (drop_cursor_visible_ && IsDropCursorForInsertion()) {
canvas->FillRect(render_text->GetCursorBounds(drop_cursor_position_, true),
GetTextColor());
}
canvas->Restore();
}
void Textfield::MoveCursorTo(const gfx::Point& point, bool select) {
if (model_->MoveCursorTo(point, select))
UpdateAfterChange(TextChangeType::kNone, true);
}
void Textfield::OnCaretBoundsChanged() {
if (GetInputMethod())
GetInputMethod()->OnCaretBoundsChanged(this);
if (touch_selection_controller_)
touch_selection_controller_->SelectionChanged();
if (HasFocus())
NotifyAccessibilityEvent(ax::mojom::Event::kTextSelectionChanged, true);
UpdateCursorViewPosition();
}
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 (!GetReadOnly() && text_input_type_ != ui::TEXT_INPUT_TYPE_PASSWORD &&
model_->Cut()) {
if (controller_)
controller_->OnAfterCutOrCopy(ui::ClipboardBuffer::kCopyPaste);
return true;
}
return false;
}
bool Textfield::Copy() {
if (text_input_type_ != ui::TEXT_INPUT_TYPE_PASSWORD && model_->Copy()) {
if (controller_)
controller_->OnAfterCutOrCopy(ui::ClipboardBuffer::kCopyPaste);
return true;
}
return false;
}
bool Textfield::Paste() {
if (!GetReadOnly() && model_->Paste()) {
if (controller_)
controller_->OnAfterPaste();
return true;
}
return false;
}
void Textfield::UpdateContextMenu() {
context_menu_runner_.reset();
context_menu_contents_.reset();
context_menu_contents_ = std::make_unique<ui::SimpleMenuModel>(this);
context_menu_contents_->AddItemWithStringId(kUndo, IDS_APP_UNDO);
context_menu_contents_->AddSeparator(ui::NORMAL_SEPARATOR);
context_menu_contents_->AddItemWithStringId(kCut, IDS_APP_CUT);
context_menu_contents_->AddItemWithStringId(kCopy, IDS_APP_COPY);
context_menu_contents_->AddItemWithStringId(kPaste, IDS_APP_PASTE);
context_menu_contents_->AddItemWithStringId(kDelete, IDS_APP_DELETE);
context_menu_contents_->AddSeparator(ui::NORMAL_SEPARATOR);
context_menu_contents_->AddItemWithStringId(kSelectAll, IDS_APP_SELECT_ALL);
if (controller_)
controller_->UpdateContextMenu(context_menu_contents_.get());
text_services_context_menu_ =
ViewsTextServicesContextMenu::Create(context_menu_contents_.get(), this);
context_menu_runner_ = std::make_unique<MenuRunner>(
context_menu_contents_.get(),
MenuRunner::HAS_MNEMONICS | MenuRunner::CONTEXT_MENU);
}
bool Textfield::ImeEditingAllowed() const {
ui::TextInputType t = GetTextInputType();
return (t != ui::TEXT_INPUT_TYPE_NONE && t != ui::TEXT_INPUT_TYPE_PASSWORD);
}
void Textfield::RevealPasswordChar(absl::optional<size_t> index,
base::TimeDelta duration) {
GetRenderText()->SetObscuredRevealIndex(index);
SchedulePaint();
password_char_reveal_index_ = index;
UpdateCursorViewPosition();
if (index.has_value()) {
password_reveal_timer_.Start(FROM_HERE, duration,
base::BindOnce(&Textfield::RevealPasswordChar,
weak_ptr_factory_.GetWeakPtr(),
absl::nullopt, duration));
}
}
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::OnEditFailed() {
PlatformStyle::OnTextfieldEditFailed();
}
bool Textfield::ShouldShowCursor() const {
return HasFocus() && !HasSelection(true) && GetEnabled() && !GetReadOnly() &&
!drop_cursor_visible_ && GetRenderText()->cursor_enabled() &&
GetLocalBounds().Contains(cursor_view_->bounds());
}
int Textfield::CharsToDips(int width_in_chars) const {
const int cursor_width =
(!GetReadOnly() && GetRenderText()->cursor_enabled()) ? 1 : 0;
return GetFontList().GetExpectedTextWidth(width_in_chars) + cursor_width +
GetInsets().width();
}
bool Textfield::ShouldBlinkCursor() const {
return ShouldShowCursor() && !Textfield::GetCaretBlinkInterval().is_zero();
}
void Textfield::StartBlinkingCursor() {
DCHECK(ShouldBlinkCursor());
cursor_blink_timer_.Start(FROM_HERE, Textfield::GetCaretBlinkInterval(), this,
&Textfield::OnCursorBlinkTimerFired);
}
void Textfield::StopBlinkingCursor() {
cursor_blink_timer_.Stop();
}
void Textfield::OnCursorBlinkTimerFired() {
DCHECK(ShouldBlinkCursor());
UpdateCursorViewPosition();
#if BUILDFLAG(IS_MAC)
float new_opacity = cursor_view_->layer()->opacity() != kOpaque
? kOpaque
: kAlmostTransparent;
cursor_view_->layer()->SetOpacity(new_opacity);
#else
cursor_view_->SetVisible(!cursor_view_->GetVisible());
#endif
}
void Textfield::OnEnabledChanged() {
if (GetInputMethod())
GetInputMethod()->OnTextInputTypeChanged(this);
}
void Textfield::DropDraggedText(
const ui::DropTargetEvent& event,
ui::mojom::DragOperation& output_drag_op,
std::unique_ptr<ui::LayerTreeOwner> drag_image_layer_owner) {
DCHECK(CanDrop(event.data()));
gfx::RenderText* render_text = GetRenderText();
OnBeforeUserAction();
skip_input_method_cancel_composition_ = true;
gfx::SelectionModel drop_destination_model =
render_text->FindCursorPosition(event.location());
std::u16string new_text;
event.data().GetString(&new_text);
const bool move = initiating_drag_ && !event.IsControlDown() &&
event.source_operations() & ui::DragDropTypes::DRAG_MOVE;
if (move) {
size_t pos = drop_destination_model.caret_pos();
pos -= render_text->selection().Intersect(gfx::Range(0, pos)).length();
model_->DeletePrimarySelectionAndInsertTextAt(new_text, pos);
} else {
model_->MoveCursorTo(drop_destination_model);
model_->InsertText(new_text);
}
skip_input_method_cancel_composition_ = false;
UpdateAfterChange(TextChangeType::kUserTriggered, true);
OnAfterUserAction();
output_drag_op = move ? DragOperation::kMove : DragOperation::kCopy;
}
float Textfield::GetCornerRadius() {
return LayoutProvider::Get()->GetCornerRadiusMetric(
ShapeContextTokens::kTextfieldRadius, size());
}
void Textfield::MaybeStartSelectionDragging(ui::GestureEvent* event) {
DCHECK_EQ(event->type(), ui::ET_GESTURE_SCROLL_BEGIN);
if (event->details().touch_points() > 1) {
selection_dragging_state_ = SelectionDraggingState::kNone;
return;
}
const float delta_x = event->details().scroll_x_hint();
const float delta_y = event->details().scroll_y_hint();
if (selection_dragging_state_ ==
SelectionDraggingState::kDraggingSelectionExtent) {
gfx::RenderText* render_text = GetRenderText();
gfx::SelectionModel start_sel =
render_text->GetSelectionModelForSelectionStart();
const gfx::SelectionModel& sel = render_text->selection_model();
gfx::Point selection_start =
render_text->GetCursorBounds(start_sel, true).CenterPoint();
gfx::Point selection_end =
render_text->GetCursorBounds(sel, true).CenterPoint();
gfx::LogicalCursorDirection drag_direction = gfx::CURSOR_FORWARD;
if (std::fabs(delta_y) > std::fabs(delta_x)) {
drag_direction = delta_y < 0 ? gfx::CURSOR_BACKWARD : gfx::CURSOR_FORWARD;
} else {
drag_direction = delta_x * (selection_end.x() - selection_start.x()) < 0
? gfx::CURSOR_BACKWARD
: gfx::CURSOR_FORWARD;
}
gfx::Point base =
drag_direction == gfx::CURSOR_FORWARD ? selection_start : selection_end;
gfx::Point extent =
drag_direction == gfx::CURSOR_FORWARD ? selection_end : selection_start;
SelectBetweenCoordinates(base, extent);
selection_dragging_offset_ = extent - event->location();
} else if (std::fabs(delta_x) >= std::fabs(delta_y)) {
selection_dragging_state_ = SelectionDraggingState::kDraggingCursor;
}
}
BEGIN_METADATA(Textfield, View)
ADD_PROPERTY_METADATA(bool, ReadOnly)
ADD_PROPERTY_METADATA(std::u16string, Text)
ADD_PROPERTY_METADATA(ui::TextInputType, TextInputType)
ADD_PROPERTY_METADATA(int, TextInputFlags)
ADD_PROPERTY_METADATA(SkColor, TextColor, ui::metadata::SkColorConverter)
ADD_PROPERTY_METADATA(SkColor,
SelectionTextColor,
ui::metadata::SkColorConverter)
ADD_PROPERTY_METADATA(SkColor, BackgroundColor, ui::metadata::SkColorConverter)
ADD_PROPERTY_METADATA(SkColor,
SelectionBackgroundColor,
ui::metadata::SkColorConverter)
ADD_PROPERTY_METADATA(bool, CursorEnabled)
ADD_PROPERTY_METADATA(std::u16string, PlaceholderText)
ADD_PROPERTY_METADATA(bool, Invalid)
ADD_PROPERTY_METADATA(gfx::HorizontalAlignment, HorizontalAlignment)
ADD_PROPERTY_METADATA(gfx::Range, SelectedRange)
END_METADATA
}