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/trace_event/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/ime/input_method.h"
16 #include "ui/base/touch/selection_bound.h"
17 #include "ui/base/ui_base_switches_util.h"
18 #include "ui/compositor/canvas_painter.h"
19 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
20 #include "ui/events/event.h"
21 #include "ui/events/keycodes/keyboard_codes.h"
22 #include "ui/gfx/canvas.h"
23 #include "ui/gfx/display.h"
24 #include "ui/gfx/geometry/insets.h"
25 #include "ui/gfx/screen.h"
26 #include "ui/native_theme/native_theme.h"
27 #include "ui/strings/grit/ui_strings.h"
28 #include "ui/views/background.h"
29 #include "ui/views/controls/focusable_border.h"
30 #include "ui/views/controls/label.h"
31 #include "ui/views/controls/menu/menu_runner.h"
32 #include "ui/views/controls/native/native_view_host.h"
33 #include "ui/views/controls/textfield/textfield_controller.h"
34 #include "ui/views/drag_utils.h"
35 #include "ui/views/metrics.h"
36 #include "ui/views/native_cursor.h"
37 #include "ui/views/painter.h"
38 #include "ui/views/views_delegate.h"
39 #include "ui/views/widget/widget.h"
42 #include "base/win/win_util.h"
45 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
46 #include "base/strings/utf_string_conversions.h"
47 #include "ui/events/linux/text_edit_command_auralinux.h"
48 #include "ui/events/linux/text_edit_key_bindings_delegate_auralinux.h"
55 // Default placeholder text color.
56 const SkColor kDefaultPlaceholderTextColor
= SK_ColorLTGRAY
;
58 const int kNoCommand
= 0;
60 void ConvertRectToScreen(const View
* src
, gfx::Rect
* r
) {
63 gfx::Point new_origin
= r
->origin();
64 View::ConvertPointToScreen(src
, &new_origin
);
65 r
->set_origin(new_origin
);
68 // Get the drag selection timer delay, respecting animation scaling for testing.
69 int GetDragSelectionDelay() {
70 switch (ui::ScopedAnimationDurationScaleMode::duration_scale_mode()) {
71 case ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION
: return 100;
72 case ui::ScopedAnimationDurationScaleMode::FAST_DURATION
: return 25;
73 case ui::ScopedAnimationDurationScaleMode::SLOW_DURATION
: return 400;
74 case ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION
: return 1;
75 case ui::ScopedAnimationDurationScaleMode::ZERO_DURATION
: return 0;
80 // Get the default command for a given key |event| and selection state.
81 int GetCommandForKeyEvent(const ui::KeyEvent
& event
, bool has_selection
) {
82 if (event
.type() != ui::ET_KEY_PRESSED
|| event
.IsUnicodeKeyCode())
85 const bool shift
= event
.IsShiftDown();
86 const bool control
= event
.IsControlDown();
87 const bool alt
= event
.IsAltDown() || event
.IsAltGrDown();
88 switch (event
.key_code()) {
90 if (control
&& !shift
&& !alt
)
92 return (control
&& shift
&& !alt
) ? IDS_APP_REDO
: kNoCommand
;
94 return (control
&& !alt
) ? IDS_APP_REDO
: kNoCommand
;
96 return (control
&& !alt
) ? IDS_APP_SELECT_ALL
: kNoCommand
;
98 return (control
&& !alt
) ? IDS_APP_CUT
: kNoCommand
;
100 return (control
&& !alt
) ? IDS_APP_COPY
: kNoCommand
;
102 return (control
&& !alt
) ? IDS_APP_PASTE
: kNoCommand
;
104 // Ignore alt+right, which may be a browser navigation shortcut.
108 return control
? IDS_MOVE_WORD_RIGHT
: IDS_MOVE_RIGHT
;
109 return control
? IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION
:
110 IDS_MOVE_RIGHT_AND_MODIFY_SELECTION
;
112 // Ignore alt+left, which may be a browser navigation shortcut.
116 return control
? IDS_MOVE_WORD_LEFT
: IDS_MOVE_LEFT
;
117 return control
? IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION
:
118 IDS_MOVE_LEFT_AND_MODIFY_SELECTION
;
120 return shift
? IDS_MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION
:
121 IDS_MOVE_TO_BEGINNING_OF_LINE
;
123 return shift
? IDS_MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION
:
124 IDS_MOVE_TO_END_OF_LINE
;
126 if (!control
|| has_selection
)
127 return IDS_DELETE_BACKWARD
;
128 #if defined(OS_LINUX)
129 // Only erase by line break on Linux and ChromeOS.
131 return IDS_DELETE_TO_BEGINNING_OF_LINE
;
133 return IDS_DELETE_WORD_BACKWARD
;
134 case ui::VKEY_DELETE
:
135 if (!control
|| has_selection
)
136 return (shift
&& has_selection
) ? IDS_APP_CUT
: IDS_DELETE_FORWARD
;
137 #if defined(OS_LINUX)
138 // Only erase by line break on Linux and ChromeOS.
140 return IDS_DELETE_TO_END_OF_LINE
;
142 return IDS_DELETE_WORD_FORWARD
;
143 case ui::VKEY_INSERT
:
144 if (control
&& !shift
)
146 return (shift
&& !control
) ? IDS_APP_PASTE
: kNoCommand
;
152 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
153 // Convert a custom text edit |command| to the equivalent views command ID.
154 int GetViewsCommand(const ui::TextEditCommandAuraLinux
& command
, bool rtl
) {
155 const bool select
= command
.extend_selection();
156 switch (command
.command_id()) {
157 case ui::TextEditCommandAuraLinux::COPY
:
159 case ui::TextEditCommandAuraLinux::CUT
:
161 case ui::TextEditCommandAuraLinux::DELETE_BACKWARD
:
162 return IDS_DELETE_BACKWARD
;
163 case ui::TextEditCommandAuraLinux::DELETE_FORWARD
:
164 return IDS_DELETE_FORWARD
;
165 case ui::TextEditCommandAuraLinux::DELETE_TO_BEGINING_OF_LINE
:
166 case ui::TextEditCommandAuraLinux::DELETE_TO_BEGINING_OF_PARAGRAPH
:
167 return IDS_DELETE_TO_BEGINNING_OF_LINE
;
168 case ui::TextEditCommandAuraLinux::DELETE_TO_END_OF_LINE
:
169 case ui::TextEditCommandAuraLinux::DELETE_TO_END_OF_PARAGRAPH
:
170 return IDS_DELETE_TO_END_OF_LINE
;
171 case ui::TextEditCommandAuraLinux::DELETE_WORD_BACKWARD
:
172 return IDS_DELETE_WORD_BACKWARD
;
173 case ui::TextEditCommandAuraLinux::DELETE_WORD_FORWARD
:
174 return IDS_DELETE_WORD_FORWARD
;
175 case ui::TextEditCommandAuraLinux::INSERT_TEXT
:
177 case ui::TextEditCommandAuraLinux::MOVE_BACKWARD
:
179 return select
? IDS_MOVE_RIGHT_AND_MODIFY_SELECTION
: IDS_MOVE_RIGHT
;
180 return select
? IDS_MOVE_LEFT_AND_MODIFY_SELECTION
: IDS_MOVE_LEFT
;
181 case ui::TextEditCommandAuraLinux::MOVE_DOWN
:
182 return IDS_MOVE_DOWN
;
183 case ui::TextEditCommandAuraLinux::MOVE_FORWARD
:
185 return select
? IDS_MOVE_LEFT_AND_MODIFY_SELECTION
: IDS_MOVE_LEFT
;
186 return select
? IDS_MOVE_RIGHT_AND_MODIFY_SELECTION
: IDS_MOVE_RIGHT
;
187 case ui::TextEditCommandAuraLinux::MOVE_LEFT
:
188 return select
? IDS_MOVE_LEFT_AND_MODIFY_SELECTION
: IDS_MOVE_LEFT
;
189 case ui::TextEditCommandAuraLinux::MOVE_PAGE_DOWN
:
190 case ui::TextEditCommandAuraLinux::MOVE_PAGE_UP
:
192 case ui::TextEditCommandAuraLinux::MOVE_RIGHT
:
193 return select
? IDS_MOVE_RIGHT_AND_MODIFY_SELECTION
: IDS_MOVE_RIGHT
;
194 case ui::TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_DOCUMENT
:
195 case ui::TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_LINE
:
196 case ui::TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_PARAGRAPH
:
197 return select
? IDS_MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION
:
198 IDS_MOVE_TO_BEGINNING_OF_LINE
;
199 case ui::TextEditCommandAuraLinux::MOVE_TO_END_OF_DOCUMENT
:
200 case ui::TextEditCommandAuraLinux::MOVE_TO_END_OF_LINE
:
201 case ui::TextEditCommandAuraLinux::MOVE_TO_END_OF_PARAGRAPH
:
202 return select
? IDS_MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION
:
203 IDS_MOVE_TO_END_OF_LINE
;
204 case ui::TextEditCommandAuraLinux::MOVE_UP
:
206 case ui::TextEditCommandAuraLinux::MOVE_WORD_BACKWARD
:
208 return select
? IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION
:
211 return select
? IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION
:
213 case ui::TextEditCommandAuraLinux::MOVE_WORD_FORWARD
:
215 return select
? IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION
:
218 return select
? IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION
:
220 case ui::TextEditCommandAuraLinux::MOVE_WORD_LEFT
:
221 return select
? IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION
:
223 case ui::TextEditCommandAuraLinux::MOVE_WORD_RIGHT
:
224 return select
? IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION
:
226 case ui::TextEditCommandAuraLinux::PASTE
:
227 return IDS_APP_PASTE
;
228 case ui::TextEditCommandAuraLinux::SELECT_ALL
:
229 return IDS_APP_SELECT_ALL
;
230 case ui::TextEditCommandAuraLinux::SET_MARK
:
231 case ui::TextEditCommandAuraLinux::UNSELECT
:
232 case ui::TextEditCommandAuraLinux::INVALID_COMMAND
:
242 const char Textfield::kViewClassName
[] = "Textfield";
243 const int Textfield::kTextPadding
= 3;
246 size_t Textfield::GetCaretBlinkMs() {
247 static const size_t default_value
= 500;
249 static const size_t system_value
= ::GetCaretBlinkTime();
250 if (system_value
!= 0)
251 return (system_value
== INFINITE
) ? 0 : system_value
;
253 return default_value
;
256 Textfield::Textfield()
257 : model_(new TextfieldModel(this)),
259 scheduled_edit_command_(kNoCommand
),
261 default_width_in_chars_(0),
262 use_default_text_color_(true),
263 use_default_background_color_(true),
264 use_default_selection_text_color_(true),
265 use_default_selection_background_color_(true),
266 text_color_(SK_ColorBLACK
),
267 background_color_(SK_ColorWHITE
),
268 selection_text_color_(SK_ColorWHITE
),
269 selection_background_color_(SK_ColorBLUE
),
270 placeholder_text_color_(kDefaultPlaceholderTextColor
),
271 text_input_type_(ui::TEXT_INPUT_TYPE_TEXT
),
272 text_input_flags_(0),
273 performing_user_action_(false),
274 skip_input_method_cancel_composition_(false),
275 cursor_visible_(false),
276 drop_cursor_visible_(false),
277 initiating_drag_(false),
278 aggregated_clicks_(0),
279 drag_start_display_offset_(0),
280 touch_handles_hidden_due_to_scroll_(false),
281 weak_ptr_factory_(this) {
282 set_context_menu_controller(this);
283 set_drag_controller(this);
284 SetBorder(scoped_ptr
<Border
>(new FocusableBorder()));
287 if (ViewsDelegate::GetInstance()) {
288 password_reveal_duration_
=
289 ViewsDelegate::GetInstance()
290 ->GetDefaultTextfieldObscuredRevealDuration();
293 // These allow BrowserView to pass edit commands from the Chrome menu to us
294 // when we're focused by simply asking the FocusManager to
295 // ProcessAccelerator() with the relevant accelerators.
296 AddAccelerator(ui::Accelerator(ui::VKEY_X
, ui::EF_CONTROL_DOWN
));
297 AddAccelerator(ui::Accelerator(ui::VKEY_C
, ui::EF_CONTROL_DOWN
));
298 AddAccelerator(ui::Accelerator(ui::VKEY_V
, ui::EF_CONTROL_DOWN
));
301 Textfield::~Textfield() {
302 if (GetInputMethod()) {
303 // The textfield should have been blurred before destroy.
304 DCHECK(this != GetInputMethod()->GetTextInputClient());
308 void Textfield::SetReadOnly(bool read_only
) {
309 // Update read-only without changing the focusable state (or active, etc.).
310 read_only_
= read_only
;
311 if (GetInputMethod())
312 GetInputMethod()->OnTextInputTypeChanged(this);
313 SetColor(GetTextColor());
314 UpdateBackgroundColor();
317 void Textfield::SetTextInputType(ui::TextInputType type
) {
318 GetRenderText()->SetObscured(type
== ui::TEXT_INPUT_TYPE_PASSWORD
);
319 text_input_type_
= type
;
320 OnCaretBoundsChanged();
321 if (GetInputMethod())
322 GetInputMethod()->OnTextInputTypeChanged(this);
326 void Textfield::SetTextInputFlags(int flags
) {
327 text_input_flags_
= flags
;
330 void Textfield::SetText(const base::string16
& new_text
) {
331 model_
->SetText(new_text
);
332 OnCaretBoundsChanged();
334 NotifyAccessibilityEvent(ui::AX_EVENT_TEXT_CHANGED
, true);
337 void Textfield::AppendText(const base::string16
& new_text
) {
338 if (new_text
.empty())
340 model_
->Append(new_text
);
341 OnCaretBoundsChanged();
345 void Textfield::InsertOrReplaceText(const base::string16
& new_text
) {
346 if (new_text
.empty())
348 model_
->InsertText(new_text
);
349 OnCaretBoundsChanged();
353 base::i18n::TextDirection
Textfield::GetTextDirection() const {
354 return GetRenderText()->GetDisplayTextDirection();
357 base::string16
Textfield::GetSelectedText() const {
358 return model_
->GetSelectedText();
361 void Textfield::SelectAll(bool reversed
) {
362 model_
->SelectAll(reversed
);
363 UpdateSelectionClipboard();
364 UpdateAfterChange(false, true);
367 void Textfield::SelectWordAt(const gfx::Point
& point
) {
368 model_
->MoveCursorTo(point
, false);
369 model_
->SelectWord();
370 UpdateAfterChange(false, true);
373 void Textfield::ClearSelection() {
374 model_
->ClearSelection();
375 UpdateAfterChange(false, true);
378 bool Textfield::HasSelection() const {
379 return !GetSelectedRange().is_empty();
382 SkColor
Textfield::GetTextColor() const {
383 if (!use_default_text_color_
)
386 return GetNativeTheme()->GetSystemColor(read_only() ?
387 ui::NativeTheme::kColorId_TextfieldReadOnlyColor
:
388 ui::NativeTheme::kColorId_TextfieldDefaultColor
);
391 void Textfield::SetTextColor(SkColor color
) {
393 use_default_text_color_
= false;
397 void Textfield::UseDefaultTextColor() {
398 use_default_text_color_
= true;
399 SetColor(GetTextColor());
402 SkColor
Textfield::GetBackgroundColor() const {
403 if (!use_default_background_color_
)
404 return background_color_
;
406 return GetNativeTheme()->GetSystemColor(read_only() ?
407 ui::NativeTheme::kColorId_TextfieldReadOnlyBackground
:
408 ui::NativeTheme::kColorId_TextfieldDefaultBackground
);
411 void Textfield::SetBackgroundColor(SkColor color
) {
412 background_color_
= color
;
413 use_default_background_color_
= false;
414 UpdateBackgroundColor();
417 void Textfield::UseDefaultBackgroundColor() {
418 use_default_background_color_
= true;
419 UpdateBackgroundColor();
422 SkColor
Textfield::GetSelectionTextColor() const {
423 return use_default_selection_text_color_
?
424 GetNativeTheme()->GetSystemColor(
425 ui::NativeTheme::kColorId_TextfieldSelectionColor
) :
426 selection_text_color_
;
429 void Textfield::SetSelectionTextColor(SkColor color
) {
430 selection_text_color_
= color
;
431 use_default_selection_text_color_
= false;
432 GetRenderText()->set_selection_color(GetSelectionTextColor());
436 void Textfield::UseDefaultSelectionTextColor() {
437 use_default_selection_text_color_
= true;
438 GetRenderText()->set_selection_color(GetSelectionTextColor());
442 void Textfield::SetShadows(const gfx::ShadowValues
& shadows
) {
443 GetRenderText()->set_shadows(shadows
);
447 SkColor
Textfield::GetSelectionBackgroundColor() const {
448 return use_default_selection_background_color_
?
449 GetNativeTheme()->GetSystemColor(
450 ui::NativeTheme::kColorId_TextfieldSelectionBackgroundFocused
) :
451 selection_background_color_
;
454 void Textfield::SetSelectionBackgroundColor(SkColor color
) {
455 selection_background_color_
= color
;
456 use_default_selection_background_color_
= false;
457 GetRenderText()->set_selection_background_focused_color(
458 GetSelectionBackgroundColor());
462 void Textfield::UseDefaultSelectionBackgroundColor() {
463 use_default_selection_background_color_
= true;
464 GetRenderText()->set_selection_background_focused_color(
465 GetSelectionBackgroundColor());
469 bool Textfield::GetCursorEnabled() const {
470 return GetRenderText()->cursor_enabled();
473 void Textfield::SetCursorEnabled(bool enabled
) {
474 GetRenderText()->SetCursorEnabled(enabled
);
477 const gfx::FontList
& Textfield::GetFontList() const {
478 return GetRenderText()->font_list();
481 void Textfield::SetFontList(const gfx::FontList
& font_list
) {
482 GetRenderText()->SetFontList(font_list
);
483 OnCaretBoundsChanged();
484 PreferredSizeChanged();
487 base::string16
Textfield::GetPlaceholderText() const {
488 return placeholder_text_
;
491 gfx::HorizontalAlignment
Textfield::GetHorizontalAlignment() const {
492 return GetRenderText()->horizontal_alignment();
495 void Textfield::SetHorizontalAlignment(gfx::HorizontalAlignment alignment
) {
496 GetRenderText()->SetHorizontalAlignment(alignment
);
499 void Textfield::ShowImeIfNeeded() {
500 if (enabled() && !read_only())
501 GetInputMethod()->ShowImeIfNeeded();
504 bool Textfield::IsIMEComposing() const {
505 return model_
->HasCompositionText();
508 const gfx::Range
& Textfield::GetSelectedRange() const {
509 return GetRenderText()->selection();
512 void Textfield::SelectRange(const gfx::Range
& range
) {
513 model_
->SelectRange(range
);
514 UpdateAfterChange(false, true);
517 const gfx::SelectionModel
& Textfield::GetSelectionModel() const {
518 return GetRenderText()->selection_model();
521 void Textfield::SelectSelectionModel(const gfx::SelectionModel
& sel
) {
522 model_
->SelectSelectionModel(sel
);
523 UpdateAfterChange(false, true);
526 size_t Textfield::GetCursorPosition() const {
527 return model_
->GetCursorPosition();
530 void Textfield::SetColor(SkColor value
) {
531 GetRenderText()->SetColor(value
);
535 void Textfield::ApplyColor(SkColor value
, const gfx::Range
& range
) {
536 GetRenderText()->ApplyColor(value
, range
);
540 void Textfield::SetStyle(gfx::TextStyle style
, bool value
) {
541 GetRenderText()->SetStyle(style
, value
);
545 void Textfield::ApplyStyle(gfx::TextStyle style
,
547 const gfx::Range
& range
) {
548 GetRenderText()->ApplyStyle(style
, value
, range
);
552 void Textfield::ClearEditHistory() {
553 model_
->ClearEditHistory();
556 void Textfield::SetAccessibleName(const base::string16
& name
) {
557 accessible_name_
= name
;
560 void Textfield::ExecuteCommand(int command_id
) {
561 ExecuteCommand(command_id
, ui::EF_NONE
);
564 void Textfield::SetFocusPainter(scoped_ptr
<Painter
> focus_painter
) {
565 focus_painter_
= focus_painter
.Pass();
568 bool Textfield::HasTextBeingDragged() {
569 return initiating_drag_
;
572 ////////////////////////////////////////////////////////////////////////////////
573 // Textfield, View overrides:
575 gfx::Insets
Textfield::GetInsets() const {
576 gfx::Insets insets
= View::GetInsets();
577 insets
+= gfx::Insets(kTextPadding
, kTextPadding
, kTextPadding
, kTextPadding
);
581 int Textfield::GetBaseline() const {
582 return GetInsets().top() + GetRenderText()->GetBaseline();
585 gfx::Size
Textfield::GetPreferredSize() const {
586 const gfx::Insets
& insets
= GetInsets();
587 return gfx::Size(GetFontList().GetExpectedTextWidth(default_width_in_chars_
) +
588 insets
.width(), GetFontList().GetHeight() + insets
.height());
591 const char* Textfield::GetClassName() const {
592 return kViewClassName
;
595 gfx::NativeCursor
Textfield::GetCursor(const ui::MouseEvent
& event
) {
596 bool in_selection
= GetRenderText()->IsPointInSelection(event
.location());
597 bool drag_event
= event
.type() == ui::ET_MOUSE_DRAGGED
;
598 bool text_cursor
= !initiating_drag_
&& (drag_event
|| !in_selection
);
599 return text_cursor
? GetNativeIBeamCursor() : gfx::kNullCursor
;
602 bool Textfield::OnMousePressed(const ui::MouseEvent
& event
) {
603 TrackMouseClicks(event
);
605 if (!controller_
|| !controller_
->HandleMouseEvent(this, event
)) {
606 if (event
.IsOnlyLeftMouseButton() || event
.IsOnlyRightMouseButton()) {
611 if (event
.IsOnlyLeftMouseButton()) {
612 OnBeforeUserAction();
613 initiating_drag_
= false;
614 switch (aggregated_clicks_
) {
616 if (GetRenderText()->IsPointInSelection(event
.location()))
617 initiating_drag_
= true;
619 MoveCursorTo(event
.location(), event
.IsShiftDown());
622 SelectWordAt(event
.location());
623 double_click_word_
= GetRenderText()->selection();
634 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
635 if (event
.IsOnlyMiddleMouseButton()) {
636 if (GetRenderText()->IsPointInSelection(event
.location())) {
637 OnBeforeUserAction();
639 ui::ScopedClipboardWriter(
640 ui::CLIPBOARD_TYPE_SELECTION
).WriteText(base::string16());
642 } else if (!read_only()) {
643 PasteSelectionClipboard(event
);
652 bool Textfield::OnMouseDragged(const ui::MouseEvent
& event
) {
653 last_drag_location_
= event
.location();
655 // Don't adjust the cursor on a potential drag and drop, or if the mouse
656 // movement from the last mouse click does not exceed the drag threshold.
657 if (initiating_drag_
|| !event
.IsOnlyLeftMouseButton() ||
658 !ExceededDragThreshold(last_drag_location_
- last_click_location_
)) {
662 // A timer is used to continuously scroll while selecting beyond side edges.
663 const int x
= event
.location().x();
664 if ((x
>= 0 && x
<= width()) || GetDragSelectionDelay() == 0) {
665 drag_selection_timer_
.Stop();
666 SelectThroughLastDragLocation();
667 } else if (!drag_selection_timer_
.IsRunning()) {
668 // Select through the edge of the visible text, then start the scroll timer.
669 last_drag_location_
.set_x(std::min(std::max(0, x
), width()));
670 SelectThroughLastDragLocation();
671 drag_selection_timer_
.Start(
672 FROM_HERE
, base::TimeDelta::FromMilliseconds(GetDragSelectionDelay()),
673 this, &Textfield::SelectThroughLastDragLocation
);
679 void Textfield::OnMouseReleased(const ui::MouseEvent
& event
) {
680 OnBeforeUserAction();
681 drag_selection_timer_
.Stop();
682 // Cancel suspected drag initiations, the user was clicking in the selection.
683 if (initiating_drag_
)
684 MoveCursorTo(event
.location(), false);
685 initiating_drag_
= false;
686 UpdateSelectionClipboard();
690 bool Textfield::OnKeyPressed(const ui::KeyEvent
& event
) {
691 int edit_command
= scheduled_edit_command_
;
692 scheduled_edit_command_
= kNoCommand
;
694 // Since HandleKeyEvent() might destroy |this|, get a weak pointer and verify
695 // it isn't null before proceeding.
696 base::WeakPtr
<Textfield
> textfield(weak_ptr_factory_
.GetWeakPtr());
698 bool handled
= controller_
&& controller_
->HandleKeyEvent(this, event
);
703 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
704 ui::TextEditKeyBindingsDelegateAuraLinux
* delegate
=
705 ui::GetTextEditKeyBindingsDelegate();
706 std::vector
<ui::TextEditCommandAuraLinux
> commands
;
707 if (!handled
&& delegate
&& delegate
->MatchEvent(event
, &commands
)) {
708 const bool rtl
= GetTextDirection() == base::i18n::RIGHT_TO_LEFT
;
709 for (size_t i
= 0; i
< commands
.size(); ++i
) {
710 const int command
= GetViewsCommand(commands
[i
], rtl
);
711 if (IsCommandIdEnabled(command
)) {
712 ExecuteCommand(command
);
720 if (edit_command
== kNoCommand
)
721 edit_command
= GetCommandForKeyEvent(event
, HasSelection());
723 if (!handled
&& IsCommandIdEnabled(edit_command
)) {
724 ExecuteCommand(edit_command
);
730 void Textfield::OnGestureEvent(ui::GestureEvent
* event
) {
731 switch (event
->type()) {
732 case ui::ET_GESTURE_TAP_DOWN
:
737 case ui::ET_GESTURE_TAP
:
738 if (event
->details().tap_count() == 1) {
739 // If tap is on the selection and touch handles are not present, handles
740 // should be shown without changing selection. Otherwise, cursor should
741 // be moved to the tap location.
742 if (touch_selection_controller_
||
743 !GetRenderText()->IsPointInSelection(event
->location())) {
744 OnBeforeUserAction();
745 MoveCursorTo(event
->location(), false);
748 } else if (event
->details().tap_count() == 2) {
749 OnBeforeUserAction();
750 SelectWordAt(event
->location());
753 OnBeforeUserAction();
757 CreateTouchSelectionControllerAndNotifyIt();
760 base::win::DisplayVirtualKeyboard();
764 case ui::ET_GESTURE_LONG_PRESS
:
765 if (!GetRenderText()->IsPointInSelection(event
->location())) {
766 // If long-press happens outside selection, select word and try to
767 // activate touch selection.
768 OnBeforeUserAction();
769 SelectWordAt(event
->location());
771 CreateTouchSelectionControllerAndNotifyIt();
772 // If touch selection activated successfully, mark event as handled so
773 // that the regular context menu is not shown.
774 if (touch_selection_controller_
)
777 // If long-press happens on the selection, deactivate touch selection
778 // and try to initiate drag-drop. If drag-drop is not enabled, context
779 // menu will be shown. Event is not marked as handled to let Views
780 // handle drag-drop or context menu.
781 DestroyTouchSelection();
782 initiating_drag_
= switches::IsTouchDragDropEnabled();
785 case ui::ET_GESTURE_LONG_TAP
:
786 // If touch selection is enabled, the context menu on long tap will be
787 // shown by the |touch_selection_controller_|, hence we mark the event
788 // handled so Views does not try to show context menu on it.
789 if (touch_selection_controller_
)
792 case ui::ET_GESTURE_SCROLL_BEGIN
:
793 touch_handles_hidden_due_to_scroll_
= touch_selection_controller_
!= NULL
;
794 DestroyTouchSelection();
795 drag_start_location_
= event
->location();
796 drag_start_display_offset_
=
797 GetRenderText()->GetUpdatedDisplayOffset().x();
800 case ui::ET_GESTURE_SCROLL_UPDATE
: {
801 int new_offset
= drag_start_display_offset_
+ event
->location().x() -
802 drag_start_location_
.x();
803 GetRenderText()->SetDisplayOffset(new_offset
);
808 case ui::ET_GESTURE_SCROLL_END
:
809 case ui::ET_SCROLL_FLING_START
:
810 if (touch_handles_hidden_due_to_scroll_
) {
811 CreateTouchSelectionControllerAndNotifyIt();
812 touch_handles_hidden_due_to_scroll_
= false;
821 // This function is called by BrowserView to execute clipboard commands.
822 bool Textfield::AcceleratorPressed(const ui::Accelerator
& accelerator
) {
823 ui::KeyEvent
event(accelerator
.type(), accelerator
.key_code(),
824 accelerator
.modifiers());
825 ExecuteCommand(GetCommandForKeyEvent(event
, HasSelection()));
829 bool Textfield::CanHandleAccelerators() const {
830 return GetRenderText()->focused() && View::CanHandleAccelerators();
833 void Textfield::AboutToRequestFocusFromTabTraversal(bool reverse
) {
837 bool Textfield::SkipDefaultKeyEventProcessing(const ui::KeyEvent
& event
) {
838 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
839 // Skip any accelerator handling that conflicts with custom keybindings.
840 ui::TextEditKeyBindingsDelegateAuraLinux
* delegate
=
841 ui::GetTextEditKeyBindingsDelegate();
842 std::vector
<ui::TextEditCommandAuraLinux
> commands
;
843 if (delegate
&& delegate
->MatchEvent(event
, &commands
)) {
844 const bool rtl
= GetTextDirection() == base::i18n::RIGHT_TO_LEFT
;
845 for (size_t i
= 0; i
< commands
.size(); ++i
)
846 if (IsCommandIdEnabled(GetViewsCommand(commands
[i
], rtl
)))
851 // Skip backspace accelerator handling; editable textfields handle this key.
852 // Also skip processing Windows [Alt]+<num-pad digit> Unicode alt-codes.
853 const bool is_backspace
= event
.key_code() == ui::VKEY_BACK
;
854 return (is_backspace
&& !read_only()) || event
.IsUnicodeKeyCode();
857 bool Textfield::GetDropFormats(
859 std::set
<OSExchangeData::CustomFormat
>* custom_formats
) {
860 if (!enabled() || read_only())
862 // TODO(msw): Can we support URL, FILENAME, etc.?
863 *formats
= ui::OSExchangeData::STRING
;
865 controller_
->AppendDropFormats(formats
, custom_formats
);
869 bool Textfield::CanDrop(const OSExchangeData
& data
) {
871 std::set
<OSExchangeData::CustomFormat
> custom_formats
;
872 GetDropFormats(&formats
, &custom_formats
);
873 return enabled() && !read_only() &&
874 data
.HasAnyFormat(formats
, custom_formats
);
877 int Textfield::OnDragUpdated(const ui::DropTargetEvent
& event
) {
878 DCHECK(CanDrop(event
.data()));
879 gfx::RenderText
* render_text
= GetRenderText();
880 const gfx::Range
& selection
= render_text
->selection();
881 drop_cursor_position_
= render_text
->FindCursorPosition(event
.location());
882 bool in_selection
= !selection
.is_empty() &&
883 selection
.Contains(gfx::Range(drop_cursor_position_
.caret_pos()));
884 drop_cursor_visible_
= !in_selection
;
885 // TODO(msw): Pan over text when the user drags to the visible text edge.
886 OnCaretBoundsChanged();
889 if (initiating_drag_
) {
891 return ui::DragDropTypes::DRAG_NONE
;
892 return event
.IsControlDown() ? ui::DragDropTypes::DRAG_COPY
:
893 ui::DragDropTypes::DRAG_MOVE
;
895 return ui::DragDropTypes::DRAG_COPY
| ui::DragDropTypes::DRAG_MOVE
;
898 void Textfield::OnDragExited() {
899 drop_cursor_visible_
= false;
903 int Textfield::OnPerformDrop(const ui::DropTargetEvent
& event
) {
904 DCHECK(CanDrop(event
.data()));
905 drop_cursor_visible_
= false;
908 int drag_operation
= controller_
->OnDrop(event
.data());
909 if (drag_operation
!= ui::DragDropTypes::DRAG_NONE
)
910 return drag_operation
;
913 gfx::RenderText
* render_text
= GetRenderText();
914 DCHECK(!initiating_drag_
||
915 !render_text
->IsPointInSelection(event
.location()));
916 OnBeforeUserAction();
917 skip_input_method_cancel_composition_
= true;
919 gfx::SelectionModel drop_destination_model
=
920 render_text
->FindCursorPosition(event
.location());
921 base::string16 new_text
;
922 event
.data().GetString(&new_text
);
924 // Delete the current selection for a drag and drop within this view.
925 const bool move
= initiating_drag_
&& !event
.IsControlDown() &&
926 event
.source_operations() & ui::DragDropTypes::DRAG_MOVE
;
928 // Adjust the drop destination if it is on or after the current selection.
929 size_t pos
= drop_destination_model
.caret_pos();
930 pos
-= render_text
->selection().Intersect(gfx::Range(0, pos
)).length();
931 model_
->DeleteSelectionAndInsertTextAt(new_text
, pos
);
933 model_
->MoveCursorTo(drop_destination_model
);
934 // Drop always inserts text even if the textfield is not in insert mode.
935 model_
->InsertText(new_text
);
937 skip_input_method_cancel_composition_
= false;
938 UpdateAfterChange(true, true);
940 return move
? ui::DragDropTypes::DRAG_MOVE
: ui::DragDropTypes::DRAG_COPY
;
943 void Textfield::OnDragDone() {
944 initiating_drag_
= false;
945 drop_cursor_visible_
= false;
948 void Textfield::GetAccessibleState(ui::AXViewState
* state
) {
949 state
->role
= ui::AX_ROLE_TEXT_FIELD
;
950 state
->name
= accessible_name_
;
952 state
->AddStateFlag(ui::AX_STATE_READ_ONLY
);
953 if (text_input_type_
== ui::TEXT_INPUT_TYPE_PASSWORD
) {
954 state
->AddStateFlag(ui::AX_STATE_PROTECTED
);
955 state
->value
= base::string16(text().size(), '*');
957 state
->value
= text();
959 const gfx::Range range
= GetSelectedRange();
960 state
->selection_start
= range
.start();
961 state
->selection_end
= range
.end();
964 state
->set_value_callback
=
965 base::Bind(&Textfield::AccessibilitySetValue
,
966 weak_ptr_factory_
.GetWeakPtr());
970 void Textfield::OnBoundsChanged(const gfx::Rect
& previous_bounds
) {
971 // Textfield insets include a reasonable amount of whitespace on all sides of
972 // the default font list. Fallback fonts with larger heights may paint over
973 // the vertical whitespace as needed. Alternate solutions involve undesirable
974 // behavior like changing the default font size, shrinking some fallback fonts
975 // beyond their legibility, or enlarging controls dynamically with content.
976 gfx::Rect bounds
= GetContentsBounds();
977 // GetContentsBounds() does not actually use the local GetInsets() override.
978 bounds
.Inset(gfx::Insets(0, kTextPadding
, 0, kTextPadding
));
979 GetRenderText()->SetDisplayRect(bounds
);
980 OnCaretBoundsChanged();
983 bool Textfield::GetNeedsNotificationWhenVisibleBoundsChange() const {
987 void Textfield::OnVisibleBoundsChanged() {
988 if (touch_selection_controller_
)
989 touch_selection_controller_
->SelectionChanged();
992 void Textfield::OnEnabledChanged() {
993 View::OnEnabledChanged();
994 if (GetInputMethod())
995 GetInputMethod()->OnTextInputTypeChanged(this);
999 void Textfield::OnPaint(gfx::Canvas
* canvas
) {
1000 OnPaintBackground(canvas
);
1001 PaintTextAndCursor(canvas
);
1002 OnPaintBorder(canvas
);
1005 void Textfield::OnFocus() {
1006 GetRenderText()->set_focused(true);
1007 cursor_visible_
= true;
1009 if (GetInputMethod())
1010 GetInputMethod()->SetFocusedTextInputClient(this);
1011 OnCaretBoundsChanged();
1013 const size_t caret_blink_ms
= Textfield::GetCaretBlinkMs();
1014 if (caret_blink_ms
!= 0) {
1015 cursor_repaint_timer_
.Start(FROM_HERE
,
1016 base::TimeDelta::FromMilliseconds(caret_blink_ms
), this,
1017 &Textfield::UpdateCursor
);
1024 void Textfield::OnBlur() {
1025 GetRenderText()->set_focused(false);
1026 if (GetInputMethod())
1027 GetInputMethod()->DetachTextInputClient(this);
1028 cursor_repaint_timer_
.Stop();
1029 if (cursor_visible_
) {
1030 cursor_visible_
= false;
1034 DestroyTouchSelection();
1036 // Border typically draws focus indicator.
1040 gfx::Point
Textfield::GetKeyboardContextMenuLocation() {
1041 return GetCaretBounds().bottom_right();
1044 void Textfield::OnNativeThemeChanged(const ui::NativeTheme
* theme
) {
1045 gfx::RenderText
* render_text
= GetRenderText();
1046 render_text
->SetColor(GetTextColor());
1047 UpdateBackgroundColor();
1048 render_text
->set_cursor_color(GetTextColor());
1049 render_text
->set_selection_color(GetSelectionTextColor());
1050 render_text
->set_selection_background_focused_color(
1051 GetSelectionBackgroundColor());
1054 ////////////////////////////////////////////////////////////////////////////////
1055 // Textfield, TextfieldModel::Delegate overrides:
1057 void Textfield::OnCompositionTextConfirmedOrCleared() {
1058 if (!skip_input_method_cancel_composition_
)
1059 GetInputMethod()->CancelComposition(this);
1062 ////////////////////////////////////////////////////////////////////////////////
1063 // Textfield, ContextMenuController overrides:
1065 void Textfield::ShowContextMenuForView(View
* source
,
1066 const gfx::Point
& point
,
1067 ui::MenuSourceType source_type
) {
1068 UpdateContextMenu();
1069 ignore_result(context_menu_runner_
->RunMenuAt(GetWidget(),
1071 gfx::Rect(point
, gfx::Size()),
1072 MENU_ANCHOR_TOPLEFT
,
1076 ////////////////////////////////////////////////////////////////////////////////
1077 // Textfield, DragController overrides:
1079 void Textfield::WriteDragDataForView(View
* sender
,
1080 const gfx::Point
& press_pt
,
1081 OSExchangeData
* data
) {
1082 const base::string16
& selected_text(GetSelectedText());
1083 data
->SetString(selected_text
);
1084 Label
label(selected_text
, GetFontList());
1085 label
.SetBackgroundColor(GetBackgroundColor());
1086 label
.SetSubpixelRenderingEnabled(false);
1087 gfx::Size
size(label
.GetPreferredSize());
1088 gfx::NativeView native_view
= GetWidget()->GetNativeView();
1089 gfx::Display display
= gfx::Screen::GetScreenFor(native_view
)->
1090 GetDisplayNearestWindow(native_view
);
1091 size
.SetToMin(gfx::Size(display
.size().width(), height()));
1092 label
.SetBoundsRect(gfx::Rect(size
));
1093 scoped_ptr
<gfx::Canvas
> canvas(
1094 GetCanvasForDragImage(GetWidget(), label
.size()));
1095 label
.SetEnabledColor(GetTextColor());
1096 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
1097 // Desktop Linux Aura does not yet support transparency in drag images.
1098 canvas
->DrawColor(GetBackgroundColor());
1100 label
.Paint(ui::CanvasPainter(canvas
.get(), 1.f
).context());
1101 const gfx::Vector2d
kOffset(-15, 0);
1102 drag_utils::SetDragImageOnDataObject(*canvas
, kOffset
, data
);
1104 controller_
->OnWriteDragData(data
);
1107 int Textfield::GetDragOperationsForView(View
* sender
, const gfx::Point
& p
) {
1108 int drag_operations
= ui::DragDropTypes::DRAG_COPY
;
1109 if (!enabled() || text_input_type_
== ui::TEXT_INPUT_TYPE_PASSWORD
||
1110 !GetRenderText()->IsPointInSelection(p
)) {
1111 drag_operations
= ui::DragDropTypes::DRAG_NONE
;
1112 } else if (sender
== this && !read_only()) {
1114 ui::DragDropTypes::DRAG_MOVE
| ui::DragDropTypes::DRAG_COPY
;
1117 controller_
->OnGetDragOperationsForTextfield(&drag_operations
);
1118 return drag_operations
;
1121 bool Textfield::CanStartDragForView(View
* sender
,
1122 const gfx::Point
& press_pt
,
1123 const gfx::Point
& p
) {
1124 return initiating_drag_
&& GetRenderText()->IsPointInSelection(press_pt
);
1127 ////////////////////////////////////////////////////////////////////////////////
1128 // Textfield, ui::TouchEditable overrides:
1130 void Textfield::SelectRect(const gfx::Point
& start
, const gfx::Point
& end
) {
1131 if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE
)
1134 gfx::SelectionModel start_caret
= GetRenderText()->FindCursorPosition(start
);
1135 gfx::SelectionModel end_caret
= GetRenderText()->FindCursorPosition(end
);
1136 gfx::SelectionModel
selection(
1137 gfx::Range(start_caret
.caret_pos(), end_caret
.caret_pos()),
1138 end_caret
.caret_affinity());
1140 OnBeforeUserAction();
1141 SelectSelectionModel(selection
);
1142 OnAfterUserAction();
1145 void Textfield::MoveCaretTo(const gfx::Point
& point
) {
1146 SelectRect(point
, point
);
1149 void Textfield::GetSelectionEndPoints(ui::SelectionBound
* anchor
,
1150 ui::SelectionBound
* focus
) {
1151 gfx::RenderText
* render_text
= GetRenderText();
1152 const gfx::SelectionModel
& sel
= render_text
->selection_model();
1153 gfx::SelectionModel start_sel
=
1154 render_text
->GetSelectionModelForSelectionStart();
1155 gfx::Rect r1
= render_text
->GetCursorBounds(start_sel
, true);
1156 gfx::Rect r2
= render_text
->GetCursorBounds(sel
, true);
1158 anchor
->SetEdge(r1
.origin(), r1
.bottom_left());
1159 focus
->SetEdge(r2
.origin(), r2
.bottom_left());
1161 // Determine the SelectionBound's type for focus and anchor.
1162 // TODO(mfomitchev): Ideally we should have different logical directions for
1163 // start and end to support proper handle direction for mixed LTR/RTL text.
1164 const bool ltr
= GetTextDirection() != base::i18n::RIGHT_TO_LEFT
;
1165 size_t anchor_position_index
= sel
.selection().start();
1166 size_t focus_position_index
= sel
.selection().end();
1168 if (anchor_position_index
== focus_position_index
) {
1169 anchor
->set_type(ui::SelectionBound::CENTER
);
1170 focus
->set_type(ui::SelectionBound::CENTER
);
1171 } else if ((ltr
&& anchor_position_index
< focus_position_index
) ||
1172 (!ltr
&& anchor_position_index
> focus_position_index
)) {
1173 anchor
->set_type(ui::SelectionBound::LEFT
);
1174 focus
->set_type(ui::SelectionBound::RIGHT
);
1176 anchor
->set_type(ui::SelectionBound::RIGHT
);
1177 focus
->set_type(ui::SelectionBound::LEFT
);
1181 gfx::Rect
Textfield::GetBounds() {
1182 return GetLocalBounds();
1185 gfx::NativeView
Textfield::GetNativeView() const {
1186 return GetWidget()->GetNativeView();
1189 void Textfield::ConvertPointToScreen(gfx::Point
* point
) {
1190 View::ConvertPointToScreen(this, point
);
1193 void Textfield::ConvertPointFromScreen(gfx::Point
* point
) {
1194 View::ConvertPointFromScreen(this, point
);
1197 bool Textfield::DrawsHandles() {
1201 void Textfield::OpenContextMenu(const gfx::Point
& anchor
) {
1202 DestroyTouchSelection();
1203 ShowContextMenu(anchor
, ui::MENU_SOURCE_TOUCH_EDIT_MENU
);
1206 void Textfield::DestroyTouchSelection() {
1207 touch_selection_controller_
.reset();
1210 ////////////////////////////////////////////////////////////////////////////////
1211 // Textfield, ui::SimpleMenuModel::Delegate overrides:
1213 bool Textfield::IsCommandIdChecked(int command_id
) const {
1217 bool Textfield::IsCommandIdEnabled(int command_id
) const {
1218 base::string16 result
;
1219 bool editable
= !read_only();
1220 bool readable
= text_input_type_
!= ui::TEXT_INPUT_TYPE_PASSWORD
;
1221 switch (command_id
) {
1223 return editable
&& model_
->CanUndo();
1225 return editable
&& model_
->CanRedo();
1227 return editable
&& readable
&& model_
->HasSelection();
1229 return readable
&& model_
->HasSelection();
1231 ui::Clipboard::GetForCurrentThread()->ReadText(
1232 ui::CLIPBOARD_TYPE_COPY_PASTE
, &result
);
1233 return editable
&& !result
.empty();
1234 case IDS_APP_DELETE
:
1235 return editable
&& model_
->HasSelection();
1236 case IDS_APP_SELECT_ALL
:
1237 return !text().empty();
1238 case IDS_DELETE_FORWARD
:
1239 case IDS_DELETE_BACKWARD
:
1240 case IDS_DELETE_TO_BEGINNING_OF_LINE
:
1241 case IDS_DELETE_TO_END_OF_LINE
:
1242 case IDS_DELETE_WORD_BACKWARD
:
1243 case IDS_DELETE_WORD_FORWARD
:
1246 case IDS_MOVE_LEFT_AND_MODIFY_SELECTION
:
1247 case IDS_MOVE_RIGHT
:
1248 case IDS_MOVE_RIGHT_AND_MODIFY_SELECTION
:
1249 case IDS_MOVE_WORD_LEFT
:
1250 case IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION
:
1251 case IDS_MOVE_WORD_RIGHT
:
1252 case IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION
:
1253 case IDS_MOVE_TO_BEGINNING_OF_LINE
:
1254 case IDS_MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION
:
1255 case IDS_MOVE_TO_END_OF_LINE
:
1256 case IDS_MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION
:
1263 bool Textfield::GetAcceleratorForCommandId(int command_id
,
1264 ui::Accelerator
* accelerator
) {
1265 switch (command_id
) {
1267 *accelerator
= ui::Accelerator(ui::VKEY_Z
, ui::EF_CONTROL_DOWN
);
1271 *accelerator
= ui::Accelerator(ui::VKEY_X
, ui::EF_CONTROL_DOWN
);
1275 *accelerator
= ui::Accelerator(ui::VKEY_C
, ui::EF_CONTROL_DOWN
);
1279 *accelerator
= ui::Accelerator(ui::VKEY_V
, ui::EF_CONTROL_DOWN
);
1282 case IDS_APP_SELECT_ALL
:
1283 *accelerator
= ui::Accelerator(ui::VKEY_A
, ui::EF_CONTROL_DOWN
);
1291 void Textfield::ExecuteCommand(int command_id
, int event_flags
) {
1292 DestroyTouchSelection();
1293 if (!IsCommandIdEnabled(command_id
))
1296 bool text_changed
= false;
1297 bool cursor_changed
= false;
1298 bool rtl
= GetTextDirection() == base::i18n::RIGHT_TO_LEFT
;
1299 gfx::VisualCursorDirection begin
= rtl
? gfx::CURSOR_RIGHT
: gfx::CURSOR_LEFT
;
1300 gfx::VisualCursorDirection end
= rtl
? gfx::CURSOR_LEFT
: gfx::CURSOR_RIGHT
;
1301 gfx::SelectionModel selection_model
= GetSelectionModel();
1303 OnBeforeUserAction();
1304 switch (command_id
) {
1306 text_changed
= cursor_changed
= model_
->Undo();
1309 text_changed
= cursor_changed
= model_
->Redo();
1312 text_changed
= cursor_changed
= Cut();
1318 text_changed
= cursor_changed
= Paste();
1320 case IDS_APP_DELETE
:
1321 text_changed
= cursor_changed
= model_
->Delete();
1323 case IDS_APP_SELECT_ALL
:
1326 case IDS_DELETE_BACKWARD
:
1327 text_changed
= cursor_changed
= model_
->Backspace();
1329 case IDS_DELETE_FORWARD
:
1330 text_changed
= cursor_changed
= model_
->Delete();
1332 case IDS_DELETE_TO_END_OF_LINE
:
1333 model_
->MoveCursor(gfx::LINE_BREAK
, end
, true);
1334 text_changed
= cursor_changed
= model_
->Delete();
1336 case IDS_DELETE_TO_BEGINNING_OF_LINE
:
1337 model_
->MoveCursor(gfx::LINE_BREAK
, begin
, true);
1338 text_changed
= cursor_changed
= model_
->Backspace();
1340 case IDS_DELETE_WORD_BACKWARD
:
1341 model_
->MoveCursor(gfx::WORD_BREAK
, begin
, true);
1342 text_changed
= cursor_changed
= model_
->Backspace();
1344 case IDS_DELETE_WORD_FORWARD
:
1345 model_
->MoveCursor(gfx::WORD_BREAK
, end
, true);
1346 text_changed
= cursor_changed
= model_
->Delete();
1349 model_
->MoveCursor(gfx::CHARACTER_BREAK
, gfx::CURSOR_LEFT
, false);
1351 case IDS_MOVE_LEFT_AND_MODIFY_SELECTION
:
1352 model_
->MoveCursor(gfx::CHARACTER_BREAK
, gfx::CURSOR_LEFT
, true);
1354 case IDS_MOVE_RIGHT
:
1355 model_
->MoveCursor(gfx::CHARACTER_BREAK
, gfx::CURSOR_RIGHT
, false);
1357 case IDS_MOVE_RIGHT_AND_MODIFY_SELECTION
:
1358 model_
->MoveCursor(gfx::CHARACTER_BREAK
, gfx::CURSOR_RIGHT
, true);
1360 case IDS_MOVE_WORD_LEFT
:
1361 model_
->MoveCursor(gfx::WORD_BREAK
, gfx::CURSOR_LEFT
, false);
1363 case IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION
:
1364 model_
->MoveCursor(gfx::WORD_BREAK
, gfx::CURSOR_LEFT
, true);
1366 case IDS_MOVE_WORD_RIGHT
:
1367 model_
->MoveCursor(gfx::WORD_BREAK
, gfx::CURSOR_RIGHT
, false);
1369 case IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION
:
1370 model_
->MoveCursor(gfx::WORD_BREAK
, gfx::CURSOR_RIGHT
, true);
1372 case IDS_MOVE_TO_BEGINNING_OF_LINE
:
1373 model_
->MoveCursor(gfx::LINE_BREAK
, begin
, false);
1375 case IDS_MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION
:
1376 model_
->MoveCursor(gfx::LINE_BREAK
, begin
, true);
1378 case IDS_MOVE_TO_END_OF_LINE
:
1379 model_
->MoveCursor(gfx::LINE_BREAK
, end
, false);
1381 case IDS_MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION
:
1382 model_
->MoveCursor(gfx::LINE_BREAK
, end
, true);
1389 cursor_changed
|= GetSelectionModel() != selection_model
;
1391 UpdateSelectionClipboard();
1392 UpdateAfterChange(text_changed
, cursor_changed
);
1393 OnAfterUserAction();
1396 ////////////////////////////////////////////////////////////////////////////////
1397 // Textfield, ui::TextInputClient overrides:
1399 void Textfield::SetCompositionText(const ui::CompositionText
& composition
) {
1400 if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE
)
1403 OnBeforeUserAction();
1404 skip_input_method_cancel_composition_
= true;
1405 model_
->SetCompositionText(composition
);
1406 skip_input_method_cancel_composition_
= false;
1407 UpdateAfterChange(true, true);
1408 OnAfterUserAction();
1411 void Textfield::ConfirmCompositionText() {
1412 if (!model_
->HasCompositionText())
1415 OnBeforeUserAction();
1416 skip_input_method_cancel_composition_
= true;
1417 model_
->ConfirmCompositionText();
1418 skip_input_method_cancel_composition_
= false;
1419 UpdateAfterChange(true, true);
1420 OnAfterUserAction();
1423 void Textfield::ClearCompositionText() {
1424 if (!model_
->HasCompositionText())
1427 OnBeforeUserAction();
1428 skip_input_method_cancel_composition_
= true;
1429 model_
->CancelCompositionText();
1430 skip_input_method_cancel_composition_
= false;
1431 UpdateAfterChange(true, true);
1432 OnAfterUserAction();
1435 void Textfield::InsertText(const base::string16
& new_text
) {
1436 // TODO(suzhe): Filter invalid characters.
1437 if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE
|| new_text
.empty())
1440 OnBeforeUserAction();
1441 skip_input_method_cancel_composition_
= true;
1442 if (GetRenderText()->insert_mode())
1443 model_
->InsertText(new_text
);
1445 model_
->ReplaceText(new_text
);
1446 skip_input_method_cancel_composition_
= false;
1447 UpdateAfterChange(true, true);
1448 OnAfterUserAction();
1451 void Textfield::InsertChar(base::char16 ch
, int flags
) {
1452 const int kControlModifierMask
= ui::EF_CONTROL_DOWN
| ui::EF_ALT_DOWN
|
1453 ui::EF_COMMAND_DOWN
| ui::EF_ALTGR_DOWN
|
1456 // Filter out all control characters, including tab and new line characters,
1457 // and all characters with Alt modifier. But allow characters with the AltGr
1458 // modifier. On Windows AltGr is represented by Alt+Ctrl, and on Linux it's a
1459 // different flag that we don't care about.
1460 const bool should_insert_char
=
1461 ((ch
>= 0x20 && ch
< 0x7F) || ch
> 0x9F) &&
1462 (flags
& kControlModifierMask
) != ui::EF_ALT_DOWN
;
1463 if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE
|| !should_insert_char
)
1468 if (text_input_type_
== ui::TEXT_INPUT_TYPE_PASSWORD
&&
1469 password_reveal_duration_
!= base::TimeDelta()) {
1470 const size_t change_offset
= model_
->GetCursorPosition();
1471 DCHECK_GT(change_offset
, 0u);
1472 RevealPasswordChar(change_offset
- 1);
1476 ui::TextInputType
Textfield::GetTextInputType() const {
1477 if (read_only() || !enabled())
1478 return ui::TEXT_INPUT_TYPE_NONE
;
1479 return text_input_type_
;
1482 ui::TextInputMode
Textfield::GetTextInputMode() const {
1483 return ui::TEXT_INPUT_MODE_DEFAULT
;
1486 int Textfield::GetTextInputFlags() const {
1487 return text_input_flags_
;
1490 bool Textfield::CanComposeInline() const {
1494 gfx::Rect
Textfield::GetCaretBounds() const {
1495 gfx::Rect rect
= GetRenderText()->GetUpdatedCursorBounds();
1496 ConvertRectToScreen(this, &rect
);
1500 bool Textfield::GetCompositionCharacterBounds(uint32 index
,
1501 gfx::Rect
* rect
) const {
1503 if (!HasCompositionText())
1505 gfx::Range composition_range
;
1506 model_
->GetCompositionTextRange(&composition_range
);
1507 DCHECK(!composition_range
.is_empty());
1509 size_t text_index
= composition_range
.start() + index
;
1510 if (composition_range
.end() <= text_index
)
1512 gfx::RenderText
* render_text
= GetRenderText();
1513 if (!render_text
->IsValidCursorIndex(text_index
)) {
1514 text_index
= render_text
->IndexOfAdjacentGrapheme(
1515 text_index
, gfx::CURSOR_BACKWARD
);
1517 if (text_index
< composition_range
.start())
1519 const gfx::SelectionModel
caret(text_index
, gfx::CURSOR_BACKWARD
);
1520 *rect
= render_text
->GetCursorBounds(caret
, false);
1521 ConvertRectToScreen(this, rect
);
1525 bool Textfield::HasCompositionText() const {
1526 return model_
->HasCompositionText();
1529 bool Textfield::GetTextRange(gfx::Range
* range
) const {
1530 if (!ImeEditingAllowed())
1533 model_
->GetTextRange(range
);
1537 bool Textfield::GetCompositionTextRange(gfx::Range
* range
) const {
1538 if (!ImeEditingAllowed())
1541 model_
->GetCompositionTextRange(range
);
1545 bool Textfield::GetSelectionRange(gfx::Range
* range
) const {
1546 if (!ImeEditingAllowed())
1548 *range
= GetRenderText()->selection();
1552 bool Textfield::SetSelectionRange(const gfx::Range
& range
) {
1553 if (!ImeEditingAllowed() || !range
.IsValid())
1555 OnBeforeUserAction();
1557 OnAfterUserAction();
1561 bool Textfield::DeleteRange(const gfx::Range
& range
) {
1562 if (!ImeEditingAllowed() || range
.is_empty())
1565 OnBeforeUserAction();
1566 model_
->SelectRange(range
);
1567 if (model_
->HasSelection()) {
1568 model_
->DeleteSelection();
1569 UpdateAfterChange(true, true);
1571 OnAfterUserAction();
1575 bool Textfield::GetTextFromRange(const gfx::Range
& range
,
1576 base::string16
* range_text
) const {
1577 if (!ImeEditingAllowed() || !range
.IsValid())
1580 gfx::Range text_range
;
1581 if (!GetTextRange(&text_range
) || !text_range
.Contains(range
))
1584 *range_text
= model_
->GetTextFromRange(range
);
1588 void Textfield::OnInputMethodChanged() {}
1590 bool Textfield::ChangeTextDirectionAndLayoutAlignment(
1591 base::i18n::TextDirection direction
) {
1592 // Restore text directionality mode when the indicated direction matches the
1593 // current forced mode; otherwise, force the mode indicated. This helps users
1594 // manage BiDi text layout without getting stuck in forced LTR or RTL modes.
1595 const gfx::DirectionalityMode mode
= direction
== base::i18n::RIGHT_TO_LEFT
?
1596 gfx::DIRECTIONALITY_FORCE_RTL
: gfx::DIRECTIONALITY_FORCE_LTR
;
1597 if (mode
== GetRenderText()->directionality_mode())
1598 GetRenderText()->SetDirectionalityMode(gfx::DIRECTIONALITY_FROM_TEXT
);
1600 GetRenderText()->SetDirectionalityMode(mode
);
1605 void Textfield::ExtendSelectionAndDelete(size_t before
, size_t after
) {
1606 gfx::Range range
= GetRenderText()->selection();
1607 DCHECK_GE(range
.start(), before
);
1609 range
.set_start(range
.start() - before
);
1610 range
.set_end(range
.end() + after
);
1611 gfx::Range text_range
;
1612 if (GetTextRange(&text_range
) && text_range
.Contains(range
))
1616 void Textfield::EnsureCaretInRect(const gfx::Rect
& rect
) {}
1618 bool Textfield::IsEditCommandEnabled(int command_id
) {
1619 return IsCommandIdEnabled(command_id
);
1622 void Textfield::SetEditCommandForNextKeyEvent(int command_id
) {
1623 DCHECK_EQ(kNoCommand
, scheduled_edit_command_
);
1624 scheduled_edit_command_
= command_id
;
1627 ////////////////////////////////////////////////////////////////////////////////
1628 // Textfield, protected:
1630 void Textfield::DoInsertChar(base::char16 ch
) {
1631 OnBeforeUserAction();
1632 skip_input_method_cancel_composition_
= true;
1633 if (GetRenderText()->insert_mode())
1634 model_
->InsertChar(ch
);
1636 model_
->ReplaceChar(ch
);
1637 skip_input_method_cancel_composition_
= false;
1639 UpdateAfterChange(true, true);
1640 OnAfterUserAction();
1643 gfx::RenderText
* Textfield::GetRenderText() const {
1644 return model_
->render_text();
1647 base::string16
Textfield::GetSelectionClipboardText() const {
1648 base::string16 selection_clipboard_text
;
1649 ui::Clipboard::GetForCurrentThread()->ReadText(
1650 ui::CLIPBOARD_TYPE_SELECTION
, &selection_clipboard_text
);
1651 return selection_clipboard_text
;
1654 ////////////////////////////////////////////////////////////////////////////////
1655 // Textfield, private:
1657 void Textfield::AccessibilitySetValue(const base::string16
& new_value
) {
1664 void Textfield::UpdateBackgroundColor() {
1665 const SkColor color
= GetBackgroundColor();
1666 set_background(Background::CreateSolidBackground(color
));
1667 // Disable subpixel rendering when the background color is transparent
1668 // because it draws incorrect colors around the glyphs in that case.
1669 // See crbug.com/115198
1670 GetRenderText()->set_subpixel_rendering_suppressed(
1671 SkColorGetA(color
) != 0xFF);
1675 void Textfield::UpdateAfterChange(bool text_changed
, bool cursor_changed
) {
1678 controller_
->ContentsChanged(this, text());
1679 NotifyAccessibilityEvent(ui::AX_EVENT_TEXT_CHANGED
, true);
1681 if (cursor_changed
) {
1682 cursor_visible_
= true;
1684 if (cursor_repaint_timer_
.IsRunning())
1685 cursor_repaint_timer_
.Reset();
1686 if (!text_changed
) {
1687 // TEXT_CHANGED implies TEXT_SELECTION_CHANGED, so we only need to fire
1688 // this if only the selection changed.
1689 NotifyAccessibilityEvent(ui::AX_EVENT_TEXT_SELECTION_CHANGED
, true);
1692 if (text_changed
|| cursor_changed
) {
1693 OnCaretBoundsChanged();
1698 void Textfield::UpdateCursor() {
1699 const size_t caret_blink_ms
= Textfield::GetCaretBlinkMs();
1700 cursor_visible_
= !cursor_visible_
|| (caret_blink_ms
== 0);
1704 void Textfield::RepaintCursor() {
1705 gfx::Rect
r(GetRenderText()->GetUpdatedCursorBounds());
1706 r
.Inset(-1, -1, -1, -1);
1707 SchedulePaintInRect(r
);
1710 void Textfield::PaintTextAndCursor(gfx::Canvas
* canvas
) {
1711 TRACE_EVENT0("views", "Textfield::PaintTextAndCursor");
1714 // Draw placeholder text if needed.
1715 gfx::RenderText
* render_text
= GetRenderText();
1716 if (text().empty() && !GetPlaceholderText().empty()) {
1717 canvas
->DrawStringRect(GetPlaceholderText(), GetFontList(),
1718 placeholder_text_color(), render_text
->display_rect());
1721 // Draw the text, cursor, and selection.
1722 render_text
->set_cursor_visible(cursor_visible_
&& !drop_cursor_visible_
&&
1724 render_text
->Draw(canvas
);
1726 // Draw the detached drop cursor that marks where the text will be dropped.
1727 if (drop_cursor_visible_
)
1728 render_text
->DrawCursor(canvas
, drop_cursor_position_
);
1733 void Textfield::MoveCursorTo(const gfx::Point
& point
, bool select
) {
1734 if (model_
->MoveCursorTo(point
, select
))
1735 UpdateAfterChange(false, true);
1738 void Textfield::SelectThroughLastDragLocation() {
1739 OnBeforeUserAction();
1740 model_
->MoveCursorTo(last_drag_location_
, true);
1741 if (aggregated_clicks_
== 1) {
1742 model_
->SelectWord();
1743 // Expand the selection so the initially selected word remains selected.
1744 gfx::Range selection
= GetRenderText()->selection();
1745 const size_t min
= std::min(selection
.GetMin(),
1746 double_click_word_
.GetMin());
1747 const size_t max
= std::max(selection
.GetMax(),
1748 double_click_word_
.GetMax());
1749 const bool reversed
= selection
.is_reversed();
1750 selection
.set_start(reversed
? max
: min
);
1751 selection
.set_end(reversed
? min
: max
);
1752 model_
->SelectRange(selection
);
1754 UpdateAfterChange(false, true);
1755 OnAfterUserAction();
1758 void Textfield::OnCaretBoundsChanged() {
1759 if (GetInputMethod())
1760 GetInputMethod()->OnCaretBoundsChanged(this);
1761 if (touch_selection_controller_
)
1762 touch_selection_controller_
->SelectionChanged();
1765 void Textfield::OnBeforeUserAction() {
1766 DCHECK(!performing_user_action_
);
1767 performing_user_action_
= true;
1769 controller_
->OnBeforeUserAction(this);
1772 void Textfield::OnAfterUserAction() {
1774 controller_
->OnAfterUserAction(this);
1775 DCHECK(performing_user_action_
);
1776 performing_user_action_
= false;
1779 bool Textfield::Cut() {
1780 if (!read_only() && text_input_type_
!= ui::TEXT_INPUT_TYPE_PASSWORD
&&
1783 controller_
->OnAfterCutOrCopy(ui::CLIPBOARD_TYPE_COPY_PASTE
);
1789 bool Textfield::Copy() {
1790 if (text_input_type_
!= ui::TEXT_INPUT_TYPE_PASSWORD
&& model_
->Copy()) {
1792 controller_
->OnAfterCutOrCopy(ui::CLIPBOARD_TYPE_COPY_PASTE
);
1798 bool Textfield::Paste() {
1799 if (!read_only() && model_
->Paste()) {
1801 controller_
->OnAfterPaste();
1807 void Textfield::UpdateContextMenu() {
1808 if (!context_menu_contents_
.get()) {
1809 context_menu_contents_
.reset(new ui::SimpleMenuModel(this));
1810 context_menu_contents_
->AddItemWithStringId(IDS_APP_UNDO
, IDS_APP_UNDO
);
1811 context_menu_contents_
->AddSeparator(ui::NORMAL_SEPARATOR
);
1812 context_menu_contents_
->AddItemWithStringId(IDS_APP_CUT
, IDS_APP_CUT
);
1813 context_menu_contents_
->AddItemWithStringId(IDS_APP_COPY
, IDS_APP_COPY
);
1814 context_menu_contents_
->AddItemWithStringId(IDS_APP_PASTE
, IDS_APP_PASTE
);
1815 context_menu_contents_
->AddItemWithStringId(IDS_APP_DELETE
, IDS_APP_DELETE
);
1816 context_menu_contents_
->AddSeparator(ui::NORMAL_SEPARATOR
);
1817 context_menu_contents_
->AddItemWithStringId(IDS_APP_SELECT_ALL
,
1818 IDS_APP_SELECT_ALL
);
1820 controller_
->UpdateContextMenu(context_menu_contents_
.get());
1822 context_menu_runner_
.reset(
1823 new MenuRunner(context_menu_contents_
.get(),
1824 MenuRunner::HAS_MNEMONICS
| MenuRunner::CONTEXT_MENU
));
1827 void Textfield::TrackMouseClicks(const ui::MouseEvent
& event
) {
1828 if (event
.IsOnlyLeftMouseButton()) {
1829 base::TimeDelta time_delta
= event
.time_stamp() - last_click_time_
;
1830 if (time_delta
.InMilliseconds() <= GetDoubleClickInterval() &&
1831 !ExceededDragThreshold(event
.location() - last_click_location_
)) {
1832 // Upon clicking after a triple click, the count should go back to double
1833 // click and alternate between double and triple. This assignment maps
1834 // 0 to 1, 1 to 2, 2 to 1.
1835 aggregated_clicks_
= (aggregated_clicks_
% 2) + 1;
1837 aggregated_clicks_
= 0;
1839 last_click_time_
= event
.time_stamp();
1840 last_click_location_
= event
.location();
1844 bool Textfield::ImeEditingAllowed() const {
1845 // Disallow input method editing of password fields.
1846 ui::TextInputType t
= GetTextInputType();
1847 return (t
!= ui::TEXT_INPUT_TYPE_NONE
&& t
!= ui::TEXT_INPUT_TYPE_PASSWORD
);
1850 void Textfield::RevealPasswordChar(int index
) {
1851 GetRenderText()->SetObscuredRevealIndex(index
);
1855 password_reveal_timer_
.Start(FROM_HERE
, password_reveal_duration_
,
1856 base::Bind(&Textfield::RevealPasswordChar
,
1857 weak_ptr_factory_
.GetWeakPtr(), -1));
1861 void Textfield::CreateTouchSelectionControllerAndNotifyIt() {
1865 if (!touch_selection_controller_
) {
1866 touch_selection_controller_
.reset(
1867 ui::TouchEditingControllerDeprecated::Create(this));
1869 if (touch_selection_controller_
)
1870 touch_selection_controller_
->SelectionChanged();
1873 void Textfield::UpdateSelectionClipboard() const {
1874 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
1875 if (performing_user_action_
&& HasSelection()) {
1876 ui::ScopedClipboardWriter(
1877 ui::CLIPBOARD_TYPE_SELECTION
).WriteText(GetSelectedText());
1879 controller_
->OnAfterCutOrCopy(ui::CLIPBOARD_TYPE_SELECTION
);
1884 void Textfield::PasteSelectionClipboard(const ui::MouseEvent
& event
) {
1885 DCHECK(event
.IsOnlyMiddleMouseButton());
1886 DCHECK(!read_only());
1887 base::string16 selection_clipboard_text
= GetSelectionClipboardText();
1888 OnBeforeUserAction();
1889 const gfx::SelectionModel mouse
=
1890 GetRenderText()->FindCursorPosition(event
.location());
1893 model_
->MoveCursorTo(mouse
);
1894 if (!selection_clipboard_text
.empty()) {
1895 model_
->InsertText(selection_clipboard_text
);
1896 UpdateAfterChange(true, true);
1898 OnAfterUserAction();
1901 } // namespace views