1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "ui/views/controls/textfield/textfield.h"
9 #include "base/debug/trace_event.h"
10 #include "ui/accessibility/ax_view_state.h"
11 #include "ui/base/clipboard/scoped_clipboard_writer.h"
12 #include "ui/base/cursor/cursor.h"
13 #include "ui/base/dragdrop/drag_drop_types.h"
14 #include "ui/base/dragdrop/drag_utils.h"
15 #include "ui/base/ui_base_switches_util.h"
16 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
17 #include "ui/events/event.h"
18 #include "ui/events/keycodes/keyboard_codes.h"
19 #include "ui/gfx/canvas.h"
20 #include "ui/gfx/display.h"
21 #include "ui/gfx/insets.h"
22 #include "ui/gfx/screen.h"
23 #include "ui/native_theme/native_theme.h"
24 #include "ui/strings/grit/ui_strings.h"
25 #include "ui/views/background.h"
26 #include "ui/views/controls/focusable_border.h"
27 #include "ui/views/controls/label.h"
28 #include "ui/views/controls/menu/menu_runner.h"
29 #include "ui/views/controls/native/native_view_host.h"
30 #include "ui/views/controls/textfield/textfield_controller.h"
31 #include "ui/views/drag_utils.h"
32 #include "ui/views/ime/input_method.h"
33 #include "ui/views/metrics.h"
34 #include "ui/views/native_cursor.h"
35 #include "ui/views/painter.h"
36 #include "ui/views/views_delegate.h"
37 #include "ui/views/widget/widget.h"
40 #include "base/win/win_util.h"
43 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
44 #include "base/strings/utf_string_conversions.h"
45 #include "ui/events/linux/text_edit_command_auralinux.h"
46 #include "ui/events/linux/text_edit_key_bindings_delegate_auralinux.h"
53 // Default placeholder text color.
54 const SkColor kDefaultPlaceholderTextColor
= SK_ColorLTGRAY
;
56 const int kNoCommand
= 0;
58 void ConvertRectToScreen(const View
* src
, gfx::Rect
* r
) {
61 gfx::Point new_origin
= r
->origin();
62 View::ConvertPointToScreen(src
, &new_origin
);
63 r
->set_origin(new_origin
);
66 // Get the drag selection timer delay, respecting animation scaling for testing.
67 int GetDragSelectionDelay() {
68 switch (ui::ScopedAnimationDurationScaleMode::duration_scale_mode()) {
69 case ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION
: return 100;
70 case ui::ScopedAnimationDurationScaleMode::FAST_DURATION
: return 25;
71 case ui::ScopedAnimationDurationScaleMode::SLOW_DURATION
: return 400;
72 case ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION
: return 1;
73 case ui::ScopedAnimationDurationScaleMode::ZERO_DURATION
: return 0;
78 // Get the default command for a given key |event| and selection state.
79 int GetCommandForKeyEvent(const ui::KeyEvent
& event
, bool has_selection
) {
80 if (event
.type() != ui::ET_KEY_PRESSED
|| event
.IsUnicodeKeyCode())
83 const bool shift
= event
.IsShiftDown();
84 const bool control
= event
.IsControlDown();
85 const bool alt
= event
.IsAltDown() || event
.IsAltGrDown();
86 switch (event
.key_code()) {
88 if (control
&& !shift
&& !alt
)
90 return (control
&& shift
&& !alt
) ? IDS_APP_REDO
: kNoCommand
;
92 return (control
&& !alt
) ? IDS_APP_REDO
: kNoCommand
;
94 return (control
&& !alt
) ? IDS_APP_SELECT_ALL
: kNoCommand
;
96 return (control
&& !alt
) ? IDS_APP_CUT
: kNoCommand
;
98 return (control
&& !alt
) ? IDS_APP_COPY
: kNoCommand
;
100 return (control
&& !alt
) ? IDS_APP_PASTE
: kNoCommand
;
102 // Ignore alt+right, which may be a browser navigation shortcut.
106 return control
? IDS_MOVE_WORD_RIGHT
: IDS_MOVE_RIGHT
;
107 return control
? IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION
:
108 IDS_MOVE_RIGHT_AND_MODIFY_SELECTION
;
110 // Ignore alt+left, which may be a browser navigation shortcut.
114 return control
? IDS_MOVE_WORD_LEFT
: IDS_MOVE_LEFT
;
115 return control
? IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION
:
116 IDS_MOVE_LEFT_AND_MODIFY_SELECTION
;
118 return shift
? IDS_MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION
:
119 IDS_MOVE_TO_BEGINNING_OF_LINE
;
121 return shift
? IDS_MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION
:
122 IDS_MOVE_TO_END_OF_LINE
;
124 if (!control
|| has_selection
)
125 return IDS_DELETE_BACKWARD
;
126 #if defined(OS_LINUX)
127 // Only erase by line break on Linux and ChromeOS.
129 return IDS_DELETE_TO_BEGINNING_OF_LINE
;
131 return IDS_DELETE_WORD_BACKWARD
;
132 case ui::VKEY_DELETE
:
133 if (!control
|| has_selection
)
134 return (shift
&& has_selection
) ? IDS_APP_CUT
: IDS_DELETE_FORWARD
;
135 #if defined(OS_LINUX)
136 // Only erase by line break on Linux and ChromeOS.
138 return IDS_DELETE_TO_END_OF_LINE
;
140 return IDS_DELETE_WORD_FORWARD
;
141 case ui::VKEY_INSERT
:
142 if (control
&& !shift
)
144 return (shift
&& !control
) ? IDS_APP_PASTE
: kNoCommand
;
150 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
151 // Convert a custom text edit |command| to the equivalent views command ID.
152 int GetViewsCommand(const ui::TextEditCommandAuraLinux
& command
, bool rtl
) {
153 const bool select
= command
.extend_selection();
154 switch (command
.command_id()) {
155 case ui::TextEditCommandAuraLinux::COPY
:
157 case ui::TextEditCommandAuraLinux::CUT
:
159 case ui::TextEditCommandAuraLinux::DELETE_BACKWARD
:
160 return IDS_DELETE_BACKWARD
;
161 case ui::TextEditCommandAuraLinux::DELETE_FORWARD
:
162 return IDS_DELETE_FORWARD
;
163 case ui::TextEditCommandAuraLinux::DELETE_TO_BEGINING_OF_LINE
:
164 case ui::TextEditCommandAuraLinux::DELETE_TO_BEGINING_OF_PARAGRAPH
:
165 return IDS_DELETE_TO_BEGINNING_OF_LINE
;
166 case ui::TextEditCommandAuraLinux::DELETE_TO_END_OF_LINE
:
167 case ui::TextEditCommandAuraLinux::DELETE_TO_END_OF_PARAGRAPH
:
168 return IDS_DELETE_TO_END_OF_LINE
;
169 case ui::TextEditCommandAuraLinux::DELETE_WORD_BACKWARD
:
170 return IDS_DELETE_WORD_BACKWARD
;
171 case ui::TextEditCommandAuraLinux::DELETE_WORD_FORWARD
:
172 return IDS_DELETE_WORD_FORWARD
;
173 case ui::TextEditCommandAuraLinux::INSERT_TEXT
:
175 case ui::TextEditCommandAuraLinux::MOVE_BACKWARD
:
177 return select
? IDS_MOVE_RIGHT_AND_MODIFY_SELECTION
: IDS_MOVE_RIGHT
;
178 return select
? IDS_MOVE_LEFT_AND_MODIFY_SELECTION
: IDS_MOVE_LEFT
;
179 case ui::TextEditCommandAuraLinux::MOVE_DOWN
:
180 return IDS_MOVE_DOWN
;
181 case ui::TextEditCommandAuraLinux::MOVE_FORWARD
:
183 return select
? IDS_MOVE_LEFT_AND_MODIFY_SELECTION
: IDS_MOVE_LEFT
;
184 return select
? IDS_MOVE_RIGHT_AND_MODIFY_SELECTION
: IDS_MOVE_RIGHT
;
185 case ui::TextEditCommandAuraLinux::MOVE_LEFT
:
186 return select
? IDS_MOVE_LEFT_AND_MODIFY_SELECTION
: IDS_MOVE_LEFT
;
187 case ui::TextEditCommandAuraLinux::MOVE_PAGE_DOWN
:
188 case ui::TextEditCommandAuraLinux::MOVE_PAGE_UP
:
190 case ui::TextEditCommandAuraLinux::MOVE_RIGHT
:
191 return select
? IDS_MOVE_RIGHT_AND_MODIFY_SELECTION
: IDS_MOVE_RIGHT
;
192 case ui::TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_DOCUMENT
:
193 case ui::TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_LINE
:
194 case ui::TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_PARAGRAPH
:
195 return select
? IDS_MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION
:
196 IDS_MOVE_TO_BEGINNING_OF_LINE
;
197 case ui::TextEditCommandAuraLinux::MOVE_TO_END_OF_DOCUMENT
:
198 case ui::TextEditCommandAuraLinux::MOVE_TO_END_OF_LINE
:
199 case ui::TextEditCommandAuraLinux::MOVE_TO_END_OF_PARAGRAPH
:
200 return select
? IDS_MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION
:
201 IDS_MOVE_TO_END_OF_LINE
;
202 case ui::TextEditCommandAuraLinux::MOVE_UP
:
204 case ui::TextEditCommandAuraLinux::MOVE_WORD_BACKWARD
:
206 return select
? IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION
:
209 return select
? IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION
:
211 case ui::TextEditCommandAuraLinux::MOVE_WORD_FORWARD
:
213 return select
? IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION
:
216 return select
? IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION
:
218 case ui::TextEditCommandAuraLinux::MOVE_WORD_LEFT
:
219 return select
? IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION
:
221 case ui::TextEditCommandAuraLinux::MOVE_WORD_RIGHT
:
222 return select
? IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION
:
224 case ui::TextEditCommandAuraLinux::PASTE
:
225 return IDS_APP_PASTE
;
226 case ui::TextEditCommandAuraLinux::SELECT_ALL
:
227 return IDS_APP_SELECT_ALL
;
228 case ui::TextEditCommandAuraLinux::SET_MARK
:
229 case ui::TextEditCommandAuraLinux::UNSELECT
:
230 case ui::TextEditCommandAuraLinux::INVALID_COMMAND
:
240 const char Textfield::kViewClassName
[] = "Textfield";
241 const int Textfield::kTextPadding
= 3;
244 size_t Textfield::GetCaretBlinkMs() {
245 static const size_t default_value
= 500;
247 static const size_t system_value
= ::GetCaretBlinkTime();
248 if (system_value
!= 0)
249 return (system_value
== INFINITE
) ? 0 : system_value
;
251 return default_value
;
254 Textfield::Textfield()
255 : model_(new TextfieldModel(this)),
258 default_width_in_chars_(0),
259 use_default_text_color_(true),
260 use_default_background_color_(true),
261 use_default_selection_text_color_(true),
262 use_default_selection_background_color_(true),
263 text_color_(SK_ColorBLACK
),
264 background_color_(SK_ColorWHITE
),
265 selection_text_color_(SK_ColorWHITE
),
266 selection_background_color_(SK_ColorBLUE
),
267 placeholder_text_color_(kDefaultPlaceholderTextColor
),
268 text_input_type_(ui::TEXT_INPUT_TYPE_TEXT
),
269 performing_user_action_(false),
270 skip_input_method_cancel_composition_(false),
271 cursor_visible_(false),
272 drop_cursor_visible_(false),
273 initiating_drag_(false),
274 aggregated_clicks_(0),
275 drag_start_display_offset_(0),
276 touch_handles_hidden_due_to_scroll_(false),
277 weak_ptr_factory_(this) {
278 set_context_menu_controller(this);
279 set_drag_controller(this);
280 SetBorder(scoped_ptr
<Border
>(new FocusableBorder()));
283 if (ViewsDelegate::views_delegate
) {
284 password_reveal_duration_
= ViewsDelegate::views_delegate
->
285 GetDefaultTextfieldObscuredRevealDuration();
289 Textfield::~Textfield() {}
291 void Textfield::SetReadOnly(bool read_only
) {
292 // Update read-only without changing the focusable state (or active, etc.).
293 read_only_
= read_only
;
294 if (GetInputMethod())
295 GetInputMethod()->OnTextInputTypeChanged(this);
296 SetColor(GetTextColor());
297 UpdateBackgroundColor();
300 void Textfield::SetTextInputType(ui::TextInputType type
) {
301 GetRenderText()->SetObscured(type
== ui::TEXT_INPUT_TYPE_PASSWORD
);
302 text_input_type_
= type
;
303 OnCaretBoundsChanged();
304 if (GetInputMethod())
305 GetInputMethod()->OnTextInputTypeChanged(this);
309 void Textfield::SetText(const base::string16
& new_text
) {
310 model_
->SetText(new_text
);
311 OnCaretBoundsChanged();
313 NotifyAccessibilityEvent(ui::AX_EVENT_TEXT_CHANGED
, true);
316 void Textfield::AppendText(const base::string16
& new_text
) {
317 if (new_text
.empty())
319 model_
->Append(new_text
);
320 OnCaretBoundsChanged();
324 void Textfield::InsertOrReplaceText(const base::string16
& new_text
) {
325 if (new_text
.empty())
327 model_
->InsertText(new_text
);
328 OnCaretBoundsChanged();
332 base::i18n::TextDirection
Textfield::GetTextDirection() const {
333 return GetRenderText()->GetTextDirection();
336 base::string16
Textfield::GetSelectedText() const {
337 return model_
->GetSelectedText();
340 void Textfield::SelectAll(bool reversed
) {
341 model_
->SelectAll(reversed
);
342 UpdateSelectionClipboard();
343 UpdateAfterChange(false, true);
346 void Textfield::SelectWordAt(const gfx::Point
& point
) {
347 model_
->MoveCursorTo(point
, false);
348 model_
->SelectWord();
349 UpdateAfterChange(false, true);
352 void Textfield::ClearSelection() {
353 model_
->ClearSelection();
354 UpdateAfterChange(false, true);
357 bool Textfield::HasSelection() const {
358 return !GetSelectedRange().is_empty();
361 SkColor
Textfield::GetTextColor() const {
362 if (!use_default_text_color_
)
365 return GetNativeTheme()->GetSystemColor(read_only() ?
366 ui::NativeTheme::kColorId_TextfieldReadOnlyColor
:
367 ui::NativeTheme::kColorId_TextfieldDefaultColor
);
370 void Textfield::SetTextColor(SkColor color
) {
372 use_default_text_color_
= false;
376 void Textfield::UseDefaultTextColor() {
377 use_default_text_color_
= true;
378 SetColor(GetTextColor());
381 SkColor
Textfield::GetBackgroundColor() const {
382 if (!use_default_background_color_
)
383 return background_color_
;
385 return GetNativeTheme()->GetSystemColor(read_only() ?
386 ui::NativeTheme::kColorId_TextfieldReadOnlyBackground
:
387 ui::NativeTheme::kColorId_TextfieldDefaultBackground
);
390 void Textfield::SetBackgroundColor(SkColor color
) {
391 background_color_
= color
;
392 use_default_background_color_
= false;
393 UpdateBackgroundColor();
396 void Textfield::UseDefaultBackgroundColor() {
397 use_default_background_color_
= true;
398 UpdateBackgroundColor();
401 SkColor
Textfield::GetSelectionTextColor() const {
402 return use_default_selection_text_color_
?
403 GetNativeTheme()->GetSystemColor(
404 ui::NativeTheme::kColorId_TextfieldSelectionColor
) :
405 selection_text_color_
;
408 void Textfield::SetSelectionTextColor(SkColor color
) {
409 selection_text_color_
= color
;
410 use_default_selection_text_color_
= false;
411 GetRenderText()->set_selection_color(GetSelectionTextColor());
415 void Textfield::UseDefaultSelectionTextColor() {
416 use_default_selection_text_color_
= true;
417 GetRenderText()->set_selection_color(GetSelectionTextColor());
421 void Textfield::SetShadows(const gfx::ShadowValues
& shadows
) {
422 GetRenderText()->set_shadows(shadows
);
426 SkColor
Textfield::GetSelectionBackgroundColor() const {
427 return use_default_selection_background_color_
?
428 GetNativeTheme()->GetSystemColor(
429 ui::NativeTheme::kColorId_TextfieldSelectionBackgroundFocused
) :
430 selection_background_color_
;
433 void Textfield::SetSelectionBackgroundColor(SkColor color
) {
434 selection_background_color_
= color
;
435 use_default_selection_background_color_
= false;
436 GetRenderText()->set_selection_background_focused_color(
437 GetSelectionBackgroundColor());
441 void Textfield::UseDefaultSelectionBackgroundColor() {
442 use_default_selection_background_color_
= true;
443 GetRenderText()->set_selection_background_focused_color(
444 GetSelectionBackgroundColor());
448 bool Textfield::GetCursorEnabled() const {
449 return GetRenderText()->cursor_enabled();
452 void Textfield::SetCursorEnabled(bool enabled
) {
453 GetRenderText()->SetCursorEnabled(enabled
);
456 const gfx::FontList
& Textfield::GetFontList() const {
457 return GetRenderText()->font_list();
460 void Textfield::SetFontList(const gfx::FontList
& font_list
) {
461 GetRenderText()->SetFontList(font_list
);
462 OnCaretBoundsChanged();
463 PreferredSizeChanged();
466 base::string16
Textfield::GetPlaceholderText() const {
467 return placeholder_text_
;
470 gfx::HorizontalAlignment
Textfield::GetHorizontalAlignment() const {
471 return GetRenderText()->horizontal_alignment();
474 void Textfield::SetHorizontalAlignment(gfx::HorizontalAlignment alignment
) {
475 GetRenderText()->SetHorizontalAlignment(alignment
);
478 void Textfield::ShowImeIfNeeded() {
479 if (enabled() && !read_only())
480 GetInputMethod()->ShowImeIfNeeded();
483 bool Textfield::IsIMEComposing() const {
484 return model_
->HasCompositionText();
487 const gfx::Range
& Textfield::GetSelectedRange() const {
488 return GetRenderText()->selection();
491 void Textfield::SelectRange(const gfx::Range
& range
) {
492 model_
->SelectRange(range
);
493 UpdateAfterChange(false, true);
496 const gfx::SelectionModel
& Textfield::GetSelectionModel() const {
497 return GetRenderText()->selection_model();
500 void Textfield::SelectSelectionModel(const gfx::SelectionModel
& sel
) {
501 model_
->SelectSelectionModel(sel
);
502 UpdateAfterChange(false, true);
505 size_t Textfield::GetCursorPosition() const {
506 return model_
->GetCursorPosition();
509 void Textfield::SetColor(SkColor value
) {
510 GetRenderText()->SetColor(value
);
514 void Textfield::ApplyColor(SkColor value
, const gfx::Range
& range
) {
515 GetRenderText()->ApplyColor(value
, range
);
519 void Textfield::SetStyle(gfx::TextStyle style
, bool value
) {
520 GetRenderText()->SetStyle(style
, value
);
524 void Textfield::ApplyStyle(gfx::TextStyle style
,
526 const gfx::Range
& range
) {
527 GetRenderText()->ApplyStyle(style
, value
, range
);
531 void Textfield::ClearEditHistory() {
532 model_
->ClearEditHistory();
535 void Textfield::SetAccessibleName(const base::string16
& name
) {
536 accessible_name_
= name
;
539 void Textfield::ExecuteCommand(int command_id
) {
540 ExecuteCommand(command_id
, ui::EF_NONE
);
543 void Textfield::SetFocusPainter(scoped_ptr
<Painter
> focus_painter
) {
544 focus_painter_
= focus_painter
.Pass();
547 bool Textfield::HasTextBeingDragged() {
548 return initiating_drag_
;
551 ////////////////////////////////////////////////////////////////////////////////
552 // Textfield, View overrides:
554 gfx::Insets
Textfield::GetInsets() const {
555 gfx::Insets insets
= View::GetInsets();
556 insets
+= gfx::Insets(kTextPadding
, kTextPadding
, kTextPadding
, kTextPadding
);
560 int Textfield::GetBaseline() const {
561 return GetInsets().top() + GetRenderText()->GetBaseline();
564 gfx::Size
Textfield::GetPreferredSize() const {
565 const gfx::Insets
& insets
= GetInsets();
566 return gfx::Size(GetFontList().GetExpectedTextWidth(default_width_in_chars_
) +
567 insets
.width(), GetFontList().GetHeight() + insets
.height());
570 const char* Textfield::GetClassName() const {
571 return kViewClassName
;
574 gfx::NativeCursor
Textfield::GetCursor(const ui::MouseEvent
& event
) {
575 bool in_selection
= GetRenderText()->IsPointInSelection(event
.location());
576 bool drag_event
= event
.type() == ui::ET_MOUSE_DRAGGED
;
577 bool text_cursor
= !initiating_drag_
&& (drag_event
|| !in_selection
);
578 return text_cursor
? GetNativeIBeamCursor() : gfx::kNullCursor
;
581 bool Textfield::OnMousePressed(const ui::MouseEvent
& event
) {
582 TrackMouseClicks(event
);
584 if (!controller_
|| !controller_
->HandleMouseEvent(this, event
)) {
585 if (event
.IsOnlyLeftMouseButton() || event
.IsOnlyRightMouseButton()) {
590 if (event
.IsOnlyLeftMouseButton()) {
591 OnBeforeUserAction();
592 initiating_drag_
= false;
593 switch (aggregated_clicks_
) {
595 if (GetRenderText()->IsPointInSelection(event
.location()))
596 initiating_drag_
= true;
598 MoveCursorTo(event
.location(), event
.IsShiftDown());
601 SelectWordAt(event
.location());
602 double_click_word_
= GetRenderText()->selection();
613 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
614 if (event
.IsOnlyMiddleMouseButton()) {
615 if (GetRenderText()->IsPointInSelection(event
.location())) {
616 OnBeforeUserAction();
618 ui::ScopedClipboardWriter(
619 ui::CLIPBOARD_TYPE_SELECTION
).WriteText(base::string16());
621 } else if (!read_only()) {
622 PasteSelectionClipboard(event
);
631 bool Textfield::OnMouseDragged(const ui::MouseEvent
& event
) {
632 last_drag_location_
= event
.location();
634 // Don't adjust the cursor on a potential drag and drop, or if the mouse
635 // movement from the last mouse click does not exceed the drag threshold.
636 if (initiating_drag_
|| !event
.IsOnlyLeftMouseButton() ||
637 !ExceededDragThreshold(last_drag_location_
- last_click_location_
)) {
641 // A timer is used to continuously scroll while selecting beyond side edges.
642 if ((event
.location().x() > 0 && event
.location().x() < size().width()) ||
643 GetDragSelectionDelay() == 0) {
644 drag_selection_timer_
.Stop();
645 SelectThroughLastDragLocation();
646 } else if (!drag_selection_timer_
.IsRunning()) {
647 drag_selection_timer_
.Start(
648 FROM_HERE
, base::TimeDelta::FromMilliseconds(GetDragSelectionDelay()),
649 this, &Textfield::SelectThroughLastDragLocation
);
655 void Textfield::OnMouseReleased(const ui::MouseEvent
& event
) {
656 OnBeforeUserAction();
657 drag_selection_timer_
.Stop();
658 // Cancel suspected drag initiations, the user was clicking in the selection.
659 if (initiating_drag_
)
660 MoveCursorTo(event
.location(), false);
661 initiating_drag_
= false;
662 UpdateSelectionClipboard();
666 bool Textfield::OnKeyPressed(const ui::KeyEvent
& event
) {
667 // Since HandleKeyEvent() might destroy |this|, get a weak pointer and verify
668 // it isn't null before proceeding.
669 base::WeakPtr
<Textfield
> textfield(weak_ptr_factory_
.GetWeakPtr());
671 bool handled
= controller_
&& controller_
->HandleKeyEvent(this, event
);
676 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
677 ui::TextEditKeyBindingsDelegateAuraLinux
* delegate
=
678 ui::GetTextEditKeyBindingsDelegate();
679 std::vector
<ui::TextEditCommandAuraLinux
> commands
;
680 if (!handled
&& delegate
&& delegate
->MatchEvent(event
, &commands
)) {
681 const bool rtl
= GetTextDirection() == base::i18n::RIGHT_TO_LEFT
;
682 for (size_t i
= 0; i
< commands
.size(); ++i
) {
683 const int command
= GetViewsCommand(commands
[i
], rtl
);
684 if (IsCommandIdEnabled(command
)) {
685 ExecuteCommand(command
);
693 const int command
= GetCommandForKeyEvent(event
, HasSelection());
694 if (!handled
&& IsCommandIdEnabled(command
)) {
695 ExecuteCommand(command
);
701 ui::TextInputClient
* Textfield::GetTextInputClient() {
702 return read_only_
? NULL
: this;
705 void Textfield::OnGestureEvent(ui::GestureEvent
* event
) {
706 switch (event
->type()) {
707 case ui::ET_GESTURE_TAP_DOWN
:
712 case ui::ET_GESTURE_TAP
:
713 if (event
->details().tap_count() == 1) {
714 if (!GetRenderText()->IsPointInSelection(event
->location())) {
715 OnBeforeUserAction();
716 MoveCursorTo(event
->location(), false);
719 } else if (event
->details().tap_count() == 2) {
720 OnBeforeUserAction();
721 SelectWordAt(event
->location());
724 OnBeforeUserAction();
728 CreateTouchSelectionControllerAndNotifyIt();
731 base::win::DisplayVirtualKeyboard();
735 case ui::ET_GESTURE_LONG_PRESS
:
736 if (!GetRenderText()->IsPointInSelection(event
->location())) {
737 // If long-press happens outside selection, select word and try to
738 // activate touch selection.
739 OnBeforeUserAction();
740 SelectWordAt(event
->location());
742 CreateTouchSelectionControllerAndNotifyIt();
743 // If touch selection activated successfully, mark event as handled so
744 // that the regular context menu is not shown.
745 if (touch_selection_controller_
)
748 // If long-press happens on the selection, deactivate touch selection
749 // and try to initiate drag-drop. If drag-drop is not enabled, context
750 // menu will be shown. Event is not marked as handled to let Views
751 // handle drag-drop or context menu.
752 DestroyTouchSelection();
753 initiating_drag_
= switches::IsTouchDragDropEnabled();
756 case ui::ET_GESTURE_LONG_TAP
:
757 // If touch selection is enabled, the context menu on long tap will be
758 // shown by the |touch_selection_controller_|, hence we mark the event
759 // handled so Views does not try to show context menu on it.
760 if (touch_selection_controller_
)
763 case ui::ET_GESTURE_SCROLL_BEGIN
:
764 touch_handles_hidden_due_to_scroll_
= touch_selection_controller_
!= NULL
;
765 DestroyTouchSelection();
766 drag_start_location_
= event
->location();
767 drag_start_display_offset_
=
768 GetRenderText()->GetUpdatedDisplayOffset().x();
771 case ui::ET_GESTURE_SCROLL_UPDATE
: {
772 int new_offset
= drag_start_display_offset_
+ event
->location().x() -
773 drag_start_location_
.x();
774 GetRenderText()->SetDisplayOffset(new_offset
);
779 case ui::ET_GESTURE_SCROLL_END
:
780 case ui::ET_SCROLL_FLING_START
:
781 if (touch_handles_hidden_due_to_scroll_
) {
782 CreateTouchSelectionControllerAndNotifyIt();
783 touch_handles_hidden_due_to_scroll_
= false;
792 void Textfield::AboutToRequestFocusFromTabTraversal(bool reverse
) {
796 bool Textfield::SkipDefaultKeyEventProcessing(const ui::KeyEvent
& event
) {
797 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
798 // Skip any accelerator handling that conflicts with custom keybindings.
799 ui::TextEditKeyBindingsDelegateAuraLinux
* delegate
=
800 ui::GetTextEditKeyBindingsDelegate();
801 std::vector
<ui::TextEditCommandAuraLinux
> commands
;
802 if (delegate
&& delegate
->MatchEvent(event
, &commands
)) {
803 const bool rtl
= GetTextDirection() == base::i18n::RIGHT_TO_LEFT
;
804 for (size_t i
= 0; i
< commands
.size(); ++i
)
805 if (IsCommandIdEnabled(GetViewsCommand(commands
[i
], rtl
)))
810 // Skip backspace accelerator handling; editable textfields handle this key.
811 // Also skip processing Windows [Alt]+<num-pad digit> Unicode alt-codes.
812 const bool is_backspace
= event
.key_code() == ui::VKEY_BACK
;
813 return (is_backspace
&& !read_only()) || event
.IsUnicodeKeyCode();
816 bool Textfield::GetDropFormats(
818 std::set
<OSExchangeData::CustomFormat
>* custom_formats
) {
819 if (!enabled() || read_only())
821 // TODO(msw): Can we support URL, FILENAME, etc.?
822 *formats
= ui::OSExchangeData::STRING
;
824 controller_
->AppendDropFormats(formats
, custom_formats
);
828 bool Textfield::CanDrop(const OSExchangeData
& data
) {
830 std::set
<OSExchangeData::CustomFormat
> custom_formats
;
831 GetDropFormats(&formats
, &custom_formats
);
832 return enabled() && !read_only() &&
833 data
.HasAnyFormat(formats
, custom_formats
);
836 int Textfield::OnDragUpdated(const ui::DropTargetEvent
& event
) {
837 DCHECK(CanDrop(event
.data()));
838 gfx::RenderText
* render_text
= GetRenderText();
839 const gfx::Range
& selection
= render_text
->selection();
840 drop_cursor_position_
= render_text
->FindCursorPosition(event
.location());
841 bool in_selection
= !selection
.is_empty() &&
842 selection
.Contains(gfx::Range(drop_cursor_position_
.caret_pos()));
843 drop_cursor_visible_
= !in_selection
;
844 // TODO(msw): Pan over text when the user drags to the visible text edge.
845 OnCaretBoundsChanged();
848 if (initiating_drag_
) {
850 return ui::DragDropTypes::DRAG_NONE
;
851 return event
.IsControlDown() ? ui::DragDropTypes::DRAG_COPY
:
852 ui::DragDropTypes::DRAG_MOVE
;
854 return ui::DragDropTypes::DRAG_COPY
| ui::DragDropTypes::DRAG_MOVE
;
857 void Textfield::OnDragExited() {
858 drop_cursor_visible_
= false;
862 int Textfield::OnPerformDrop(const ui::DropTargetEvent
& event
) {
863 DCHECK(CanDrop(event
.data()));
864 drop_cursor_visible_
= false;
867 int drag_operation
= controller_
->OnDrop(event
.data());
868 if (drag_operation
!= ui::DragDropTypes::DRAG_NONE
)
869 return drag_operation
;
872 gfx::RenderText
* render_text
= GetRenderText();
873 DCHECK(!initiating_drag_
||
874 !render_text
->IsPointInSelection(event
.location()));
875 OnBeforeUserAction();
876 skip_input_method_cancel_composition_
= true;
878 gfx::SelectionModel drop_destination_model
=
879 render_text
->FindCursorPosition(event
.location());
880 base::string16 new_text
;
881 event
.data().GetString(&new_text
);
883 // Delete the current selection for a drag and drop within this view.
884 const bool move
= initiating_drag_
&& !event
.IsControlDown() &&
885 event
.source_operations() & ui::DragDropTypes::DRAG_MOVE
;
887 // Adjust the drop destination if it is on or after the current selection.
888 size_t pos
= drop_destination_model
.caret_pos();
889 pos
-= render_text
->selection().Intersect(gfx::Range(0, pos
)).length();
890 model_
->DeleteSelectionAndInsertTextAt(new_text
, pos
);
892 model_
->MoveCursorTo(drop_destination_model
);
893 // Drop always inserts text even if the textfield is not in insert mode.
894 model_
->InsertText(new_text
);
896 skip_input_method_cancel_composition_
= false;
897 UpdateAfterChange(true, true);
899 return move
? ui::DragDropTypes::DRAG_MOVE
: ui::DragDropTypes::DRAG_COPY
;
902 void Textfield::OnDragDone() {
903 initiating_drag_
= false;
904 drop_cursor_visible_
= false;
907 void Textfield::GetAccessibleState(ui::AXViewState
* state
) {
908 state
->role
= ui::AX_ROLE_TEXT_FIELD
;
909 state
->name
= accessible_name_
;
911 state
->AddStateFlag(ui::AX_STATE_READ_ONLY
);
912 if (text_input_type_
== ui::TEXT_INPUT_TYPE_PASSWORD
)
913 state
->AddStateFlag(ui::AX_STATE_PROTECTED
);
914 state
->value
= text();
916 const gfx::Range range
= GetSelectedRange();
917 state
->selection_start
= range
.start();
918 state
->selection_end
= range
.end();
921 state
->set_value_callback
=
922 base::Bind(&Textfield::AccessibilitySetValue
,
923 weak_ptr_factory_
.GetWeakPtr());
927 void Textfield::OnBoundsChanged(const gfx::Rect
& previous_bounds
) {
928 // Textfield insets include a reasonable amount of whitespace on all sides of
929 // the default font list. Fallback fonts with larger heights may paint over
930 // the vertical whitespace as needed. Alternate solutions involve undesirable
931 // behavior like changing the default font size, shrinking some fallback fonts
932 // beyond their legibility, or enlarging controls dynamically with content.
933 gfx::Rect bounds
= GetContentsBounds();
934 // GetContentsBounds() does not actually use the local GetInsets() override.
935 bounds
.Inset(gfx::Insets(0, kTextPadding
, 0, kTextPadding
));
936 GetRenderText()->SetDisplayRect(bounds
);
937 OnCaretBoundsChanged();
940 bool Textfield::GetNeedsNotificationWhenVisibleBoundsChange() const {
944 void Textfield::OnVisibleBoundsChanged() {
945 if (touch_selection_controller_
)
946 touch_selection_controller_
->SelectionChanged();
949 void Textfield::OnEnabledChanged() {
950 View::OnEnabledChanged();
951 if (GetInputMethod())
952 GetInputMethod()->OnTextInputTypeChanged(this);
956 void Textfield::OnPaint(gfx::Canvas
* canvas
) {
957 OnPaintBackground(canvas
);
958 PaintTextAndCursor(canvas
);
959 OnPaintBorder(canvas
);
962 void Textfield::OnFocus() {
963 GetRenderText()->set_focused(true);
964 cursor_visible_
= true;
966 GetInputMethod()->OnFocus();
967 OnCaretBoundsChanged();
969 const size_t caret_blink_ms
= Textfield::GetCaretBlinkMs();
970 if (caret_blink_ms
!= 0) {
971 cursor_repaint_timer_
.Start(FROM_HERE
,
972 base::TimeDelta::FromMilliseconds(caret_blink_ms
), this,
973 &Textfield::UpdateCursor
);
980 void Textfield::OnBlur() {
981 GetRenderText()->set_focused(false);
982 GetInputMethod()->OnBlur();
983 cursor_repaint_timer_
.Stop();
984 if (cursor_visible_
) {
985 cursor_visible_
= false;
989 DestroyTouchSelection();
991 // Border typically draws focus indicator.
995 gfx::Point
Textfield::GetKeyboardContextMenuLocation() {
996 return GetCaretBounds().bottom_right();
999 void Textfield::OnNativeThemeChanged(const ui::NativeTheme
* theme
) {
1000 gfx::RenderText
* render_text
= GetRenderText();
1001 render_text
->SetColor(GetTextColor());
1002 UpdateBackgroundColor();
1003 render_text
->set_cursor_color(GetTextColor());
1004 render_text
->set_selection_color(GetSelectionTextColor());
1005 render_text
->set_selection_background_focused_color(
1006 GetSelectionBackgroundColor());
1009 ////////////////////////////////////////////////////////////////////////////////
1010 // Textfield, TextfieldModel::Delegate overrides:
1012 void Textfield::OnCompositionTextConfirmedOrCleared() {
1013 if (!skip_input_method_cancel_composition_
)
1014 GetInputMethod()->CancelComposition(this);
1017 ////////////////////////////////////////////////////////////////////////////////
1018 // Textfield, ContextMenuController overrides:
1020 void Textfield::ShowContextMenuForView(View
* source
,
1021 const gfx::Point
& point
,
1022 ui::MenuSourceType source_type
) {
1023 UpdateContextMenu();
1024 ignore_result(context_menu_runner_
->RunMenuAt(GetWidget(),
1026 gfx::Rect(point
, gfx::Size()),
1027 MENU_ANCHOR_TOPLEFT
,
1031 ////////////////////////////////////////////////////////////////////////////////
1032 // Textfield, DragController overrides:
1034 void Textfield::WriteDragDataForView(View
* sender
,
1035 const gfx::Point
& press_pt
,
1036 OSExchangeData
* data
) {
1037 const base::string16
& selected_text(GetSelectedText());
1038 data
->SetString(selected_text
);
1039 Label
label(selected_text
, GetFontList());
1040 label
.SetBackgroundColor(GetBackgroundColor());
1041 label
.SetSubpixelRenderingEnabled(false);
1042 gfx::Size
size(label
.GetPreferredSize());
1043 gfx::NativeView native_view
= GetWidget()->GetNativeView();
1044 gfx::Display display
= gfx::Screen::GetScreenFor(native_view
)->
1045 GetDisplayNearestWindow(native_view
);
1046 size
.SetToMin(gfx::Size(display
.size().width(), height()));
1047 label
.SetBoundsRect(gfx::Rect(size
));
1048 scoped_ptr
<gfx::Canvas
> canvas(
1049 GetCanvasForDragImage(GetWidget(), label
.size()));
1050 label
.SetEnabledColor(GetTextColor());
1051 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
1052 // Desktop Linux Aura does not yet support transparency in drag images.
1053 canvas
->DrawColor(GetBackgroundColor());
1055 label
.Paint(canvas
.get(), views::CullSet());
1056 const gfx::Vector2d
kOffset(-15, 0);
1057 drag_utils::SetDragImageOnDataObject(*canvas
, kOffset
, data
);
1059 controller_
->OnWriteDragData(data
);
1062 int Textfield::GetDragOperationsForView(View
* sender
, const gfx::Point
& p
) {
1063 int drag_operations
= ui::DragDropTypes::DRAG_COPY
;
1064 if (!enabled() || text_input_type_
== ui::TEXT_INPUT_TYPE_PASSWORD
||
1065 !GetRenderText()->IsPointInSelection(p
)) {
1066 drag_operations
= ui::DragDropTypes::DRAG_NONE
;
1067 } else if (sender
== this && !read_only()) {
1069 ui::DragDropTypes::DRAG_MOVE
| ui::DragDropTypes::DRAG_COPY
;
1072 controller_
->OnGetDragOperationsForTextfield(&drag_operations
);
1073 return drag_operations
;
1076 bool Textfield::CanStartDragForView(View
* sender
,
1077 const gfx::Point
& press_pt
,
1078 const gfx::Point
& p
) {
1079 return initiating_drag_
&& GetRenderText()->IsPointInSelection(press_pt
);
1082 ////////////////////////////////////////////////////////////////////////////////
1083 // Textfield, ui::TouchEditable overrides:
1085 void Textfield::SelectRect(const gfx::Point
& start
, const gfx::Point
& end
) {
1086 if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE
)
1089 gfx::SelectionModel start_caret
= GetRenderText()->FindCursorPosition(start
);
1090 gfx::SelectionModel end_caret
= GetRenderText()->FindCursorPosition(end
);
1091 gfx::SelectionModel
selection(
1092 gfx::Range(start_caret
.caret_pos(), end_caret
.caret_pos()),
1093 end_caret
.caret_affinity());
1095 OnBeforeUserAction();
1096 SelectSelectionModel(selection
);
1097 OnAfterUserAction();
1100 void Textfield::MoveCaretTo(const gfx::Point
& point
) {
1101 SelectRect(point
, point
);
1104 void Textfield::GetSelectionEndPoints(gfx::Rect
* p1
, gfx::Rect
* p2
) {
1105 gfx::RenderText
* render_text
= GetRenderText();
1106 const gfx::SelectionModel
& sel
= render_text
->selection_model();
1107 gfx::SelectionModel start_sel
=
1108 render_text
->GetSelectionModelForSelectionStart();
1109 *p1
= render_text
->GetCursorBounds(start_sel
, true);
1110 *p2
= render_text
->GetCursorBounds(sel
, true);
1113 gfx::Rect
Textfield::GetBounds() {
1114 return GetLocalBounds();
1117 gfx::NativeView
Textfield::GetNativeView() const {
1118 return GetWidget()->GetNativeView();
1121 void Textfield::ConvertPointToScreen(gfx::Point
* point
) {
1122 View::ConvertPointToScreen(this, point
);
1125 void Textfield::ConvertPointFromScreen(gfx::Point
* point
) {
1126 View::ConvertPointFromScreen(this, point
);
1129 bool Textfield::DrawsHandles() {
1133 void Textfield::OpenContextMenu(const gfx::Point
& anchor
) {
1134 DestroyTouchSelection();
1135 ShowContextMenu(anchor
, ui::MENU_SOURCE_TOUCH_EDIT_MENU
);
1138 void Textfield::DestroyTouchSelection() {
1139 touch_selection_controller_
.reset();
1142 ////////////////////////////////////////////////////////////////////////////////
1143 // Textfield, ui::SimpleMenuModel::Delegate overrides:
1145 bool Textfield::IsCommandIdChecked(int command_id
) const {
1149 bool Textfield::IsCommandIdEnabled(int command_id
) const {
1150 base::string16 result
;
1151 bool editable
= !read_only();
1152 bool readable
= text_input_type_
!= ui::TEXT_INPUT_TYPE_PASSWORD
;
1153 switch (command_id
) {
1155 return editable
&& model_
->CanUndo();
1157 return editable
&& model_
->CanRedo();
1159 return editable
&& readable
&& model_
->HasSelection();
1161 return readable
&& model_
->HasSelection();
1163 ui::Clipboard::GetForCurrentThread()->ReadText(
1164 ui::CLIPBOARD_TYPE_COPY_PASTE
, &result
);
1165 return editable
&& !result
.empty();
1166 case IDS_APP_DELETE
:
1167 return editable
&& model_
->HasSelection();
1168 case IDS_APP_SELECT_ALL
:
1169 return !text().empty();
1170 case IDS_DELETE_FORWARD
:
1171 case IDS_DELETE_BACKWARD
:
1172 case IDS_DELETE_TO_BEGINNING_OF_LINE
:
1173 case IDS_DELETE_TO_END_OF_LINE
:
1174 case IDS_DELETE_WORD_BACKWARD
:
1175 case IDS_DELETE_WORD_FORWARD
:
1178 case IDS_MOVE_LEFT_AND_MODIFY_SELECTION
:
1179 case IDS_MOVE_RIGHT
:
1180 case IDS_MOVE_RIGHT_AND_MODIFY_SELECTION
:
1181 case IDS_MOVE_WORD_LEFT
:
1182 case IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION
:
1183 case IDS_MOVE_WORD_RIGHT
:
1184 case IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION
:
1185 case IDS_MOVE_TO_BEGINNING_OF_LINE
:
1186 case IDS_MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION
:
1187 case IDS_MOVE_TO_END_OF_LINE
:
1188 case IDS_MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION
:
1195 bool Textfield::GetAcceleratorForCommandId(int command_id
,
1196 ui::Accelerator
* accelerator
) {
1200 void Textfield::ExecuteCommand(int command_id
, int event_flags
) {
1201 DestroyTouchSelection();
1202 if (!IsCommandIdEnabled(command_id
))
1205 bool text_changed
= false;
1206 bool cursor_changed
= false;
1207 bool rtl
= GetTextDirection() == base::i18n::RIGHT_TO_LEFT
;
1208 gfx::VisualCursorDirection begin
= rtl
? gfx::CURSOR_RIGHT
: gfx::CURSOR_LEFT
;
1209 gfx::VisualCursorDirection end
= rtl
? gfx::CURSOR_LEFT
: gfx::CURSOR_RIGHT
;
1210 gfx::SelectionModel selection_model
= GetSelectionModel();
1212 OnBeforeUserAction();
1213 switch (command_id
) {
1215 text_changed
= cursor_changed
= model_
->Undo();
1218 text_changed
= cursor_changed
= model_
->Redo();
1221 text_changed
= cursor_changed
= Cut();
1227 text_changed
= cursor_changed
= Paste();
1229 case IDS_APP_DELETE
:
1230 text_changed
= cursor_changed
= model_
->Delete();
1232 case IDS_APP_SELECT_ALL
:
1235 case IDS_DELETE_BACKWARD
:
1236 text_changed
= cursor_changed
= model_
->Backspace();
1238 case IDS_DELETE_FORWARD
:
1239 text_changed
= cursor_changed
= model_
->Delete();
1241 case IDS_DELETE_TO_END_OF_LINE
:
1242 model_
->MoveCursor(gfx::LINE_BREAK
, end
, true);
1243 text_changed
= cursor_changed
= model_
->Delete();
1245 case IDS_DELETE_TO_BEGINNING_OF_LINE
:
1246 model_
->MoveCursor(gfx::LINE_BREAK
, begin
, true);
1247 text_changed
= cursor_changed
= model_
->Backspace();
1249 case IDS_DELETE_WORD_BACKWARD
:
1250 model_
->MoveCursor(gfx::WORD_BREAK
, begin
, true);
1251 text_changed
= cursor_changed
= model_
->Backspace();
1253 case IDS_DELETE_WORD_FORWARD
:
1254 model_
->MoveCursor(gfx::WORD_BREAK
, end
, true);
1255 text_changed
= cursor_changed
= model_
->Delete();
1258 model_
->MoveCursor(gfx::CHARACTER_BREAK
, gfx::CURSOR_LEFT
, false);
1260 case IDS_MOVE_LEFT_AND_MODIFY_SELECTION
:
1261 model_
->MoveCursor(gfx::CHARACTER_BREAK
, gfx::CURSOR_LEFT
, true);
1263 case IDS_MOVE_RIGHT
:
1264 model_
->MoveCursor(gfx::CHARACTER_BREAK
, gfx::CURSOR_RIGHT
, false);
1266 case IDS_MOVE_RIGHT_AND_MODIFY_SELECTION
:
1267 model_
->MoveCursor(gfx::CHARACTER_BREAK
, gfx::CURSOR_RIGHT
, true);
1269 case IDS_MOVE_WORD_LEFT
:
1270 model_
->MoveCursor(gfx::WORD_BREAK
, gfx::CURSOR_LEFT
, false);
1272 case IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION
:
1273 model_
->MoveCursor(gfx::WORD_BREAK
, gfx::CURSOR_LEFT
, true);
1275 case IDS_MOVE_WORD_RIGHT
:
1276 model_
->MoveCursor(gfx::WORD_BREAK
, gfx::CURSOR_RIGHT
, false);
1278 case IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION
:
1279 model_
->MoveCursor(gfx::WORD_BREAK
, gfx::CURSOR_RIGHT
, true);
1281 case IDS_MOVE_TO_BEGINNING_OF_LINE
:
1282 model_
->MoveCursor(gfx::LINE_BREAK
, begin
, false);
1284 case IDS_MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION
:
1285 model_
->MoveCursor(gfx::LINE_BREAK
, begin
, true);
1287 case IDS_MOVE_TO_END_OF_LINE
:
1288 model_
->MoveCursor(gfx::LINE_BREAK
, end
, false);
1290 case IDS_MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION
:
1291 model_
->MoveCursor(gfx::LINE_BREAK
, end
, true);
1298 cursor_changed
|= GetSelectionModel() != selection_model
;
1300 UpdateSelectionClipboard();
1301 UpdateAfterChange(text_changed
, cursor_changed
);
1302 OnAfterUserAction();
1305 ////////////////////////////////////////////////////////////////////////////////
1306 // Textfield, ui::TextInputClient overrides:
1308 void Textfield::SetCompositionText(const ui::CompositionText
& composition
) {
1309 if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE
)
1312 OnBeforeUserAction();
1313 skip_input_method_cancel_composition_
= true;
1314 model_
->SetCompositionText(composition
);
1315 skip_input_method_cancel_composition_
= false;
1316 UpdateAfterChange(true, true);
1317 OnAfterUserAction();
1320 void Textfield::ConfirmCompositionText() {
1321 if (!model_
->HasCompositionText())
1324 OnBeforeUserAction();
1325 skip_input_method_cancel_composition_
= true;
1326 model_
->ConfirmCompositionText();
1327 skip_input_method_cancel_composition_
= false;
1328 UpdateAfterChange(true, true);
1329 OnAfterUserAction();
1332 void Textfield::ClearCompositionText() {
1333 if (!model_
->HasCompositionText())
1336 OnBeforeUserAction();
1337 skip_input_method_cancel_composition_
= true;
1338 model_
->CancelCompositionText();
1339 skip_input_method_cancel_composition_
= false;
1340 UpdateAfterChange(true, true);
1341 OnAfterUserAction();
1344 void Textfield::InsertText(const base::string16
& new_text
) {
1345 // TODO(suzhe): Filter invalid characters.
1346 if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE
|| new_text
.empty())
1349 OnBeforeUserAction();
1350 skip_input_method_cancel_composition_
= true;
1351 if (GetRenderText()->insert_mode())
1352 model_
->InsertText(new_text
);
1354 model_
->ReplaceText(new_text
);
1355 skip_input_method_cancel_composition_
= false;
1356 UpdateAfterChange(true, true);
1357 OnAfterUserAction();
1360 void Textfield::InsertChar(base::char16 ch
, int flags
) {
1361 const int kControlModifierMask
= ui::EF_CONTROL_DOWN
| ui::EF_ALT_DOWN
|
1362 ui::EF_COMMAND_DOWN
| ui::EF_ALTGR_DOWN
|
1365 // Filter out all control characters, including tab and new line characters,
1366 // and all characters with Alt modifier. But allow characters with the AltGr
1367 // modifier. On Windows AltGr is represented by Alt+Ctrl, and on Linux it's a
1368 // different flag that we don't care about.
1369 const bool should_insert_char
=
1370 ((ch
>= 0x20 && ch
< 0x7F) || ch
> 0x9F) &&
1371 (flags
& kControlModifierMask
) != ui::EF_ALT_DOWN
;
1372 if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE
|| !should_insert_char
)
1375 OnBeforeUserAction();
1376 skip_input_method_cancel_composition_
= true;
1377 if (GetRenderText()->insert_mode())
1378 model_
->InsertChar(ch
);
1380 model_
->ReplaceChar(ch
);
1381 skip_input_method_cancel_composition_
= false;
1383 UpdateAfterChange(true, true);
1384 OnAfterUserAction();
1386 if (text_input_type_
== ui::TEXT_INPUT_TYPE_PASSWORD
&&
1387 password_reveal_duration_
!= base::TimeDelta()) {
1388 const size_t change_offset
= model_
->GetCursorPosition();
1389 DCHECK_GT(change_offset
, 0u);
1390 RevealPasswordChar(change_offset
- 1);
1394 gfx::NativeWindow
Textfield::GetAttachedWindow() const {
1395 // Imagine the following hierarchy.
1396 // [NativeWidget A] - FocusManager
1401 // An important thing is that [NativeWidget A] owns Win32 input focus even
1402 // when [View X] is logically focused by FocusManager. As a result, an Win32
1403 // IME may want to interact with the native view of [NativeWidget A] rather
1404 // than that of [NativeWidget B]. This is why we need to call
1405 // GetTopLevelWidget() here.
1406 return GetWidget()->GetTopLevelWidget()->GetNativeWindow();
1409 ui::TextInputType
Textfield::GetTextInputType() const {
1410 if (read_only() || !enabled())
1411 return ui::TEXT_INPUT_TYPE_NONE
;
1412 return text_input_type_
;
1415 ui::TextInputMode
Textfield::GetTextInputMode() const {
1416 return ui::TEXT_INPUT_MODE_DEFAULT
;
1419 int Textfield::GetTextInputFlags() const {
1423 bool Textfield::CanComposeInline() const {
1427 gfx::Rect
Textfield::GetCaretBounds() const {
1428 gfx::Rect rect
= GetRenderText()->GetUpdatedCursorBounds();
1429 ConvertRectToScreen(this, &rect
);
1433 bool Textfield::GetCompositionCharacterBounds(uint32 index
,
1434 gfx::Rect
* rect
) const {
1436 if (!HasCompositionText())
1438 gfx::RenderText
* render_text
= GetRenderText();
1439 const gfx::Range
& composition_range
= render_text
->GetCompositionRange();
1440 DCHECK(!composition_range
.is_empty());
1442 size_t text_index
= composition_range
.start() + index
;
1443 if (composition_range
.end() <= text_index
)
1445 if (!render_text
->IsValidCursorIndex(text_index
)) {
1446 text_index
= render_text
->IndexOfAdjacentGrapheme(
1447 text_index
, gfx::CURSOR_BACKWARD
);
1449 if (text_index
< composition_range
.start())
1451 const gfx::SelectionModel
caret(text_index
, gfx::CURSOR_BACKWARD
);
1452 *rect
= render_text
->GetCursorBounds(caret
, false);
1453 ConvertRectToScreen(this, rect
);
1457 bool Textfield::HasCompositionText() const {
1458 return model_
->HasCompositionText();
1461 bool Textfield::GetTextRange(gfx::Range
* range
) const {
1462 if (!ImeEditingAllowed())
1465 model_
->GetTextRange(range
);
1469 bool Textfield::GetCompositionTextRange(gfx::Range
* range
) const {
1470 if (!ImeEditingAllowed())
1473 model_
->GetCompositionTextRange(range
);
1477 bool Textfield::GetSelectionRange(gfx::Range
* range
) const {
1478 if (!ImeEditingAllowed())
1480 *range
= GetRenderText()->selection();
1484 bool Textfield::SetSelectionRange(const gfx::Range
& range
) {
1485 if (!ImeEditingAllowed() || !range
.IsValid())
1487 OnBeforeUserAction();
1489 OnAfterUserAction();
1493 bool Textfield::DeleteRange(const gfx::Range
& range
) {
1494 if (!ImeEditingAllowed() || range
.is_empty())
1497 OnBeforeUserAction();
1498 model_
->SelectRange(range
);
1499 if (model_
->HasSelection()) {
1500 model_
->DeleteSelection();
1501 UpdateAfterChange(true, true);
1503 OnAfterUserAction();
1507 bool Textfield::GetTextFromRange(const gfx::Range
& range
,
1508 base::string16
* range_text
) const {
1509 if (!ImeEditingAllowed() || !range
.IsValid())
1512 gfx::Range text_range
;
1513 if (!GetTextRange(&text_range
) || !text_range
.Contains(range
))
1516 *range_text
= model_
->GetTextFromRange(range
);
1520 void Textfield::OnInputMethodChanged() {}
1522 bool Textfield::ChangeTextDirectionAndLayoutAlignment(
1523 base::i18n::TextDirection direction
) {
1524 // Restore text directionality mode when the indicated direction matches the
1525 // current forced mode; otherwise, force the mode indicated. This helps users
1526 // manage BiDi text layout without getting stuck in forced LTR or RTL modes.
1527 const gfx::DirectionalityMode mode
= direction
== base::i18n::RIGHT_TO_LEFT
?
1528 gfx::DIRECTIONALITY_FORCE_RTL
: gfx::DIRECTIONALITY_FORCE_LTR
;
1529 if (mode
== GetRenderText()->directionality_mode())
1530 GetRenderText()->SetDirectionalityMode(gfx::DIRECTIONALITY_FROM_TEXT
);
1532 GetRenderText()->SetDirectionalityMode(mode
);
1537 void Textfield::ExtendSelectionAndDelete(size_t before
, size_t after
) {
1538 gfx::Range range
= GetRenderText()->selection();
1539 DCHECK_GE(range
.start(), before
);
1541 range
.set_start(range
.start() - before
);
1542 range
.set_end(range
.end() + after
);
1543 gfx::Range text_range
;
1544 if (GetTextRange(&text_range
) && text_range
.Contains(range
))
1548 void Textfield::EnsureCaretInRect(const gfx::Rect
& rect
) {}
1550 void Textfield::OnCandidateWindowShown() {}
1552 void Textfield::OnCandidateWindowUpdated() {}
1554 void Textfield::OnCandidateWindowHidden() {}
1556 bool Textfield::IsEditingCommandEnabled(int command_id
) {
1557 return IsCommandIdEnabled(command_id
);
1560 void Textfield::ExecuteEditingCommand(int command_id
) {
1561 ExecuteCommand(command_id
);
1564 ////////////////////////////////////////////////////////////////////////////////
1565 // Textfield, protected:
1567 gfx::RenderText
* Textfield::GetRenderText() const {
1568 return model_
->render_text();
1571 base::string16
Textfield::GetSelectionClipboardText() const {
1572 base::string16 selection_clipboard_text
;
1573 ui::Clipboard::GetForCurrentThread()->ReadText(
1574 ui::CLIPBOARD_TYPE_SELECTION
, &selection_clipboard_text
);
1575 return selection_clipboard_text
;
1578 ////////////////////////////////////////////////////////////////////////////////
1579 // Textfield, private:
1581 void Textfield::AccessibilitySetValue(const base::string16
& new_value
) {
1588 void Textfield::UpdateBackgroundColor() {
1589 const SkColor color
= GetBackgroundColor();
1590 set_background(Background::CreateSolidBackground(color
));
1591 GetRenderText()->set_background_is_transparent(SkColorGetA(color
) != 0xFF);
1595 void Textfield::UpdateAfterChange(bool text_changed
, bool cursor_changed
) {
1598 controller_
->ContentsChanged(this, text());
1599 NotifyAccessibilityEvent(ui::AX_EVENT_TEXT_CHANGED
, true);
1601 if (cursor_changed
) {
1602 cursor_visible_
= true;
1604 if (cursor_repaint_timer_
.IsRunning())
1605 cursor_repaint_timer_
.Reset();
1606 if (!text_changed
) {
1607 // TEXT_CHANGED implies TEXT_SELECTION_CHANGED, so we only need to fire
1608 // this if only the selection changed.
1609 NotifyAccessibilityEvent(ui::AX_EVENT_TEXT_SELECTION_CHANGED
, true);
1612 if (text_changed
|| cursor_changed
) {
1613 OnCaretBoundsChanged();
1618 void Textfield::UpdateCursor() {
1619 const size_t caret_blink_ms
= Textfield::GetCaretBlinkMs();
1620 cursor_visible_
= !cursor_visible_
|| (caret_blink_ms
== 0);
1624 void Textfield::RepaintCursor() {
1625 gfx::Rect
r(GetRenderText()->GetUpdatedCursorBounds());
1626 r
.Inset(-1, -1, -1, -1);
1627 SchedulePaintInRect(r
);
1630 void Textfield::PaintTextAndCursor(gfx::Canvas
* canvas
) {
1631 TRACE_EVENT0("views", "Textfield::PaintTextAndCursor");
1634 // Draw placeholder text if needed.
1635 gfx::RenderText
* render_text
= GetRenderText();
1636 if (text().empty() && !GetPlaceholderText().empty()) {
1637 canvas
->DrawStringRect(GetPlaceholderText(), GetFontList(),
1638 placeholder_text_color(), render_text
->display_rect());
1641 // Draw the text, cursor, and selection.
1642 render_text
->set_cursor_visible(cursor_visible_
&& !drop_cursor_visible_
&&
1644 render_text
->Draw(canvas
);
1646 // Draw the detached drop cursor that marks where the text will be dropped.
1647 if (drop_cursor_visible_
)
1648 render_text
->DrawCursor(canvas
, drop_cursor_position_
);
1653 void Textfield::MoveCursorTo(const gfx::Point
& point
, bool select
) {
1654 if (model_
->MoveCursorTo(point
, select
))
1655 UpdateAfterChange(false, true);
1658 void Textfield::SelectThroughLastDragLocation() {
1659 OnBeforeUserAction();
1660 model_
->MoveCursorTo(last_drag_location_
, true);
1661 if (aggregated_clicks_
== 1) {
1662 model_
->SelectWord();
1663 // Expand the selection so the initially selected word remains selected.
1664 gfx::Range selection
= GetRenderText()->selection();
1665 const size_t min
= std::min(selection
.GetMin(),
1666 double_click_word_
.GetMin());
1667 const size_t max
= std::max(selection
.GetMax(),
1668 double_click_word_
.GetMax());
1669 const bool reversed
= selection
.is_reversed();
1670 selection
.set_start(reversed
? max
: min
);
1671 selection
.set_end(reversed
? min
: max
);
1672 model_
->SelectRange(selection
);
1674 UpdateAfterChange(false, true);
1675 OnAfterUserAction();
1678 void Textfield::OnCaretBoundsChanged() {
1679 if (GetInputMethod())
1680 GetInputMethod()->OnCaretBoundsChanged(this);
1681 if (touch_selection_controller_
)
1682 touch_selection_controller_
->SelectionChanged();
1685 void Textfield::OnBeforeUserAction() {
1686 DCHECK(!performing_user_action_
);
1687 performing_user_action_
= true;
1689 controller_
->OnBeforeUserAction(this);
1692 void Textfield::OnAfterUserAction() {
1694 controller_
->OnAfterUserAction(this);
1695 DCHECK(performing_user_action_
);
1696 performing_user_action_
= false;
1699 bool Textfield::Cut() {
1700 if (!read_only() && text_input_type_
!= ui::TEXT_INPUT_TYPE_PASSWORD
&&
1703 controller_
->OnAfterCutOrCopy(ui::CLIPBOARD_TYPE_COPY_PASTE
);
1709 bool Textfield::Copy() {
1710 if (text_input_type_
!= ui::TEXT_INPUT_TYPE_PASSWORD
&& model_
->Copy()) {
1712 controller_
->OnAfterCutOrCopy(ui::CLIPBOARD_TYPE_COPY_PASTE
);
1718 bool Textfield::Paste() {
1719 if (!read_only() && model_
->Paste()) {
1721 controller_
->OnAfterPaste();
1727 void Textfield::UpdateContextMenu() {
1728 if (!context_menu_contents_
.get()) {
1729 context_menu_contents_
.reset(new ui::SimpleMenuModel(this));
1730 context_menu_contents_
->AddItemWithStringId(IDS_APP_UNDO
, IDS_APP_UNDO
);
1731 context_menu_contents_
->AddSeparator(ui::NORMAL_SEPARATOR
);
1732 context_menu_contents_
->AddItemWithStringId(IDS_APP_CUT
, IDS_APP_CUT
);
1733 context_menu_contents_
->AddItemWithStringId(IDS_APP_COPY
, IDS_APP_COPY
);
1734 context_menu_contents_
->AddItemWithStringId(IDS_APP_PASTE
, IDS_APP_PASTE
);
1735 context_menu_contents_
->AddItemWithStringId(IDS_APP_DELETE
, IDS_APP_DELETE
);
1736 context_menu_contents_
->AddSeparator(ui::NORMAL_SEPARATOR
);
1737 context_menu_contents_
->AddItemWithStringId(IDS_APP_SELECT_ALL
,
1738 IDS_APP_SELECT_ALL
);
1740 controller_
->UpdateContextMenu(context_menu_contents_
.get());
1742 context_menu_runner_
.reset(
1743 new MenuRunner(context_menu_contents_
.get(),
1744 MenuRunner::HAS_MNEMONICS
| MenuRunner::CONTEXT_MENU
));
1747 void Textfield::TrackMouseClicks(const ui::MouseEvent
& event
) {
1748 if (event
.IsOnlyLeftMouseButton()) {
1749 base::TimeDelta time_delta
= event
.time_stamp() - last_click_time_
;
1750 if (time_delta
.InMilliseconds() <= GetDoubleClickInterval() &&
1751 !ExceededDragThreshold(event
.location() - last_click_location_
)) {
1752 // Upon clicking after a triple click, the count should go back to double
1753 // click and alternate between double and triple. This assignment maps
1754 // 0 to 1, 1 to 2, 2 to 1.
1755 aggregated_clicks_
= (aggregated_clicks_
% 2) + 1;
1757 aggregated_clicks_
= 0;
1759 last_click_time_
= event
.time_stamp();
1760 last_click_location_
= event
.location();
1764 bool Textfield::ImeEditingAllowed() const {
1765 // Disallow input method editing of password fields.
1766 ui::TextInputType t
= GetTextInputType();
1767 return (t
!= ui::TEXT_INPUT_TYPE_NONE
&& t
!= ui::TEXT_INPUT_TYPE_PASSWORD
);
1770 void Textfield::RevealPasswordChar(int index
) {
1771 GetRenderText()->SetObscuredRevealIndex(index
);
1775 password_reveal_timer_
.Start(FROM_HERE
, password_reveal_duration_
,
1776 base::Bind(&Textfield::RevealPasswordChar
,
1777 weak_ptr_factory_
.GetWeakPtr(), -1));
1781 void Textfield::CreateTouchSelectionControllerAndNotifyIt() {
1785 if (!touch_selection_controller_
) {
1786 touch_selection_controller_
.reset(
1787 ui::TouchSelectionController::create(this));
1789 if (touch_selection_controller_
)
1790 touch_selection_controller_
->SelectionChanged();
1793 void Textfield::UpdateSelectionClipboard() const {
1794 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
1795 if (performing_user_action_
&& HasSelection()) {
1796 ui::ScopedClipboardWriter(
1797 ui::CLIPBOARD_TYPE_SELECTION
).WriteText(GetSelectedText());
1799 controller_
->OnAfterCutOrCopy(ui::CLIPBOARD_TYPE_SELECTION
);
1804 void Textfield::PasteSelectionClipboard(const ui::MouseEvent
& event
) {
1805 DCHECK(event
.IsOnlyMiddleMouseButton());
1806 DCHECK(!read_only());
1807 base::string16 selection_clipboard_text
= GetSelectionClipboardText();
1808 if (!selection_clipboard_text
.empty()) {
1809 OnBeforeUserAction();
1810 gfx::Range range
= GetSelectionModel().selection();
1811 gfx::LogicalCursorDirection affinity
= GetSelectionModel().caret_affinity();
1812 const gfx::SelectionModel mouse
=
1813 GetRenderText()->FindCursorPosition(event
.location());
1814 model_
->MoveCursorTo(mouse
);
1815 model_
->InsertText(selection_clipboard_text
);
1816 // Update the new selection range as needed.
1817 if (range
.GetMin() >= mouse
.caret_pos()) {
1818 const size_t length
= selection_clipboard_text
.length();
1819 range
= gfx::Range(range
.start() + length
, range
.end() + length
);
1821 model_
->MoveCursorTo(gfx::SelectionModel(range
, affinity
));
1822 UpdateAfterChange(true, true);
1823 OnAfterUserAction();
1827 } // namespace views