blob: d2fbb143179bdf44fa776d04f4d2e91639428127 [file] [log] [blame]
// Copyright (c) 2011 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 "views/controls/textfield/native_textfield_views.h"
#include <algorithm>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/message_loop.h"
#include "base/utf_string_conversions.h"
#include "grit/ui_strings.h"
#include "ui/base/clipboard/clipboard.h"
#include "ui/base/dragdrop/drag_drop_types.h"
#include "ui/base/range/range.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/insets.h"
#include "ui/gfx/render_text.h"
#include "views/background.h"
#include "views/border.h"
#include "views/controls/focusable_border.h"
#include "views/controls/menu/menu_item_view.h"
#include "views/controls/menu/menu_model_adapter.h"
#include "views/controls/menu/menu_runner.h"
#include "views/controls/textfield/textfield.h"
#include "views/controls/textfield/textfield_controller.h"
#include "views/controls/textfield/textfield_views_model.h"
#include "views/events/event.h"
#include "views/ime/input_method.h"
#include "views/metrics.h"
#include "views/views_delegate.h"
#include "views/widget/widget.h"
#if defined(OS_LINUX)
#include "ui/gfx/gtk_util.h"
#endif
#if defined(USE_AURA)
#include "ui/aura/cursor.h"
#endif
namespace {
// Text color for read only.
const SkColor kReadonlyTextColor = SK_ColorDKGRAY;
// Parameters to control cursor blinking.
const int kCursorVisibleTimeMs = 800;
const int kCursorInvisibleTimeMs = 500;
} // namespace
namespace views {
const char NativeTextfieldViews::kViewClassName[] =
"views/NativeTextfieldViews";
NativeTextfieldViews::NativeTextfieldViews(Textfield* parent)
: textfield_(parent),
ALLOW_THIS_IN_INITIALIZER_LIST(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),
ALLOW_THIS_IN_INITIALIZER_LIST(cursor_timer_(this)),
aggregated_clicks_(0),
last_click_time_(),
last_click_location_(),
ALLOW_THIS_IN_INITIALIZER_LIST(touch_selection_controller_(
TouchSelectionController::create(this))) {
set_border(text_border_);
// Lowercase is not supported.
DCHECK_NE(parent->style(), Textfield::STYLE_LOWERCASE);
// Set the default text style.
gfx::StyleRange default_style;
default_style.font = textfield_->font();
default_style.foreground = textfield_->text_color();
GetRenderText()->set_default_style(default_style);
GetRenderText()->ApplyDefaultStyle();
set_context_menu_controller(this);
set_drag_controller(this);
}
NativeTextfieldViews::~NativeTextfieldViews() {
}
////////////////////////////////////////////////////////////////////////////////
// NativeTextfieldViews, View overrides:
bool NativeTextfieldViews::OnMousePressed(const MouseEvent& event) {
OnBeforeUserAction();
textfield_->RequestFocus();
if (event.IsOnlyLeftMouseButton()) {
base::TimeDelta time_delta = event.time_stamp() - last_click_time_;
gfx::Point location_delta = event.location().Subtract(last_click_location_);
if (time_delta.InMilliseconds() <= GetDoubleClickInterval() &&
!ExceededDragThreshold(location_delta.x(), location_delta.y())) {
aggregated_clicks_ = (aggregated_clicks_ + 1) % 3;
} else {
aggregated_clicks_ = 0;
}
last_click_time_ = event.time_stamp();
last_click_location_ = event.location();
initiating_drag_ = false;
bool can_drag = true;
#if defined(TOUCH_UI)
can_drag = false;
#endif
switch(aggregated_clicks_) {
case 0:
if (can_drag && GetRenderText()->IsPointInSelection(event.location()))
initiating_drag_ = true;
else
MoveCursorTo(event.location(), event.IsShiftDown());
break;
case 1:
model_->SelectWord();
OnCaretBoundsChanged();
break;
case 2:
model_->SelectAll();
OnCaretBoundsChanged();
break;
default:
NOTREACHED();
}
SchedulePaint();
}
OnAfterUserAction();
return true;
}
bool NativeTextfieldViews::OnMouseDragged(const MouseEvent& event) {
// Don't adjust the cursor on a potential drag and drop.
if (initiating_drag_)
return true;
OnBeforeUserAction();
if (MoveCursorTo(event.location(), true))
SchedulePaint();
OnAfterUserAction();
return true;
}
void NativeTextfieldViews::OnMouseReleased(const MouseEvent& event) {
OnBeforeUserAction();
// Cancel suspected drag initiations, the user was clicking in the selection.
if (initiating_drag_ && MoveCursorTo(event.location(), false))
SchedulePaint();
initiating_drag_ = false;
OnAfterUserAction();
}
bool NativeTextfieldViews::OnKeyPressed(const 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 KeyEvent& event) {
NOTREACHED();
return false;
}
bool NativeTextfieldViews::GetDropFormats(
int* formats,
std::set<OSExchangeData::CustomFormat>* custom_formats) {
if (!textfield_->IsEnabled() || textfield_->read_only())
return false;
// TODO(msw): Can we support URL, FILENAME, etc.?
*formats = ui::OSExchangeData::STRING;
return true;
}
bool NativeTextfieldViews::CanDrop(const OSExchangeData& data) {
return textfield_->IsEnabled() && !textfield_->read_only() &&
data.HasString();
}
int NativeTextfieldViews::OnDragUpdated(const DropTargetEvent& event) {
DCHECK(CanDrop(event.data()));
bool in_selection = GetRenderText()->IsPointInSelection(event.location());
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;
}
int NativeTextfieldViews::OnPerformDrop(const DropTargetEvent& event) {
DCHECK(CanDrop(event.data()));
DCHECK(!initiating_drag_ ||
!GetRenderText()->IsPointInSelection(event.location()));
OnBeforeUserAction();
skip_input_method_cancel_composition_ = true;
// TODO(msw): Remove final reference to FindCursorPosition.
gfx::SelectionModel drop_destination =
GetRenderText()->FindCursorPosition(event.location());
string16 text;
event.data().GetString(&text);
// We'll delete the current selection for a drag and drop within this view.
bool move = initiating_drag_ && !event.IsControlDown() &&
event.source_operations() & ui::DragDropTypes::DRAG_MOVE;
if (move) {
gfx::SelectionModel selected;
model_->GetSelectionModel(&selected);
// Adjust the drop destination if it is on or after the current selection.
size_t max_of_selected_range = std::max(selected.selection_start(),
selected.selection_end());
size_t min_of_selected_range = std::min(selected.selection_start(),
selected.selection_end());
size_t selected_range_length = max_of_selected_range -
min_of_selected_range;
size_t drop_destination_end = drop_destination.selection_end();
if (max_of_selected_range <= drop_destination_end)
drop_destination_end -= selected_range_length;
else if (min_of_selected_range <= drop_destination_end)
drop_destination_end = min_of_selected_range;
model_->DeleteSelectionAndInsertTextAt(text, drop_destination_end);
} else {
model_->MoveCursorTo(drop_destination);
// 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::SelectRect(const gfx::Point& start,
const gfx::Point& end) {
if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE)
return;
gfx::SelectionModel start_pos = GetRenderText()->FindCursorPosition(start);
gfx::SelectionModel end_pos = GetRenderText()->FindCursorPosition(end);
OnBeforeUserAction();
// Merge selection models of "start_pos" and "end_pos" so that
// selection start is the value from "start_pos", while selection end,
// caret position, and caret placement are values from "end_pos".
if (start_pos.selection_start() == end_pos.selection_end())
model_->SelectSelectionModel(end_pos);
else
model_->SelectRange(ui::Range(start_pos.selection_start(),
end_pos.selection_end()));
OnCaretBoundsChanged();
SchedulePaint();
OnAfterUserAction();
}
gfx::NativeCursor NativeTextfieldViews::GetCursor(const 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 ? aura::kCursorIBeam : aura::kCursorNull;
#elif defined(OS_WIN)
static HCURSOR ibeam = LoadCursor(NULL, IDC_IBEAM);
static HCURSOR arrow = LoadCursor(NULL, IDC_ARROW);
return text_cursor ? ibeam : arrow;
#else
return text_cursor ? gfx::GetCursor(GDK_XTERM) : NULL;
#endif
}
/////////////////////////////////////////////////////////////////
// NativeTextfieldViews, ContextMenuController overrides:
void NativeTextfieldViews::ShowContextMenuForView(View* source,
const gfx::Point& p,
bool is_mouse_gesture) {
UpdateContextMenu();
if (context_menu_runner_->RunMenuAt(
GetWidget(), NULL, gfx::Rect(p, gfx::Size()),
views::MenuItemView::TOPLEFT, MenuRunner::HAS_MNEMONICS) ==
MenuRunner::MENU_DELETED)
return;
}
/////////////////////////////////////////////////////////////////
// NativeTextfieldViews, views::DragController overrides:
void NativeTextfieldViews::WriteDragDataForView(views::View* sender,
const gfx::Point& press_pt,
OSExchangeData* data) {
#if !defined(TOUCH_UI)
DCHECK_NE(ui::DragDropTypes::DRAG_NONE,
GetDragOperationsForView(sender, press_pt));
#endif
data->SetString(GetSelectedText());
}
int NativeTextfieldViews::GetDragOperationsForView(views::View* sender,
const gfx::Point& p) {
#if defined(TOUCH_UI)
return ui::DragDropTypes::DRAG_NONE;
#else
if (!textfield_->IsEnabled() || !GetRenderText()->IsPointInSelection(p))
return ui::DragDropTypes::DRAG_NONE;
if (sender == this && !textfield_->read_only())
return ui::DragDropTypes::DRAG_MOVE | ui::DragDropTypes::DRAG_COPY;
return ui::DragDropTypes::DRAG_COPY;
#endif
}
bool NativeTextfieldViews::CanStartDragForView(View* sender,
const gfx::Point& press_pt,
const gfx::Point& p) {
#if defined(TOUCH_UI)
return false;
#else
return GetRenderText()->IsPointInSelection(press_pt);
#endif
}
/////////////////////////////////////////////////////////////////
// NativeTextfieldViews, NativeTextifieldWrapper overrides:
string16 NativeTextfieldViews::GetText() const {
return model_->GetText();
}
void NativeTextfieldViews::UpdateText() {
model_->SetText(textfield_->text());
OnCaretBoundsChanged();
SchedulePaint();
}
void NativeTextfieldViews::AppendText(const string16& text) {
if (text.empty())
return;
model_->Append(text);
OnCaretBoundsChanged();
SchedulePaint();
}
string16 NativeTextfieldViews::GetSelectedText() const {
return model_->GetSelectedText();
}
void NativeTextfieldViews::SelectAll() {
OnBeforeUserAction();
model_->SelectAll();
OnCaretBoundsChanged();
SchedulePaint();
OnAfterUserAction();
}
void NativeTextfieldViews::ClearSelection() {
OnBeforeUserAction();
model_->ClearSelection();
OnCaretBoundsChanged();
SchedulePaint();
OnAfterUserAction();
}
void NativeTextfieldViews::UpdateBorder() {
if (textfield_->draw_border()) {
gfx::Insets insets = GetInsets();
textfield_->SetHorizontalMargins(insets.left(), insets.right());
textfield_->SetVerticalMargins(insets.top(), insets.bottom());
} else {
textfield_->SetHorizontalMargins(0, 0);
textfield_->SetVerticalMargins(0, 0);
}
}
void NativeTextfieldViews::UpdateTextColor() {
SchedulePaint();
}
void NativeTextfieldViews::UpdateBackgroundColor() {
// TODO(oshima): Background has to match the border's shape.
set_background(
Background::CreateSolidBackground(textfield_->background_color()));
SchedulePaint();
}
void NativeTextfieldViews::UpdateReadOnly() {
// Update the default text style.
gfx::StyleRange default_style(GetRenderText()->default_style());
default_style.foreground = textfield_->read_only() ? kReadonlyTextColor :
textfield_->text_color();
GetRenderText()->set_default_style(default_style);
GetRenderText()->ApplyDefaultStyle();
SchedulePaint();
OnTextInputTypeChanged();
}
void NativeTextfieldViews::UpdateFont() {
// Update the default text style.
gfx::StyleRange default_style(GetRenderText()->default_style());
default_style.font = textfield_->font();
GetRenderText()->set_default_style(default_style);
GetRenderText()->ApplyDefaultStyle();
OnCaretBoundsChanged();
}
void NativeTextfieldViews::UpdateIsPassword() {
model_->set_is_password(textfield_->IsPassword());
OnCaretBoundsChanged();
SchedulePaint();
OnTextInputTypeChanged();
}
void NativeTextfieldViews::UpdateEnabled() {
SetEnabled(textfield_->IsEnabled());
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);
OnCaretBoundsChanged();
}
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());
OnCaretBoundsChanged();
}
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();
}
void NativeTextfieldViews::GetSelectedRange(ui::Range* range) const {
model_->GetSelectedRange(range);
}
void NativeTextfieldViews::SelectRange(const ui::Range& range) {
model_->SelectRange(range);
OnCaretBoundsChanged();
SchedulePaint();
}
void NativeTextfieldViews::GetSelectionModel(gfx::SelectionModel* sel) const {
model_->GetSelectionModel(sel);
}
void NativeTextfieldViews::SelectSelectionModel(
const gfx::SelectionModel& sel) {
model_->SelectSelectionModel(sel);
OnCaretBoundsChanged();
SchedulePaint();
}
size_t NativeTextfieldViews::GetCursorPosition() const {
return model_->GetCursorPosition();
}
bool NativeTextfieldViews::HandleKeyPressed(const KeyEvent& e) {
TextfieldController* controller = textfield_->GetController();
bool handled = false;
if (controller)
handled = controller->HandleKeyEvent(textfield_, e);
return handled || HandleKeyEvent(e);
}
bool NativeTextfieldViews::HandleKeyReleased(const KeyEvent& e) {
return true;
}
void NativeTextfieldViews::HandleFocus() {
GetRenderText()->set_focused(true);
is_cursor_visible_ = true;
SchedulePaint();
OnCaretBoundsChanged();
// Start blinking cursor.
MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&NativeTextfieldViews::UpdateCursor,
cursor_timer_.GetWeakPtr()),
kCursorVisibleTimeMs);
}
void NativeTextfieldViews::HandleBlur() {
GetRenderText()->set_focused(false);
// Stop blinking cursor.
cursor_timer_.InvalidateWeakPtrs();
if (is_cursor_visible_) {
is_cursor_visible_ = false;
RepaintCursor();
}
if (touch_selection_controller_.get())
touch_selection_controller_->ClientViewLostFocus();
}
TextInputClient* NativeTextfieldViews::GetTextInputClient() {
return textfield_->read_only() ? NULL : this;
}
void NativeTextfieldViews::ClearEditHistory() {
model_->ClearEditHistory();
}
/////////////////////////////////////////////////////////////////
// NativeTextfieldViews, ui::SimpleMenuModel::Delegate overrides:
bool NativeTextfieldViews::IsCommandIdChecked(int command_id) const {
return true;
}
bool NativeTextfieldViews::IsCommandIdEnabled(int command_id) const {
bool editable = !textfield_->read_only();
string16 result;
switch (command_id) {
case IDS_APP_CUT:
return editable && model_->HasSelection();
case IDS_APP_COPY:
return model_->HasSelection();
case IDS_APP_PASTE:
ViewsDelegate::views_delegate->GetClipboard()
->ReadText(ui::Clipboard::BUFFER_STANDARD, &result);
return editable && !result.empty();
case IDS_APP_DELETE:
return editable && model_->HasSelection();
case IDS_APP_SELECT_ALL:
return true;
default:
NOTREACHED();
return false;
}
}
bool NativeTextfieldViews::GetAcceleratorForCommandId(int command_id,
ui::Accelerator* accelerator) {
return false;
}
void NativeTextfieldViews::ExecuteCommand(int command_id) {
bool text_changed = false;
bool editable = !textfield_->read_only();
OnBeforeUserAction();
switch (command_id) {
case IDS_APP_CUT:
if (editable)
text_changed = model_->Cut();
break;
case IDS_APP_COPY:
model_->Copy();
break;
case IDS_APP_PASTE:
if (editable)
text_changed = Paste();
break;
case IDS_APP_DELETE:
if (editable)
text_changed = model_->Delete();
break;
case IDS_APP_SELECT_ALL:
SelectAll();
break;
default:
NOTREACHED() << "unknown command: " << command_id;
break;
}
// The cursor must have changed if text changed during cut/paste/delete.
UpdateAfterChange(text_changed, text_changed);
OnAfterUserAction();
}
void NativeTextfieldViews::ApplyStyleRange(const gfx::StyleRange& style) {
GetRenderText()->ApplyStyleRange(style);
SchedulePaint();
}
void NativeTextfieldViews::ApplyDefaultStyle() {
GetRenderText()->ApplyDefaultStyle();
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, 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(text);
else
model_->ReplaceText(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;
UpdateAfterChange(true, true);
OnAfterUserAction();
}
ui::TextInputType NativeTextfieldViews::GetTextInputType() const {
return textfield_->GetTextInputType();
}
gfx::Rect NativeTextfieldViews::GetCaretBounds() {
return GetRenderText()->GetUpdatedCursorBounds();
}
bool NativeTextfieldViews::HasCompositionText() {
return model_->HasCompositionText();
}
bool NativeTextfieldViews::GetTextRange(ui::Range* range) {
// We don't allow the input method to retrieve or delete content from a
// password box.
if (GetTextInputType() != ui::TEXT_INPUT_TYPE_TEXT)
return false;
model_->GetTextRange(range);
return true;
}
bool NativeTextfieldViews::GetCompositionTextRange(ui::Range* range) {
if (GetTextInputType() != ui::TEXT_INPUT_TYPE_TEXT)
return false;
model_->GetCompositionTextRange(range);
return true;
}
bool NativeTextfieldViews::GetSelectionRange(ui::Range* range) {
if (GetTextInputType() != ui::TEXT_INPUT_TYPE_TEXT)
return false;
gfx::SelectionModel sel;
model_->GetSelectionModel(&sel);
range->set_start(sel.selection_start());
range->set_end(sel.selection_end());
return true;
}
bool NativeTextfieldViews::SetSelectionRange(const ui::Range& range) {
if (GetTextInputType() != ui::TEXT_INPUT_TYPE_TEXT || !range.IsValid())
return false;
OnBeforeUserAction();
SelectRange(range);
OnAfterUserAction();
return true;
}
bool NativeTextfieldViews::DeleteRange(const ui::Range& range) {
if (GetTextInputType() != ui::TEXT_INPUT_TYPE_TEXT || range.is_empty())
return false;
OnBeforeUserAction();
model_->SelectRange(range);
if (model_->HasSelection()) {
model_->DeleteSelection();
UpdateAfterChange(true, true);
}
OnAfterUserAction();
return true;
}
bool NativeTextfieldViews::GetTextFromRange(
const ui::Range& range,
const base::Callback<void(const string16&)>& callback) {
if (GetTextInputType() != ui::TEXT_INPUT_TYPE_TEXT || range.is_empty())
return false;
callback.Run(model_->GetTextFromRange(range));
return true;
}
void NativeTextfieldViews::OnInputMethodChanged() {
NOTIMPLEMENTED();
}
bool NativeTextfieldViews::ChangeTextDirectionAndLayoutAlignment(
base::i18n::TextDirection direction) {
NOTIMPLEMENTED();
return false;
}
View* NativeTextfieldViews::GetOwnerViewOfTextInputClient() {
return textfield_;
}
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();
}
void NativeTextfieldViews::UpdateCursor() {
is_cursor_visible_ = !is_cursor_visible_;
RepaintCursor();
MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&NativeTextfieldViews::UpdateCursor,
cursor_timer_.GetWeakPtr()),
is_cursor_visible_ ? kCursorVisibleTimeMs : kCursorInvisibleTimeMs);
}
void NativeTextfieldViews::RepaintCursor() {
gfx::Rect r(GetCaretBounds());
r.Inset(-1, -1, -1, -1);
SchedulePaintInRect(r);
}
void NativeTextfieldViews::PaintTextAndCursor(gfx::Canvas* canvas) {
canvas->Save();
GetRenderText()->set_cursor_visible(is_drop_cursor_visible_ ||
(is_cursor_visible_ && !model_->HasSelection()));
// Draw the text, cursor, and selection.
GetRenderText()->Draw(canvas);
canvas->Restore();
}
bool NativeTextfieldViews::HandleKeyEvent(const 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();
// TODO(oshima): shift-tab does not work. Figure out why and fix.
if (key_code == ui::VKEY_TAB)
return false;
OnBeforeUserAction();
bool editable = !textfield_->read_only();
bool selection = key_event.IsShiftDown();
bool control = key_event.IsControlDown();
bool text_changed = false;
bool cursor_changed = false;
switch (key_code) {
case ui::VKEY_Z:
if (control && editable)
cursor_changed = text_changed = model_->Undo();
break;
case ui::VKEY_Y:
if (control && editable)
cursor_changed = text_changed = model_->Redo();
break;
case ui::VKEY_A:
if (control) {
model_->SelectAll();
cursor_changed = true;
}
break;
case ui::VKEY_X:
if (control && editable)
cursor_changed = text_changed = model_->Cut();
break;
case ui::VKEY_C:
if (control)
model_->Copy();
break;
case ui::VKEY_V:
if (control && editable)
cursor_changed = text_changed = Paste();
break;
case ui::VKEY_RIGHT:
model_->MoveCursorRight(
control ? gfx::WORD_BREAK : gfx::CHARACTER_BREAK, selection);
cursor_changed = true;
break;
case ui::VKEY_LEFT:
model_->MoveCursorLeft(
control ? gfx::WORD_BREAK : gfx::CHARACTER_BREAK, selection);
cursor_changed = true;
break;
case ui::VKEY_END:
case ui::VKEY_HOME:
if ((key_code == ui::VKEY_HOME) ==
(GetRenderText()->GetTextDirection() == base::i18n::RIGHT_TO_LEFT))
model_->MoveCursorRight(gfx::LINE_BREAK, selection);
else
model_->MoveCursorLeft(gfx::LINE_BREAK, selection);
cursor_changed = true;
break;
case ui::VKEY_BACK:
if (!editable)
break;
if (!model_->HasSelection()) {
if (selection && control) {
// If both shift and control are pressed, then erase upto the
// beginning of the buffer in ChromeOS. In windows, do nothing.
#if defined(OS_WIN)
break;
#else
model_->MoveCursorLeft(gfx::LINE_BREAK, true);
#endif
} else if (control) {
// If only control is pressed, then erase the previous word.
model_->MoveCursorLeft(gfx::WORD_BREAK, true);
}
}
text_changed = model_->Backspace();
cursor_changed = true;
break;
case ui::VKEY_DELETE:
if (!editable)
break;
if (!model_->HasSelection()) {
if (selection && control) {
// If both shift and control are pressed, then erase upto the
// end of the buffer in ChromeOS. In windows, do nothing.
#if defined(OS_WIN)
break;
#else
model_->MoveCursorRight(gfx::LINE_BREAK, true);
#endif
} else if (control) {
// If only control is pressed, then erase the next word.
model_->MoveCursorRight(gfx::WORD_BREAK, true);
}
}
cursor_changed = text_changed = model_->Delete();
break;
case ui::VKEY_INSERT:
GetRenderText()->ToggleInsertMode();
cursor_changed = true;
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();
if (cursor_changed) {
is_cursor_visible_ = true;
RepaintCursor();
}
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_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();
context_menu_contents_->AddItemWithStringId(IDS_APP_SELECT_ALL,
IDS_APP_SELECT_ALL);
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())
return;
gfx::RenderText* render_text = GetRenderText();
const gfx::SelectionModel& sel = render_text->selection_model();
gfx::SelectionModel start_sel =
render_text->GetSelectionModelForSelectionStart();
gfx::Rect start_cursor = render_text->GetCursorBounds(start_sel, true);
gfx::Rect end_cursor = render_text->GetCursorBounds(sel, true);
gfx::Point start(start_cursor.x(), start_cursor.bottom() - 1);
gfx::Point end(end_cursor.x(), end_cursor.bottom() - 1);
touch_selection_controller_->SelectionChanged(start, end);
}
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::Paste() {
const bool success = model_->Paste();
// Calls TextfieldController::ContentsChanged() explicitly if the paste action
// did not change the content at all. See http://crbug.com/79002
if (success && GetText() == textfield_->text()) {
TextfieldController* controller = textfield_->GetController();
if (controller)
controller->ContentsChanged(textfield_, textfield_->text());
}
return success;
}
// 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;
}
#if defined(USE_AURA)
// static
NativeTextfieldWrapper* NativeTextfieldWrapper::CreateWrapper(
Textfield* field) {
return new NativeTextfieldViews(field);
}
#endif
} // namespace views