blob: 749de28a8c69b629ccf854184d66e767855c1f0c [file] [log] [blame]
// Copyright (c) 2013 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/native_textfield_views.h"
#include <algorithm>
#include <set>
#include "base/bind.h"
#include "base/debug/trace_event.h"
#include "base/i18n/case_conversion.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "grit/ui_strings.h"
#include "third_party/icu/source/common/unicode/uchar.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/base/clipboard/clipboard.h"
#include "ui/base/dragdrop/drag_drop_types.h"
#include "ui/base/dragdrop/drag_utils.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/ui_base_switches_util.h"
#include "ui/compositor/layer.h"
#include "ui/events/event.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/insets.h"
#include "ui/gfx/range/range.h"
#include "ui/gfx/render_text.h"
#include "ui/gfx/text_constants.h"
#include "ui/native_theme/native_theme.h"
#include "ui/views/background.h"
#include "ui/views/border.h"
#include "ui/views/controls/focusable_border.h"
#include "ui/views/controls/menu/menu_item_view.h"
#include "ui/views/controls/menu/menu_model_adapter.h"
#include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/controls/textfield/textfield_controller.h"
#include "ui/views/controls/textfield/textfield_views_model.h"
#include "ui/views/drag_utils.h"
#include "ui/views/ime/input_method.h"
#include "ui/views/metrics.h"
#include "ui/views/widget/widget.h"
#if defined(USE_AURA)
#include "ui/base/cursor/cursor.h"
#endif
#if defined(OS_WIN) && defined(USE_AURA)
#include "base/win/win_util.h"
#endif
namespace {
void ConvertRectToScreen(const views::View* src, gfx::Rect* r) {
DCHECK(src);
gfx::Point new_origin = r->origin();
views::View::ConvertPointToScreen(src, &new_origin);
r->set_origin(new_origin);
}
} // namespace
namespace views {
const char NativeTextfieldViews::kViewClassName[] =
"views/NativeTextfieldViews";
NativeTextfieldViews::NativeTextfieldViews(Textfield* parent)
: textfield_(parent),
model_(new TextfieldViewsModel(this)),
text_border_(new FocusableBorder()),
is_cursor_visible_(false),
is_drop_cursor_visible_(false),
skip_input_method_cancel_composition_(false),
initiating_drag_(false),
cursor_timer_(this),
aggregated_clicks_(0) {
set_border(text_border_);
GetRenderText()->SetFontList(textfield_->font_list());
UpdateColorsFromTheme(GetNativeTheme());
set_context_menu_controller(this);
parent->set_context_menu_controller(this);
set_drag_controller(this);
}
NativeTextfieldViews::~NativeTextfieldViews() {
}
////////////////////////////////////////////////////////////////////////////////
// NativeTextfieldViews, View overrides:
bool NativeTextfieldViews::OnMousePressed(const ui::MouseEvent& event) {
OnBeforeUserAction();
TrackMouseClicks(event);
TextfieldController* controller = textfield_->GetController();
if (!(controller && controller->HandleMouseEvent(textfield_, event)) &&
!textfield_->OnMousePressed(event)) {
HandleMousePressEvent(event);
}
OnAfterUserAction();
touch_selection_controller_.reset();
return true;
}
bool NativeTextfieldViews::ExceededDragThresholdFromLastClickLocation(
const ui::MouseEvent& event) {
return ExceededDragThreshold(event.location() - last_click_location_);
}
bool NativeTextfieldViews::OnMouseDragged(const ui::MouseEvent& event) {
// Don't adjust the cursor on a potential drag and drop, or if the mouse
// movement from the last mouse click does not exceed the drag threshold.
if (initiating_drag_ || !ExceededDragThresholdFromLastClickLocation(event) ||
!event.IsOnlyLeftMouseButton()) {
return true;
}
OnBeforeUserAction();
// TODO: Remove once NativeTextfield implementations are consolidated to
// Textfield.
if (!textfield_->OnMouseDragged(event)) {
MoveCursorTo(event.location(), true);
if (aggregated_clicks_ == 1) {
model_->SelectWord();
// Expand the selection so the initially selected word remains selected.
gfx::Range selection = GetRenderText()->selection();
const size_t min = std::min(selection.GetMin(),
double_click_word_.GetMin());
const size_t max = std::max(selection.GetMax(),
double_click_word_.GetMax());
const bool reversed = selection.is_reversed();
selection.set_start(reversed ? max : min);
selection.set_end(reversed ? min : max);
model_->SelectRange(selection);
}
SchedulePaint();
}
OnAfterUserAction();
return true;
}
void NativeTextfieldViews::OnMouseReleased(const ui::MouseEvent& event) {
OnBeforeUserAction();
// TODO: Remove once NativeTextfield implementations are consolidated to
// Textfield.
textfield_->OnMouseReleased(event);
// Cancel suspected drag initiations, the user was clicking in the selection.
if (initiating_drag_ && MoveCursorTo(event.location(), false))
SchedulePaint();
initiating_drag_ = false;
OnAfterUserAction();
}
void NativeTextfieldViews::OnGestureEvent(ui::GestureEvent* event) {
textfield_->OnGestureEvent(event);
if (event->handled())
return;
switch (event->type()) {
case ui::ET_GESTURE_TAP_DOWN:
OnBeforeUserAction();
textfield_->RequestFocus();
// We don't deselect if the point is in the selection
// because TAP_DOWN may turn into a LONG_PRESS.
if (!GetRenderText()->IsPointInSelection(event->location()) &&
MoveCursorTo(event->location(), false))
SchedulePaint();
OnAfterUserAction();
event->SetHandled();
break;
case ui::ET_GESTURE_SCROLL_UPDATE:
OnBeforeUserAction();
if (MoveCursorTo(event->location(), true))
SchedulePaint();
OnAfterUserAction();
event->SetHandled();
break;
case ui::ET_GESTURE_TAP:
if (event->details().tap_count() == 1) {
CreateTouchSelectionControllerAndNotifyIt();
} else {
OnBeforeUserAction();
SelectAll(false);
OnAfterUserAction();
event->SetHandled();
}
break;
case ui::ET_GESTURE_LONG_PRESS:
// If long press happens outside selection, select word and show context
// menu (If touch selection is enabled, context menu is shown by the
// |touch_selection_controller_|, hence we mark the event handled.
// Otherwise, the regular context menu will be shown by views).
// If long press happens in selected text and touch drag drop is enabled,
// we will turn off touch selection (if one exists) and let views do drag
// drop.
if (!GetRenderText()->IsPointInSelection(event->location())) {
OnBeforeUserAction();
model_->SelectWord();
touch_selection_controller_.reset(
ui::TouchSelectionController::create(this));
OnCaretBoundsChanged();
SchedulePaint();
OnAfterUserAction();
if (touch_selection_controller_.get())
event->SetHandled();
} else if (switches::IsTouchDragDropEnabled()) {
initiating_drag_ = true;
touch_selection_controller_.reset();
} else {
if (!touch_selection_controller_.get())
CreateTouchSelectionControllerAndNotifyIt();
if (touch_selection_controller_.get())
event->SetHandled();
}
return;
case ui::ET_GESTURE_LONG_TAP:
if (!touch_selection_controller_.get())
CreateTouchSelectionControllerAndNotifyIt();
// If touch selection is enabled, the context menu on long tap will be
// shown by the |touch_selection_controller_|, hence we mark the event
// handled so views does not try to show context menu on it.
if (touch_selection_controller_.get())
event->SetHandled();
break;
default:
View::OnGestureEvent(event);
return;
}
PlatformGestureEventHandling(event);
}
bool NativeTextfieldViews::OnKeyPressed(const ui::KeyEvent& event) {
// OnKeyPressed/OnKeyReleased/OnFocus/OnBlur will never be invoked on
// NativeTextfieldViews as it will never gain focus.
NOTREACHED();
return false;
}
bool NativeTextfieldViews::OnKeyReleased(const ui::KeyEvent& event) {
NOTREACHED();
return false;
}
bool NativeTextfieldViews::GetDropFormats(
int* formats,
std::set<OSExchangeData::CustomFormat>* custom_formats) {
if (!textfield_->enabled() || textfield_->read_only())
return false;
// TODO(msw): Can we support URL, FILENAME, etc.?
*formats = ui::OSExchangeData::STRING;
TextfieldController* controller = textfield_->GetController();
if (controller)
controller->AppendDropFormats(formats, custom_formats);
return true;
}
bool NativeTextfieldViews::CanDrop(const OSExchangeData& data) {
int formats;
std::set<OSExchangeData::CustomFormat> custom_formats;
GetDropFormats(&formats, &custom_formats);
return textfield_->enabled() && !textfield_->read_only() &&
data.HasAnyFormat(formats, custom_formats);
}
int NativeTextfieldViews::OnDragUpdated(const ui::DropTargetEvent& event) {
DCHECK(CanDrop(event.data()));
const gfx::Range& selection = GetRenderText()->selection();
drop_cursor_position_ = GetRenderText()->FindCursorPosition(event.location());
bool in_selection = !selection.is_empty() &&
selection.Contains(gfx::Range(drop_cursor_position_.caret_pos()));
is_drop_cursor_visible_ = !in_selection;
// TODO(msw): Pan over text when the user drags to the visible text edge.
OnCaretBoundsChanged();
SchedulePaint();
if (initiating_drag_) {
if (in_selection)
return ui::DragDropTypes::DRAG_NONE;
return event.IsControlDown() ? ui::DragDropTypes::DRAG_COPY :
ui::DragDropTypes::DRAG_MOVE;
}
return ui::DragDropTypes::DRAG_COPY | ui::DragDropTypes::DRAG_MOVE;
}
void NativeTextfieldViews::OnDragExited() {
is_drop_cursor_visible_ = false;
SchedulePaint();
}
int NativeTextfieldViews::OnPerformDrop(const ui::DropTargetEvent& event) {
DCHECK(CanDrop(event.data()));
is_drop_cursor_visible_ = false;
TextfieldController* controller = textfield_->GetController();
if (controller) {
int drag_operation = controller->OnDrop(event.data());
if (drag_operation != ui::DragDropTypes::DRAG_NONE)
return drag_operation;
}
DCHECK(!initiating_drag_ ||
!GetRenderText()->IsPointInSelection(event.location()));
OnBeforeUserAction();
skip_input_method_cancel_composition_ = true;
gfx::SelectionModel drop_destination_model =
GetRenderText()->FindCursorPosition(event.location());
string16 text;
event.data().GetString(&text);
text = GetTextForDisplay(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 drop = drop_destination_model.caret_pos();
drop -= GetSelectedRange().Intersect(gfx::Range(0, drop)).length();
model_->DeleteSelectionAndInsertTextAt(text, drop);
} else {
model_->MoveCursorTo(drop_destination_model);
// Drop always inserts text even if the textfield is not in insert mode.
model_->InsertText(text);
}
skip_input_method_cancel_composition_ = false;
UpdateAfterChange(true, true);
OnAfterUserAction();
return move ? ui::DragDropTypes::DRAG_MOVE : ui::DragDropTypes::DRAG_COPY;
}
void NativeTextfieldViews::OnDragDone() {
initiating_drag_ = false;
is_drop_cursor_visible_ = false;
}
void NativeTextfieldViews::OnPaint(gfx::Canvas* canvas) {
text_border_->set_has_focus(textfield_->HasFocus());
OnPaintBackground(canvas);
PaintTextAndCursor(canvas);
if (textfield_->draw_border())
OnPaintBorder(canvas);
}
void NativeTextfieldViews::OnFocus() {
NOTREACHED();
}
void NativeTextfieldViews::OnBlur() {
NOTREACHED();
}
void NativeTextfieldViews::OnNativeThemeChanged(const ui::NativeTheme* theme) {
UpdateColorsFromTheme(theme);
}
void NativeTextfieldViews::SelectRect(const gfx::Point& start,
const gfx::Point& end) {
if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE)
return;
gfx::SelectionModel start_caret = GetRenderText()->FindCursorPosition(start);
gfx::SelectionModel end_caret = GetRenderText()->FindCursorPosition(end);
gfx::SelectionModel selection(
gfx::Range(start_caret.caret_pos(), end_caret.caret_pos()),
end_caret.caret_affinity());
OnBeforeUserAction();
model_->SelectSelectionModel(selection);
OnCaretBoundsChanged();
SchedulePaint();
OnAfterUserAction();
}
void NativeTextfieldViews::MoveCaretTo(const gfx::Point& point) {
SelectRect(point, point);
}
void NativeTextfieldViews::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 NativeTextfieldViews::GetBounds() {
return bounds();
}
gfx::NativeView NativeTextfieldViews::GetNativeView() {
return GetWidget()->GetNativeView();
}
void NativeTextfieldViews::ConvertPointToScreen(gfx::Point* point) {
View::ConvertPointToScreen(this, point);
}
void NativeTextfieldViews::ConvertPointFromScreen(gfx::Point* point) {
View::ConvertPointFromScreen(this, point);
}
bool NativeTextfieldViews::DrawsHandles() {
return false;
}
void NativeTextfieldViews::OpenContextMenu(const gfx::Point& anchor) {
touch_selection_controller_.reset();
ShowContextMenu(anchor, ui::MENU_SOURCE_TOUCH_EDIT_MENU);
}
gfx::NativeCursor NativeTextfieldViews::GetCursor(const ui::MouseEvent& event) {
bool in_selection = GetRenderText()->IsPointInSelection(event.location());
bool drag_event = event.type() == ui::ET_MOUSE_DRAGGED;
bool text_cursor = !initiating_drag_ && (drag_event || !in_selection);
#if defined(USE_AURA)
return text_cursor ? ui::kCursorIBeam : ui::kCursorNull;
#elif defined(OS_WIN)
static HCURSOR ibeam = LoadCursor(NULL, IDC_IBEAM);
static HCURSOR arrow = LoadCursor(NULL, IDC_ARROW);
return text_cursor ? ibeam : arrow;
#endif
}
/////////////////////////////////////////////////////////////////
// NativeTextfieldViews, ContextMenuController overrides:
void NativeTextfieldViews::ShowContextMenuForView(
View* source,
const gfx::Point& point,
ui::MenuSourceType source_type) {
UpdateContextMenu();
if (context_menu_runner_->RunMenuAt(GetWidget(), NULL,
gfx::Rect(point, gfx::Size()), views::MenuItemView::TOPLEFT,
source_type,
MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU) ==
MenuRunner::MENU_DELETED)
return;
}
/////////////////////////////////////////////////////////////////
// NativeTextfieldViews, views::DragController overrides:
void NativeTextfieldViews::WriteDragDataForView(views::View* sender,
const gfx::Point& press_pt,
OSExchangeData* data) {
DCHECK_NE(ui::DragDropTypes::DRAG_NONE,
GetDragOperationsForView(sender, press_pt));
data->SetString(GetSelectedText());
scoped_ptr<gfx::Canvas> canvas(
views::GetCanvasForDragImage(textfield_->GetWidget(), size()));
GetRenderText()->DrawSelectedTextForDrag(canvas.get());
drag_utils::SetDragImageOnDataObject(*canvas, size(),
press_pt.OffsetFromOrigin(),
data);
TextfieldController* controller = textfield_->GetController();
if (controller)
controller->OnWriteDragData(data);
}
int NativeTextfieldViews::GetDragOperationsForView(views::View* sender,
const gfx::Point& p) {
int drag_operations = ui::DragDropTypes::DRAG_COPY;
if (!textfield_->enabled() || textfield_->IsObscured() ||
!GetRenderText()->IsPointInSelection(p))
drag_operations = ui::DragDropTypes::DRAG_NONE;
else if (sender == this && !textfield_->read_only())
drag_operations =
ui::DragDropTypes::DRAG_MOVE | ui::DragDropTypes::DRAG_COPY;
TextfieldController* controller = textfield_->GetController();
if (controller)
controller->OnGetDragOperationsForTextfield(&drag_operations);
return drag_operations;
}
bool NativeTextfieldViews::CanStartDragForView(View* sender,
const gfx::Point& press_pt,
const gfx::Point& p) {
return initiating_drag_ && GetRenderText()->IsPointInSelection(press_pt);
}
/////////////////////////////////////////////////////////////////
// NativeTextfieldViews, NativeTextifieldWrapper overrides:
string16 NativeTextfieldViews::GetText() const {
return model_->GetText();
}
void NativeTextfieldViews::UpdateText() {
model_->SetText(GetTextForDisplay(textfield_->text()));
OnCaretBoundsChanged();
SchedulePaint();
textfield_->NotifyAccessibilityEvent(
ui::AccessibilityTypes::EVENT_TEXT_CHANGED, true);
}
void NativeTextfieldViews::AppendText(const string16& text) {
if (text.empty())
return;
model_->Append(GetTextForDisplay(text));
OnCaretBoundsChanged();
SchedulePaint();
}
void NativeTextfieldViews::InsertOrReplaceText(const string16& text) {
if (text.empty())
return;
model_->InsertText(text);
OnCaretBoundsChanged();
SchedulePaint();
}
base::i18n::TextDirection NativeTextfieldViews::GetTextDirection() const {
return GetRenderText()->GetTextDirection();
}
string16 NativeTextfieldViews::GetSelectedText() const {
return model_->GetSelectedText();
}
void NativeTextfieldViews::SelectAll(bool reversed) {
model_->SelectAll(reversed);
OnCaretBoundsChanged();
SchedulePaint();
}
void NativeTextfieldViews::ClearSelection() {
model_->ClearSelection();
OnCaretBoundsChanged();
SchedulePaint();
}
void NativeTextfieldViews::UpdateBorder() {
// By default, if a caller calls Textfield::RemoveBorder() and does not set
// any explicit margins, they should get zero margins. But also call
// UpdateXXXMargins() so we respect any explicitly-set margins.
//
// NOTE: If someday Textfield supports toggling |draw_border_| back on, we'll
// need to update this conditional to set the insets to their default values.
if (!textfield_->draw_border())
text_border_->SetInsets(0, 0, 0, 0);
UpdateHorizontalMargins();
UpdateVerticalMargins();
}
void NativeTextfieldViews::UpdateTextColor() {
SetColor(textfield_->GetTextColor());
}
void NativeTextfieldViews::UpdateBackgroundColor() {
const SkColor color = textfield_->GetBackgroundColor();
set_background(Background::CreateSolidBackground(color));
GetRenderText()->set_background_is_transparent(SkColorGetA(color) != 0xFF);
SchedulePaint();
}
void NativeTextfieldViews::UpdateReadOnly() {
OnTextInputTypeChanged();
}
void NativeTextfieldViews::UpdateFont() {
GetRenderText()->SetFontList(textfield_->font_list());
OnCaretBoundsChanged();
}
void NativeTextfieldViews::UpdateIsObscured() {
GetRenderText()->SetObscured(textfield_->IsObscured());
OnCaretBoundsChanged();
SchedulePaint();
OnTextInputTypeChanged();
}
void NativeTextfieldViews::UpdateEnabled() {
SetEnabled(textfield_->enabled());
SchedulePaint();
OnTextInputTypeChanged();
}
gfx::Insets NativeTextfieldViews::CalculateInsets() {
return GetInsets();
}
void NativeTextfieldViews::UpdateHorizontalMargins() {
int left, right;
if (!textfield_->GetHorizontalMargins(&left, &right))
return;
gfx::Insets inset = GetInsets();
text_border_->SetInsets(inset.top(), left, inset.bottom(), right);
OnBoundsChanged(GetBounds());
}
void NativeTextfieldViews::UpdateVerticalMargins() {
int top, bottom;
if (!textfield_->GetVerticalMargins(&top, &bottom))
return;
gfx::Insets inset = GetInsets();
text_border_->SetInsets(top, inset.left(), bottom, inset.right());
OnBoundsChanged(GetBounds());
}
bool NativeTextfieldViews::SetFocus() {
return false;
}
View* NativeTextfieldViews::GetView() {
return this;
}
gfx::NativeView NativeTextfieldViews::GetTestingHandle() const {
NOTREACHED();
return NULL;
}
bool NativeTextfieldViews::IsIMEComposing() const {
return model_->HasCompositionText();
}
gfx::Range NativeTextfieldViews::GetSelectedRange() const {
return GetRenderText()->selection();
}
void NativeTextfieldViews::SelectRange(const gfx::Range& range) {
model_->SelectRange(range);
OnCaretBoundsChanged();
SchedulePaint();
textfield_->NotifyAccessibilityEvent(
ui::AccessibilityTypes::EVENT_SELECTION_CHANGED, true);
}
gfx::SelectionModel NativeTextfieldViews::GetSelectionModel() const {
return GetRenderText()->selection_model();
}
void NativeTextfieldViews::SelectSelectionModel(
const gfx::SelectionModel& sel) {
model_->SelectSelectionModel(sel);
OnCaretBoundsChanged();
SchedulePaint();
}
size_t NativeTextfieldViews::GetCursorPosition() const {
return model_->GetCursorPosition();
}
bool NativeTextfieldViews::GetCursorEnabled() const {
return GetRenderText()->cursor_enabled();
}
void NativeTextfieldViews::SetCursorEnabled(bool enabled) {
GetRenderText()->SetCursorEnabled(enabled);
}
bool NativeTextfieldViews::HandleKeyPressed(const ui::KeyEvent& e) {
TextfieldController* controller = textfield_->GetController();
bool handled = false;
if (controller)
handled = controller->HandleKeyEvent(textfield_, e);
touch_selection_controller_.reset();
return handled || HandleKeyEvent(e);
}
bool NativeTextfieldViews::HandleKeyReleased(const ui::KeyEvent& e) {
return false; // crbug.com/127520
}
void NativeTextfieldViews::HandleFocus() {
GetRenderText()->set_focused(true);
is_cursor_visible_ = true;
SchedulePaint();
GetInputMethod()->OnFocus();
OnCaretBoundsChanged();
const size_t caret_blink_ms = Textfield::GetCaretBlinkMs();
if (caret_blink_ms != 0) {
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&NativeTextfieldViews::UpdateCursor,
cursor_timer_.GetWeakPtr()),
base::TimeDelta::FromMilliseconds(caret_blink_ms));
}
}
void NativeTextfieldViews::HandleBlur() {
GetRenderText()->set_focused(false);
GetInputMethod()->OnBlur();
// Stop blinking cursor.
cursor_timer_.InvalidateWeakPtrs();
if (is_cursor_visible_) {
is_cursor_visible_ = false;
RepaintCursor();
}
touch_selection_controller_.reset();
}
ui::TextInputClient* NativeTextfieldViews::GetTextInputClient() {
return textfield_->read_only() ? NULL : this;
}
void NativeTextfieldViews::ClearEditHistory() {
model_->ClearEditHistory();
}
int NativeTextfieldViews::GetFontHeight() {
return GetRenderText()->font_list().GetHeight();
}
int NativeTextfieldViews::GetTextfieldBaseline() const {
return GetRenderText()->GetBaseline();
}
int NativeTextfieldViews::GetWidthNeededForText() const {
return GetRenderText()->GetContentWidth() + GetInsets().width();
}
void NativeTextfieldViews::ExecuteTextCommand(int command_id) {
ExecuteCommand(command_id, 0);
}
bool NativeTextfieldViews::HasTextBeingDragged() {
return initiating_drag_;
}
gfx::Point NativeTextfieldViews::GetContextMenuLocation() {
return GetCaretBounds().bottom_right();
}
/////////////////////////////////////////////////////////////////
// NativeTextfieldViews, ui::SimpleMenuModel::Delegate overrides:
bool NativeTextfieldViews::IsCommandIdChecked(int command_id) const {
return true;
}
bool NativeTextfieldViews::IsCommandIdEnabled(int command_id) const {
TextfieldController* controller = textfield_->GetController();
if (controller && controller->HandlesCommand(command_id))
return controller->IsCommandIdEnabled(command_id);
bool editable = !textfield_->read_only();
string16 result;
switch (command_id) {
case IDS_APP_UNDO:
return editable && model_->CanUndo();
case IDS_APP_CUT:
return editable && model_->HasSelection() && !textfield_->IsObscured();
case IDS_APP_COPY:
return model_->HasSelection() && !textfield_->IsObscured();
case IDS_APP_PASTE:
ui::Clipboard::GetForCurrentThread()->ReadText(
ui::CLIPBOARD_TYPE_COPY_PASTE, &result);
return editable && !result.empty();
case IDS_APP_DELETE:
return editable && model_->HasSelection();
case IDS_APP_SELECT_ALL:
return !model_->GetText().empty();
default:
return controller->IsCommandIdEnabled(command_id);
}
}
bool NativeTextfieldViews::GetAcceleratorForCommandId(int command_id,
ui::Accelerator* accelerator) {
return false;
}
bool NativeTextfieldViews::IsItemForCommandIdDynamic(int command_id) const {
const TextfieldController* controller = textfield_->GetController();
return controller && controller->IsItemForCommandIdDynamic(command_id);
}
string16 NativeTextfieldViews::GetLabelForCommandId(int command_id) const {
const TextfieldController* controller = textfield_->GetController();
return controller ? controller->GetLabelForCommandId(command_id) : string16();
}
void NativeTextfieldViews::ExecuteCommand(int command_id, int event_flags) {
touch_selection_controller_.reset();
if (!IsCommandIdEnabled(command_id))
return;
TextfieldController* controller = textfield_->GetController();
if (controller && controller->HandlesCommand(command_id)) {
controller->ExecuteCommand(command_id, 0);
} else {
bool text_changed = false;
switch (command_id) {
case IDS_APP_UNDO:
OnBeforeUserAction();
text_changed = model_->Undo();
UpdateAfterChange(text_changed, text_changed);
OnAfterUserAction();
break;
case IDS_APP_CUT:
OnBeforeUserAction();
text_changed = Cut();
UpdateAfterChange(text_changed, text_changed);
OnAfterUserAction();
break;
case IDS_APP_COPY:
OnBeforeUserAction();
Copy();
OnAfterUserAction();
break;
case IDS_APP_PASTE:
OnBeforeUserAction();
text_changed = Paste();
UpdateAfterChange(text_changed, text_changed);
OnAfterUserAction();
break;
case IDS_APP_DELETE:
OnBeforeUserAction();
text_changed = model_->Delete();
UpdateAfterChange(text_changed, text_changed);
OnAfterUserAction();
break;
case IDS_APP_SELECT_ALL:
OnBeforeUserAction();
SelectAll(false);
UpdateAfterChange(false, true);
OnAfterUserAction();
break;
default:
controller->ExecuteCommand(command_id, 0);
break;
}
}
}
void NativeTextfieldViews::SetColor(SkColor value) {
GetRenderText()->SetColor(value);
SchedulePaint();
}
void NativeTextfieldViews::ApplyColor(SkColor value, const gfx::Range& range) {
GetRenderText()->ApplyColor(value, range);
SchedulePaint();
}
void NativeTextfieldViews::SetStyle(gfx::TextStyle style, bool value) {
GetRenderText()->SetStyle(style, value);
SchedulePaint();
}
void NativeTextfieldViews::ApplyStyle(gfx::TextStyle style,
bool value,
const gfx::Range& range) {
GetRenderText()->ApplyStyle(style, value, range);
SchedulePaint();
}
void NativeTextfieldViews::OnBoundsChanged(const gfx::Rect& previous_bounds) {
// Set the RenderText display area.
gfx::Insets insets = GetInsets();
gfx::Rect display_rect(insets.left(),
insets.top(),
width() - insets.width(),
height() - insets.height());
GetRenderText()->SetDisplayRect(display_rect);
OnCaretBoundsChanged();
}
///////////////////////////////////////////////////////////////////////////////
// NativeTextfieldViews, ui::TextInputClient implementation, private:
void NativeTextfieldViews::SetCompositionText(
const ui::CompositionText& composition) {
if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE)
return;
OnBeforeUserAction();
skip_input_method_cancel_composition_ = true;
model_->SetCompositionText(composition);
skip_input_method_cancel_composition_ = false;
UpdateAfterChange(true, true);
OnAfterUserAction();
}
void NativeTextfieldViews::ConfirmCompositionText() {
if (!model_->HasCompositionText())
return;
OnBeforeUserAction();
skip_input_method_cancel_composition_ = true;
model_->ConfirmCompositionText();
skip_input_method_cancel_composition_ = false;
UpdateAfterChange(true, true);
OnAfterUserAction();
}
void NativeTextfieldViews::ClearCompositionText() {
if (!model_->HasCompositionText())
return;
OnBeforeUserAction();
skip_input_method_cancel_composition_ = true;
model_->CancelCompositionText();
skip_input_method_cancel_composition_ = false;
UpdateAfterChange(true, true);
OnAfterUserAction();
}
void NativeTextfieldViews::InsertText(const string16& text) {
// TODO(suzhe): Filter invalid characters.
if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE || text.empty())
return;
OnBeforeUserAction();
skip_input_method_cancel_composition_ = true;
if (GetRenderText()->insert_mode())
model_->InsertText(GetTextForDisplay(text));
else
model_->ReplaceText(GetTextForDisplay(text));
skip_input_method_cancel_composition_ = false;
UpdateAfterChange(true, true);
OnAfterUserAction();
}
void NativeTextfieldViews::InsertChar(char16 ch, int flags) {
if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE ||
!ShouldInsertChar(ch, flags)) {
return;
}
OnBeforeUserAction();
skip_input_method_cancel_composition_ = true;
if (GetRenderText()->insert_mode())
model_->InsertChar(ch);
else
model_->ReplaceChar(ch);
skip_input_method_cancel_composition_ = false;
model_->SetText(GetTextForDisplay(GetText()));
UpdateAfterChange(true, true);
OnAfterUserAction();
if (textfield_->IsObscured()) {
const base::TimeDelta& reveal_duration =
textfield_->obscured_reveal_duration();
if (reveal_duration != base::TimeDelta()) {
const size_t change_offset = model_->GetCursorPosition();
DCHECK_GT(change_offset, 0u);
RevealObscuredChar(change_offset - 1, reveal_duration);
}
}
}
gfx::NativeWindow NativeTextfieldViews::GetAttachedWindow() const {
// Imagine the following hierarchy.
// [NativeWidget A] - FocusManager
// [View]
// [NativeWidget B]
// [View]
// [View X]
// An important thing is that [NativeWidget A] owns Win32 input focus even
// when [View X] is logically focused by FocusManager. As a result, an Win32
// IME may want to interact with the native view of [NativeWidget A] rather
// than that of [NativeWidget B]. This is why we need to call
// GetTopLevelWidget() here.
return GetWidget()->GetTopLevelWidget()->GetNativeView();
}
ui::TextInputType NativeTextfieldViews::GetTextInputType() const {
return textfield_->GetTextInputType();
}
ui::TextInputMode NativeTextfieldViews::GetTextInputMode() const {
return ui::TEXT_INPUT_MODE_DEFAULT;
}
bool NativeTextfieldViews::CanComposeInline() const {
return true;
}
gfx::Rect NativeTextfieldViews::GetCaretBounds() const {
// TextInputClient::GetCaretBounds is expected to return a value in screen
// coordinates.
gfx::Rect rect = GetRenderText()->GetUpdatedCursorBounds();
ConvertRectToScreen(this, &rect);
return rect;
}
bool NativeTextfieldViews::GetCompositionCharacterBounds(
uint32 index,
gfx::Rect* rect) const {
DCHECK(rect);
if (!HasCompositionText())
return false;
const gfx::Range& composition_range = GetRenderText()->GetCompositionRange();
const uint32 left_cursor_pos = composition_range.start() + index;
const uint32 right_cursor_pos = composition_range.start() + index + 1;
DCHECK(!composition_range.is_empty());
if (composition_range.end() < right_cursor_pos)
return false;
const gfx::SelectionModel start_position(left_cursor_pos,
gfx::CURSOR_BACKWARD);
const gfx::SelectionModel end_position(right_cursor_pos,
gfx::CURSOR_BACKWARD);
gfx::Rect start_cursor = GetRenderText()->GetCursorBounds(start_position,
false);
gfx::Rect end_cursor = GetRenderText()->GetCursorBounds(end_position, false);
// TextInputClient::GetCompositionCharacterBounds is expected to fill |rect|
// in screen coordinates and GetCaretBounds returns screen coordinates.
*rect = gfx::Rect(start_cursor.x(),
start_cursor.y(),
end_cursor.x() - start_cursor.x(),
start_cursor.height());
ConvertRectToScreen(this, rect);
return true;
}
bool NativeTextfieldViews::HasCompositionText() const {
return model_->HasCompositionText();
}
bool NativeTextfieldViews::GetTextRange(gfx::Range* range) const {
if (!ImeEditingAllowed())
return false;
model_->GetTextRange(range);
return true;
}
bool NativeTextfieldViews::GetCompositionTextRange(gfx::Range* range) const {
if (!ImeEditingAllowed())
return false;
model_->GetCompositionTextRange(range);
return true;
}
bool NativeTextfieldViews::GetSelectionRange(gfx::Range* range) const {
if (!ImeEditingAllowed())
return false;
*range = GetSelectedRange();
return true;
}
bool NativeTextfieldViews::SetSelectionRange(const gfx::Range& range) {
if (!ImeEditingAllowed() || !range.IsValid())
return false;
OnBeforeUserAction();
SelectRange(range);
OnAfterUserAction();
return true;
}
bool NativeTextfieldViews::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 NativeTextfieldViews::GetTextFromRange(
const gfx::Range& range,
string16* text) const {
if (!ImeEditingAllowed() || !range.IsValid())
return false;
gfx::Range text_range;
if (!GetTextRange(&text_range) || !text_range.Contains(range))
return false;
*text = model_->GetTextFromRange(range);
return true;
}
void NativeTextfieldViews::OnInputMethodChanged() {
// TODO(msw): NOTIMPLEMENTED(); see http://crbug.com/140402
}
bool NativeTextfieldViews::ChangeTextDirectionAndLayoutAlignment(
base::i18n::TextDirection direction) {
NOTIMPLEMENTED();
return false;
}
void NativeTextfieldViews::ExtendSelectionAndDelete(
size_t before,
size_t after) {
gfx::Range range = GetSelectedRange();
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 NativeTextfieldViews::EnsureCaretInRect(const gfx::Rect& rect) {
}
void NativeTextfieldViews::OnCompositionTextConfirmedOrCleared() {
if (skip_input_method_cancel_composition_)
return;
DCHECK(textfield_->GetInputMethod());
textfield_->GetInputMethod()->CancelComposition(textfield_);
}
gfx::RenderText* NativeTextfieldViews::GetRenderText() const {
return model_->render_text();
}
string16 NativeTextfieldViews::GetTextForDisplay(const string16& text) {
return textfield_->style() & Textfield::STYLE_LOWERCASE ?
base::i18n::ToLower(text) : text;
}
void NativeTextfieldViews::UpdateColorsFromTheme(const ui::NativeTheme* theme) {
UpdateTextColor();
UpdateBackgroundColor();
gfx::RenderText* render_text = GetRenderText();
render_text->set_cursor_color(textfield_->GetTextColor());
render_text->set_selection_color(theme->GetSystemColor(
ui::NativeTheme::kColorId_TextfieldSelectionColor));
render_text->set_selection_background_focused_color(theme->GetSystemColor(
ui::NativeTheme::kColorId_TextfieldSelectionBackgroundFocused));
}
void NativeTextfieldViews::UpdateCursor() {
const size_t caret_blink_ms = Textfield::GetCaretBlinkMs();
is_cursor_visible_ = !is_cursor_visible_ || (caret_blink_ms == 0);
RepaintCursor();
if (caret_blink_ms != 0) {
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&NativeTextfieldViews::UpdateCursor,
cursor_timer_.GetWeakPtr()),
base::TimeDelta::FromMilliseconds(caret_blink_ms));
}
}
void NativeTextfieldViews::RepaintCursor() {
gfx::Rect r(GetRenderText()->GetUpdatedCursorBounds());
r.Inset(-1, -1, -1, -1);
SchedulePaintInRect(r);
}
void NativeTextfieldViews::PaintTextAndCursor(gfx::Canvas* canvas) {
TRACE_EVENT0("views", "NativeTextfieldViews::PaintTextAndCursor");
canvas->Save();
GetRenderText()->set_cursor_visible(!is_drop_cursor_visible_ &&
is_cursor_visible_ && !model_->HasSelection());
// Draw the text, cursor, and selection.
GetRenderText()->Draw(canvas);
// Draw the detached drop cursor that marks where the text will be dropped.
if (is_drop_cursor_visible_)
GetRenderText()->DrawCursor(canvas, drop_cursor_position_);
// Draw placeholder text if needed.
if (model_->GetText().empty() &&
!textfield_->GetPlaceholderText().empty()) {
canvas->DrawStringInt(
textfield_->GetPlaceholderText(),
GetRenderText()->GetPrimaryFont(),
textfield_->placeholder_text_color(),
GetRenderText()->display_rect());
}
canvas->Restore();
}
bool NativeTextfieldViews::HandleKeyEvent(const ui::KeyEvent& key_event) {
// TODO(oshima): Refactor and consolidate with ExecuteCommand.
if (key_event.type() == ui::ET_KEY_PRESSED) {
ui::KeyboardCode key_code = key_event.key_code();
if (key_code == ui::VKEY_TAB || key_event.IsUnicodeKeyCode())
return false;
OnBeforeUserAction();
const bool editable = !textfield_->read_only();
const bool readable = !textfield_->IsObscured();
const bool shift = key_event.IsShiftDown();
const bool control = key_event.IsControlDown();
const bool alt = key_event.IsAltDown() || key_event.IsAltGrDown();
bool text_changed = false;
bool cursor_changed = false;
switch (key_code) {
case ui::VKEY_Z:
if (control && !shift && !alt && editable)
cursor_changed = text_changed = model_->Undo();
else if (control && shift && !alt && editable)
cursor_changed = text_changed = model_->Redo();
break;
case ui::VKEY_Y:
if (control && !alt && editable)
cursor_changed = text_changed = model_->Redo();
break;
case ui::VKEY_A:
if (control && !alt) {
model_->SelectAll(false);
cursor_changed = true;
}
break;
case ui::VKEY_X:
if (control && !alt && editable && readable)
cursor_changed = text_changed = Cut();
break;
case ui::VKEY_C:
if (control && !alt && readable)
Copy();
break;
case ui::VKEY_V:
if (control && !alt && editable)
cursor_changed = text_changed = Paste();
break;
case ui::VKEY_RIGHT:
case ui::VKEY_LEFT: {
// We should ignore the alt-left/right keys because alt key doesn't make
// any special effects for them and they can be shortcut keys such like
// forward/back of the browser history.
if (alt)
break;
const gfx::Range selection_range = GetSelectedRange();
model_->MoveCursor(
control ? gfx::WORD_BREAK : gfx::CHARACTER_BREAK,
(key_code == ui::VKEY_RIGHT) ? gfx::CURSOR_RIGHT : gfx::CURSOR_LEFT,
shift);
cursor_changed = GetSelectedRange() != selection_range;
break;
}
case ui::VKEY_END:
case ui::VKEY_HOME:
if ((key_code == ui::VKEY_HOME) ==
(GetRenderText()->GetTextDirection() == base::i18n::RIGHT_TO_LEFT))
model_->MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_RIGHT, shift);
else
model_->MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_LEFT, shift);
cursor_changed = true;
break;
case ui::VKEY_BACK:
case ui::VKEY_DELETE:
if (!editable)
break;
if (!model_->HasSelection()) {
gfx::VisualCursorDirection direction = (key_code == ui::VKEY_DELETE) ?
gfx::CURSOR_RIGHT : gfx::CURSOR_LEFT;
if (shift && control) {
// If both shift and control are pressed, then erase up to the
// beginning/end of the buffer in ChromeOS. In windows, do nothing.
#if defined(OS_WIN)
break;
#else
model_->MoveCursor(gfx::LINE_BREAK, direction, true);
#endif
} else if (control) {
// If only control is pressed, then erase the previous/next word.
model_->MoveCursor(gfx::WORD_BREAK, direction, true);
}
}
if (key_code == ui::VKEY_BACK)
model_->Backspace();
else if (shift && model_->HasSelection() && readable)
Cut();
else
model_->Delete();
// Consume backspace and delete keys even if the edit did nothing. This
// prevents potential unintended side-effects of further event handling.
text_changed = true;
break;
case ui::VKEY_INSERT:
if (control && !shift && readable)
Copy();
else if (shift && !control && editable)
cursor_changed = text_changed = Paste();
break;
default:
break;
}
// We must have input method in order to support text input.
DCHECK(textfield_->GetInputMethod());
UpdateAfterChange(text_changed, cursor_changed);
OnAfterUserAction();
return (text_changed || cursor_changed);
}
return false;
}
bool NativeTextfieldViews::MoveCursorTo(const gfx::Point& point, bool select) {
if (!model_->MoveCursorTo(point, select))
return false;
OnCaretBoundsChanged();
return true;
}
void NativeTextfieldViews::PropagateTextChange() {
textfield_->SyncText();
}
void NativeTextfieldViews::UpdateAfterChange(bool text_changed,
bool cursor_changed) {
if (text_changed) {
PropagateTextChange();
textfield_->NotifyAccessibilityEvent(
ui::AccessibilityTypes::EVENT_TEXT_CHANGED, true);
}
if (cursor_changed) {
is_cursor_visible_ = true;
RepaintCursor();
if (!text_changed) {
// TEXT_CHANGED implies SELECTION_CHANGED, so we only need to fire
// this if only the selection changed.
textfield_->NotifyAccessibilityEvent(
ui::AccessibilityTypes::EVENT_SELECTION_CHANGED, true);
}
}
if (text_changed || cursor_changed) {
OnCaretBoundsChanged();
SchedulePaint();
}
}
void NativeTextfieldViews::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);
TextfieldController* controller = textfield_->GetController();
if (controller)
controller->UpdateContextMenu(context_menu_contents_.get());
context_menu_delegate_.reset(
new views::MenuModelAdapter(context_menu_contents_.get()));
context_menu_runner_.reset(
new MenuRunner(new views::MenuItemView(context_menu_delegate_.get())));
}
context_menu_delegate_->BuildMenu(context_menu_runner_->GetMenu());
}
void NativeTextfieldViews::OnTextInputTypeChanged() {
// TODO(suzhe): changed from DCHECK. See http://crbug.com/81320.
if (textfield_->GetInputMethod())
textfield_->GetInputMethod()->OnTextInputTypeChanged(textfield_);
}
void NativeTextfieldViews::OnCaretBoundsChanged() {
// TODO(suzhe): changed from DCHECK. See http://crbug.com/81320.
if (textfield_->GetInputMethod())
textfield_->GetInputMethod()->OnCaretBoundsChanged(textfield_);
// Notify selection controller
if (touch_selection_controller_.get())
touch_selection_controller_->SelectionChanged();
}
void NativeTextfieldViews::OnBeforeUserAction() {
TextfieldController* controller = textfield_->GetController();
if (controller)
controller->OnBeforeUserAction(textfield_);
}
void NativeTextfieldViews::OnAfterUserAction() {
TextfieldController* controller = textfield_->GetController();
if (controller)
controller->OnAfterUserAction(textfield_);
}
bool NativeTextfieldViews::Cut() {
if (!textfield_->read_only() && !textfield_->IsObscured() && model_->Cut()) {
TextfieldController* controller = textfield_->GetController();
if (controller)
controller->OnAfterCutOrCopy();
return true;
}
return false;
}
bool NativeTextfieldViews::Copy() {
if (!textfield_->IsObscured() && model_->Copy()) {
TextfieldController* controller = textfield_->GetController();
if (controller)
controller->OnAfterCutOrCopy();
return true;
}
return false;
}
bool NativeTextfieldViews::Paste() {
if (textfield_->read_only())
return false;
const string16 original_text = GetText();
const bool success = model_->Paste();
if (success) {
// As Paste is handled in model_->Paste(), the RenderText may contain
// upper case characters. This is not consistent with other places
// which keeps RenderText only containing lower case characters.
string16 new_text = GetTextForDisplay(GetText());
model_->SetText(new_text);
TextfieldController* controller = textfield_->GetController();
if (controller)
controller->OnAfterPaste();
}
return success;
}
void NativeTextfieldViews::TrackMouseClicks(const ui::MouseEvent& event) {
if (event.IsOnlyLeftMouseButton()) {
base::TimeDelta time_delta = event.time_stamp() - last_click_time_;
if (time_delta.InMilliseconds() <= GetDoubleClickInterval() &&
!ExceededDragThresholdFromLastClickLocation(event)) {
// 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();
}
}
void NativeTextfieldViews::HandleMousePressEvent(const ui::MouseEvent& event) {
if (event.IsOnlyLeftMouseButton() || event.IsOnlyRightMouseButton())
textfield_->RequestFocus();
if (!event.IsOnlyLeftMouseButton())
return;
initiating_drag_ = false;
bool can_drag = true;
switch (aggregated_clicks_) {
case 0:
if (can_drag && GetRenderText()->IsPointInSelection(event.location()))
initiating_drag_ = true;
else
MoveCursorTo(event.location(), event.IsShiftDown());
break;
case 1:
MoveCursorTo(event.location(), false);
model_->SelectWord();
double_click_word_ = GetRenderText()->selection();
OnCaretBoundsChanged();
break;
case 2:
model_->SelectAll(false);
OnCaretBoundsChanged();
break;
default:
NOTREACHED();
}
SchedulePaint();
}
bool NativeTextfieldViews::ImeEditingAllowed() const {
// We don't allow the input method to retrieve or delete content from a
// password field.
ui::TextInputType t = GetTextInputType();
return (t != ui::TEXT_INPUT_TYPE_NONE && t != ui::TEXT_INPUT_TYPE_PASSWORD);
}
// static
bool NativeTextfieldViews::ShouldInsertChar(char16 ch, int flags) {
// Filter out all control characters, including tab and new line characters,
// and all characters with Alt modifier. But we need to allow characters with
// AltGr modifier.
// On Windows AltGr is represented by Alt+Ctrl, and on Linux it's a different
// flag that we don't care about.
return ((ch >= 0x20 && ch < 0x7F) || ch > 0x9F) &&
(flags & ~(ui::EF_SHIFT_DOWN | ui::EF_CAPS_LOCK_DOWN)) != ui::EF_ALT_DOWN;
}
void NativeTextfieldViews::CreateTouchSelectionControllerAndNotifyIt() {
if (!touch_selection_controller_) {
touch_selection_controller_.reset(
ui::TouchSelectionController::create(this));
}
if (touch_selection_controller_)
touch_selection_controller_->SelectionChanged();
}
void NativeTextfieldViews::PlatformGestureEventHandling(
const ui::GestureEvent* event) {
#if defined(OS_WIN) && defined(USE_AURA)
if (event->type() == ui::ET_GESTURE_TAP && !textfield_->read_only())
base::win::DisplayVirtualKeyboard();
#endif
}
void NativeTextfieldViews::RevealObscuredChar(int index,
const base::TimeDelta& duration) {
GetRenderText()->SetObscuredRevealIndex(index);
SchedulePaint();
if (index != -1) {
obscured_reveal_timer_.Start(
FROM_HERE,
duration,
base::Bind(&NativeTextfieldViews::RevealObscuredChar,
base::Unretained(this), -1, base::TimeDelta()));
}
}
} // namespace views