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/base_event_utils.h"
21 #include "ui/events/event.h"
22 #include "ui/events/keycodes/keyboard_codes.h"
23 #include "ui/gfx/canvas.h"
24 #include "ui/gfx/display.h"
25 #include "ui/gfx/geometry/insets.h"
26 #include "ui/gfx/screen.h"
27 #include "ui/native_theme/native_theme.h"
28 #include "ui/strings/grit/ui_strings.h"
29 #include "ui/views/background.h"
30 #include "ui/views/controls/focusable_border.h"
31 #include "ui/views/controls/label.h"
32 #include "ui/views/controls/menu/menu_runner.h"
33 #include "ui/views/controls/native/native_view_host.h"
34 #include "ui/views/controls/textfield/textfield_controller.h"
35 #include "ui/views/drag_utils.h"
36 #include "ui/views/metrics.h"
37 #include "ui/views/native_cursor.h"
38 #include "ui/views/painter.h"
39 #include "ui/views/views_delegate.h"
40 #include "ui/views/widget/widget.h"
43 #include "base/win/win_util.h"
46 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
47 #include "base/strings/utf_string_conversions.h"
48 #include "ui/events/linux/text_edit_command_auralinux.h"
49 #include "ui/events/linux/text_edit_key_bindings_delegate_auralinux.h"
56 // Default placeholder text color.
57 const SkColor kDefaultPlaceholderTextColor
= SK_ColorLTGRAY
;
59 const int kNoCommand
= 0;
61 void ConvertRectToScreen(const View
* src
, gfx::Rect
* r
) {
64 gfx::Point new_origin
= r
->origin();
65 View::ConvertPointToScreen(src
, &new_origin
);
66 r
->set_origin(new_origin
);
69 // Get the drag selection timer delay, respecting animation scaling for testing.
70 int GetDragSelectionDelay() {
71 switch (ui::ScopedAnimationDurationScaleMode::duration_scale_mode()) {
72 case ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION
: return 100;
73 case ui::ScopedAnimationDurationScaleMode::FAST_DURATION
: return 25;
74 case ui::ScopedAnimationDurationScaleMode::SLOW_DURATION
: return 400;
75 case ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION
: return 1;
76 case ui::ScopedAnimationDurationScaleMode::ZERO_DURATION
: return 0;
81 // Get the default command for a given key |event| and selection state.
82 int GetCommandForKeyEvent(const ui::KeyEvent
& event
, bool has_selection
) {
83 if (event
.type() != ui::ET_KEY_PRESSED
|| event
.IsUnicodeKeyCode())
86 const bool shift
= event
.IsShiftDown();
87 const bool control
= event
.IsControlDown();
88 const bool alt
= event
.IsAltDown() || event
.IsAltGrDown();
89 switch (event
.key_code()) {
91 if (control
&& !shift
&& !alt
)
93 return (control
&& shift
&& !alt
) ? IDS_APP_REDO
: kNoCommand
;
95 return (control
&& !alt
) ? IDS_APP_REDO
: kNoCommand
;
97 return (control
&& !alt
) ? IDS_APP_SELECT_ALL
: kNoCommand
;
99 return (control
&& !alt
) ? IDS_APP_CUT
: kNoCommand
;
101 return (control
&& !alt
) ? IDS_APP_COPY
: kNoCommand
;
103 return (control
&& !alt
) ? IDS_APP_PASTE
: kNoCommand
;
105 // Ignore alt+right, which may be a browser navigation shortcut.
109 return control
? IDS_MOVE_WORD_RIGHT
: IDS_MOVE_RIGHT
;
110 return control
? IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION
:
111 IDS_MOVE_RIGHT_AND_MODIFY_SELECTION
;
113 // Ignore alt+left, which may be a browser navigation shortcut.
117 return control
? IDS_MOVE_WORD_LEFT
: IDS_MOVE_LEFT
;
118 return control
? IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION
:
119 IDS_MOVE_LEFT_AND_MODIFY_SELECTION
;
121 return shift
? IDS_MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION
:
122 IDS_MOVE_TO_BEGINNING_OF_LINE
;
124 return shift
? IDS_MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION
:
125 IDS_MOVE_TO_END_OF_LINE
;
127 if (!control
|| has_selection
)
128 return IDS_DELETE_BACKWARD
;
129 #if defined(OS_LINUX)
130 // Only erase by line break on Linux and ChromeOS.
132 return IDS_DELETE_TO_BEGINNING_OF_LINE
;
134 return IDS_DELETE_WORD_BACKWARD
;
135 case ui::VKEY_DELETE
:
136 if (!control
|| has_selection
)
137 return (shift
&& has_selection
) ? IDS_APP_CUT
: IDS_DELETE_FORWARD
;
138 #if defined(OS_LINUX)
139 // Only erase by line break on Linux and ChromeOS.
141 return IDS_DELETE_TO_END_OF_LINE
;
143 return IDS_DELETE_WORD_FORWARD
;
144 case ui::VKEY_INSERT
:
145 if (control
&& !shift
)
147 return (shift
&& !control
) ? IDS_APP_PASTE
: kNoCommand
;
153 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
154 // Convert a custom text edit |command| to the equivalent views command ID.
155 int GetViewsCommand(const ui::TextEditCommandAuraLinux
& command
, bool rtl
) {
156 const bool select
= command
.extend_selection();
157 switch (command
.command_id()) {
158 case ui::TextEditCommandAuraLinux::COPY
:
160 case ui::TextEditCommandAuraLinux::CUT
:
162 case ui::TextEditCommandAuraLinux::DELETE_BACKWARD
:
163 return IDS_DELETE_BACKWARD
;
164 case ui::TextEditCommandAuraLinux::DELETE_FORWARD
:
165 return IDS_DELETE_FORWARD
;
166 case ui::TextEditCommandAuraLinux::DELETE_TO_BEGINING_OF_LINE
:
167 case ui::TextEditCommandAuraLinux::DELETE_TO_BEGINING_OF_PARAGRAPH
:
168 return IDS_DELETE_TO_BEGINNING_OF_LINE
;
169 case ui::TextEditCommandAuraLinux::DELETE_TO_END_OF_LINE
:
170 case ui::TextEditCommandAuraLinux::DELETE_TO_END_OF_PARAGRAPH
:
171 return IDS_DELETE_TO_END_OF_LINE
;
172 case ui::TextEditCommandAuraLinux::DELETE_WORD_BACKWARD
:
173 return IDS_DELETE_WORD_BACKWARD
;
174 case ui::TextEditCommandAuraLinux::DELETE_WORD_FORWARD
:
175 return IDS_DELETE_WORD_FORWARD
;
176 case ui::TextEditCommandAuraLinux::INSERT_TEXT
:
178 case ui::TextEditCommandAuraLinux::MOVE_BACKWARD
:
180 return select
? IDS_MOVE_RIGHT_AND_MODIFY_SELECTION
: IDS_MOVE_RIGHT
;
181 return select
? IDS_MOVE_LEFT_AND_MODIFY_SELECTION
: IDS_MOVE_LEFT
;
182 case ui::TextEditCommandAuraLinux::MOVE_DOWN
:
183 return IDS_MOVE_DOWN
;
184 case ui::TextEditCommandAuraLinux::MOVE_FORWARD
:
186 return select
? IDS_MOVE_LEFT_AND_MODIFY_SELECTION
: IDS_MOVE_LEFT
;
187 return select
? IDS_MOVE_RIGHT_AND_MODIFY_SELECTION
: IDS_MOVE_RIGHT
;
188 case ui::TextEditCommandAuraLinux::MOVE_LEFT
:
189 return select
? IDS_MOVE_LEFT_AND_MODIFY_SELECTION
: IDS_MOVE_LEFT
;
190 case ui::TextEditCommandAuraLinux::MOVE_PAGE_DOWN
:
191 case ui::TextEditCommandAuraLinux::MOVE_PAGE_UP
:
193 case ui::TextEditCommandAuraLinux::MOVE_RIGHT
:
194 return select
? IDS_MOVE_RIGHT_AND_MODIFY_SELECTION
: IDS_MOVE_RIGHT
;
195 case ui::TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_DOCUMENT
:
196 case ui::TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_LINE
:
197 case ui::TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_PARAGRAPH
:
198 return select
? IDS_MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION
:
199 IDS_MOVE_TO_BEGINNING_OF_LINE
;
200 case ui::TextEditCommandAuraLinux::MOVE_TO_END_OF_DOCUMENT
:
201 case ui::TextEditCommandAuraLinux::MOVE_TO_END_OF_LINE
:
202 case ui::TextEditCommandAuraLinux::MOVE_TO_END_OF_PARAGRAPH
:
203 return select
? IDS_MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION
:
204 IDS_MOVE_TO_END_OF_LINE
;
205 case ui::TextEditCommandAuraLinux::MOVE_UP
:
207 case ui::TextEditCommandAuraLinux::MOVE_WORD_BACKWARD
:
209 return select
? IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION
:
212 return select
? IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION
:
214 case ui::TextEditCommandAuraLinux::MOVE_WORD_FORWARD
:
216 return select
? IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION
:
219 return select
? IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION
:
221 case ui::TextEditCommandAuraLinux::MOVE_WORD_LEFT
:
222 return select
? IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION
:
224 case ui::TextEditCommandAuraLinux::MOVE_WORD_RIGHT
:
225 return select
? IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION
:
227 case ui::TextEditCommandAuraLinux::PASTE
:
228 return IDS_APP_PASTE
;
229 case ui::TextEditCommandAuraLinux::SELECT_ALL
:
230 return IDS_APP_SELECT_ALL
;
231 case ui::TextEditCommandAuraLinux::SET_MARK
:
232 case ui::TextEditCommandAuraLinux::UNSELECT
:
233 case ui::TextEditCommandAuraLinux::INVALID_COMMAND
:
243 const char Textfield::kViewClassName
[] = "Textfield";
244 const int Textfield::kTextPadding
= 3;
247 size_t Textfield::GetCaretBlinkMs() {
248 static const size_t default_value
= 500;
250 static const size_t system_value
= ::GetCaretBlinkTime();
251 if (system_value
!= 0)
252 return (system_value
== INFINITE
) ? 0 : system_value
;
254 return default_value
;
257 Textfield::Textfield()
258 : model_(new TextfieldModel(this)),
260 scheduled_edit_command_(kNoCommand
),
262 default_width_in_chars_(0),
263 use_default_text_color_(true),
264 use_default_background_color_(true),
265 use_default_selection_text_color_(true),
266 use_default_selection_background_color_(true),
267 text_color_(SK_ColorBLACK
),
268 background_color_(SK_ColorWHITE
),
269 selection_text_color_(SK_ColorWHITE
),
270 selection_background_color_(SK_ColorBLUE
),
271 placeholder_text_color_(kDefaultPlaceholderTextColor
),
272 text_input_type_(ui::TEXT_INPUT_TYPE_TEXT
),
273 text_input_flags_(0),
274 performing_user_action_(false),
275 skip_input_method_cancel_composition_(false),
276 cursor_visible_(false),
277 drop_cursor_visible_(false),
278 initiating_drag_(false),
279 aggregated_clicks_(0),
280 drag_start_display_offset_(0),
281 touch_handles_hidden_due_to_scroll_(false),
282 weak_ptr_factory_(this) {
283 set_context_menu_controller(this);
284 set_drag_controller(this);
285 SetBorder(scoped_ptr
<Border
>(new FocusableBorder()));
288 if (ViewsDelegate::GetInstance()) {
289 password_reveal_duration_
=
290 ViewsDelegate::GetInstance()
291 ->GetDefaultTextfieldObscuredRevealDuration();
294 // These allow BrowserView to pass edit commands from the Chrome menu to us
295 // when we're focused by simply asking the FocusManager to
296 // ProcessAccelerator() with the relevant accelerators.
297 AddAccelerator(ui::Accelerator(ui::VKEY_X
, ui::EF_CONTROL_DOWN
));
298 AddAccelerator(ui::Accelerator(ui::VKEY_C
, ui::EF_CONTROL_DOWN
));
299 AddAccelerator(ui::Accelerator(ui::VKEY_V
, ui::EF_CONTROL_DOWN
));
302 Textfield::~Textfield() {
303 if (GetInputMethod()) {
304 // The textfield should have been blurred before destroy.
305 DCHECK(this != GetInputMethod()->GetTextInputClient());
309 void Textfield::SetReadOnly(bool read_only
) {
310 // Update read-only without changing the focusable state (or active, etc.).
311 read_only_
= read_only
;
312 if (GetInputMethod())
313 GetInputMethod()->OnTextInputTypeChanged(this);
314 SetColor(GetTextColor());
315 UpdateBackgroundColor();
318 void Textfield::SetTextInputType(ui::TextInputType type
) {
319 GetRenderText()->SetObscured(type
== ui::TEXT_INPUT_TYPE_PASSWORD
);
320 text_input_type_
= type
;
321 OnCaretBoundsChanged();
322 if (GetInputMethod())
323 GetInputMethod()->OnTextInputTypeChanged(this);
327 void Textfield::SetTextInputFlags(int flags
) {
328 text_input_flags_
= flags
;
331 void Textfield::SetText(const base::string16
& new_text
) {
332 model_
->SetText(new_text
);
333 OnCaretBoundsChanged();
335 NotifyAccessibilityEvent(ui::AX_EVENT_TEXT_CHANGED
, true);
338 void Textfield::AppendText(const base::string16
& new_text
) {
339 if (new_text
.empty())
341 model_
->Append(new_text
);
342 OnCaretBoundsChanged();
346 void Textfield::InsertOrReplaceText(const base::string16
& new_text
) {
347 if (new_text
.empty())
349 model_
->InsertText(new_text
);
350 OnCaretBoundsChanged();
354 base::i18n::TextDirection
Textfield::GetTextDirection() const {
355 return GetRenderText()->GetDisplayTextDirection();
358 base::string16
Textfield::GetSelectedText() const {
359 return model_
->GetSelectedText();
362 void Textfield::SelectAll(bool reversed
) {
363 model_
->SelectAll(reversed
);
364 UpdateSelectionClipboard();
365 UpdateAfterChange(false, true);
368 void Textfield::SelectWordAt(const gfx::Point
& point
) {
369 model_
->MoveCursorTo(point
, false);
370 model_
->SelectWord();
371 UpdateAfterChange(false, true);
374 void Textfield::ClearSelection() {
375 model_
->ClearSelection();
376 UpdateAfterChange(false, true);
379 bool Textfield::HasSelection() const {
380 return !GetSelectedRange().is_empty();
383 SkColor
Textfield::GetTextColor() const {
384 if (!use_default_text_color_
)
387 return GetNativeTheme()->GetSystemColor(read_only() ?
388 ui::NativeTheme::kColorId_TextfieldReadOnlyColor
:
389 ui::NativeTheme::kColorId_TextfieldDefaultColor
);
392 void Textfield::SetTextColor(SkColor color
) {
394 use_default_text_color_
= false;
398 void Textfield::UseDefaultTextColor() {
399 use_default_text_color_
= true;
400 SetColor(GetTextColor());
403 SkColor
Textfield::GetBackgroundColor() const {
404 if (!use_default_background_color_
)
405 return background_color_
;
407 return GetNativeTheme()->GetSystemColor(read_only() ?
408 ui::NativeTheme::kColorId_TextfieldReadOnlyBackground
:
409 ui::NativeTheme::kColorId_TextfieldDefaultBackground
);
412 void Textfield::SetBackgroundColor(SkColor color
) {
413 background_color_
= color
;
414 use_default_background_color_
= false;
415 UpdateBackgroundColor();
418 void Textfield::UseDefaultBackgroundColor() {
419 use_default_background_color_
= true;
420 UpdateBackgroundColor();
423 SkColor
Textfield::GetSelectionTextColor() const {
424 return use_default_selection_text_color_
?
425 GetNativeTheme()->GetSystemColor(
426 ui::NativeTheme::kColorId_TextfieldSelectionColor
) :
427 selection_text_color_
;
430 void Textfield::SetSelectionTextColor(SkColor color
) {
431 selection_text_color_
= color
;
432 use_default_selection_text_color_
= false;
433 GetRenderText()->set_selection_color(GetSelectionTextColor());
437 void Textfield::UseDefaultSelectionTextColor() {
438 use_default_selection_text_color_
= true;
439 GetRenderText()->set_selection_color(GetSelectionTextColor());
443 void Textfield::SetShadows(const gfx::ShadowValues
& shadows
) {
444 GetRenderText()->set_shadows(shadows
);
448 SkColor
Textfield::GetSelectionBackgroundColor() const {
449 return use_default_selection_background_color_
?
450 GetNativeTheme()->GetSystemColor(
451 ui::NativeTheme::kColorId_TextfieldSelectionBackgroundFocused
) :
452 selection_background_color_
;
455 void Textfield::SetSelectionBackgroundColor(SkColor color
) {
456 selection_background_color_
= color
;
457 use_default_selection_background_color_
= false;
458 GetRenderText()->set_selection_background_focused_color(
459 GetSelectionBackgroundColor());
463 void Textfield::UseDefaultSelectionBackgroundColor() {
464 use_default_selection_background_color_
= true;
465 GetRenderText()->set_selection_background_focused_color(
466 GetSelectionBackgroundColor());
470 bool Textfield::GetCursorEnabled() const {
471 return GetRenderText()->cursor_enabled();
474 void Textfield::SetCursorEnabled(bool enabled
) {
475 GetRenderText()->SetCursorEnabled(enabled
);
478 const gfx::FontList
& Textfield::GetFontList() const {
479 return GetRenderText()->font_list();
482 void Textfield::SetFontList(const gfx::FontList
& font_list
) {
483 GetRenderText()->SetFontList(font_list
);
484 OnCaretBoundsChanged();
485 PreferredSizeChanged();
488 base::string16
Textfield::GetPlaceholderText() const {
489 return placeholder_text_
;
492 gfx::HorizontalAlignment
Textfield::GetHorizontalAlignment() const {
493 return GetRenderText()->horizontal_alignment();
496 void Textfield::SetHorizontalAlignment(gfx::HorizontalAlignment alignment
) {
497 GetRenderText()->SetHorizontalAlignment(alignment
);
500 void Textfield::ShowImeIfNeeded() {
501 if (enabled() && !read_only())
502 GetInputMethod()->ShowImeIfNeeded();
505 bool Textfield::IsIMEComposing() const {
506 return model_
->HasCompositionText();
509 const gfx::Range
& Textfield::GetSelectedRange() const {
510 return GetRenderText()->selection();
513 void Textfield::SelectRange(const gfx::Range
& range
) {
514 model_
->SelectRange(range
);
515 UpdateAfterChange(false, true);
518 const gfx::SelectionModel
& Textfield::GetSelectionModel() const {
519 return GetRenderText()->selection_model();
522 void Textfield::SelectSelectionModel(const gfx::SelectionModel
& sel
) {
523 model_
->SelectSelectionModel(sel
);
524 UpdateAfterChange(false, true);
527 size_t Textfield::GetCursorPosition() const {
528 return model_
->GetCursorPosition();
531 void Textfield::SetColor(SkColor value
) {
532 GetRenderText()->SetColor(value
);
536 void Textfield::ApplyColor(SkColor value
, const gfx::Range
& range
) {
537 GetRenderText()->ApplyColor(value
, range
);
541 void Textfield::SetStyle(gfx::TextStyle style
, bool value
) {
542 GetRenderText()->SetStyle(style
, value
);
546 void Textfield::ApplyStyle(gfx::TextStyle style
,
548 const gfx::Range
& range
) {
549 GetRenderText()->ApplyStyle(style
, value
, range
);
553 void Textfield::ClearEditHistory() {
554 model_
->ClearEditHistory();
557 void Textfield::SetAccessibleName(const base::string16
& name
) {
558 accessible_name_
= name
;
561 void Textfield::ExecuteCommand(int command_id
) {
562 ExecuteCommand(command_id
, ui::EF_NONE
);
565 void Textfield::SetFocusPainter(scoped_ptr
<Painter
> focus_painter
) {
566 focus_painter_
= focus_painter
.Pass();
569 bool Textfield::HasTextBeingDragged() {
570 return initiating_drag_
;
573 ////////////////////////////////////////////////////////////////////////////////
574 // Textfield, View overrides:
576 gfx::Insets
Textfield::GetInsets() const {
577 gfx::Insets insets
= View::GetInsets();
578 insets
+= gfx::Insets(kTextPadding
, kTextPadding
, kTextPadding
, kTextPadding
);
582 int Textfield::GetBaseline() const {
583 return GetInsets().top() + GetRenderText()->GetBaseline();
586 gfx::Size
Textfield::GetPreferredSize() const {
587 const gfx::Insets
& insets
= GetInsets();
588 return gfx::Size(GetFontList().GetExpectedTextWidth(default_width_in_chars_
) +
589 insets
.width(), GetFontList().GetHeight() + insets
.height());
592 const char* Textfield::GetClassName() const {
593 return kViewClassName
;
596 gfx::NativeCursor
Textfield::GetCursor(const ui::MouseEvent
& event
) {
597 bool in_selection
= GetRenderText()->IsPointInSelection(event
.location());
598 bool drag_event
= event
.type() == ui::ET_MOUSE_DRAGGED
;
599 bool text_cursor
= !initiating_drag_
&& (drag_event
|| !in_selection
);
600 return text_cursor
? GetNativeIBeamCursor() : gfx::kNullCursor
;
603 bool Textfield::OnMousePressed(const ui::MouseEvent
& event
) {
604 TrackMouseClicks(event
);
606 if (!controller_
|| !controller_
->HandleMouseEvent(this, event
)) {
607 if (event
.IsOnlyLeftMouseButton() || event
.IsOnlyRightMouseButton()) {
612 if (event
.IsOnlyLeftMouseButton()) {
613 OnBeforeUserAction();
614 initiating_drag_
= false;
615 switch (aggregated_clicks_
) {
617 if (GetRenderText()->IsPointInSelection(event
.location()))
618 initiating_drag_
= true;
620 MoveCursorTo(event
.location(), event
.IsShiftDown());
623 SelectWordAt(event
.location());
624 double_click_word_
= GetRenderText()->selection();
635 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
636 if (event
.IsOnlyMiddleMouseButton()) {
637 if (GetRenderText()->IsPointInSelection(event
.location())) {
638 OnBeforeUserAction();
640 ui::ScopedClipboardWriter(
641 ui::CLIPBOARD_TYPE_SELECTION
).WriteText(base::string16());
643 } else if (!read_only()) {
644 PasteSelectionClipboard(event
);
653 bool Textfield::OnMouseDragged(const ui::MouseEvent
& event
) {
654 last_drag_location_
= event
.location();
656 // Don't adjust the cursor on a potential drag and drop, or if the mouse
657 // movement from the last mouse click does not exceed the drag threshold.
658 if (initiating_drag_
|| !event
.IsOnlyLeftMouseButton() ||
659 !ExceededDragThreshold(last_drag_location_
- last_click_location_
)) {
663 // A timer is used to continuously scroll while selecting beyond side edges.
664 const int x
= event
.location().x();
665 if ((x
>= 0 && x
<= width()) || GetDragSelectionDelay() == 0) {
666 drag_selection_timer_
.Stop();
667 SelectThroughLastDragLocation();
668 } else if (!drag_selection_timer_
.IsRunning()) {
669 // Select through the edge of the visible text, then start the scroll timer.
670 last_drag_location_
.set_x(std::min(std::max(0, x
), width()));
671 SelectThroughLastDragLocation();
672 drag_selection_timer_
.Start(
673 FROM_HERE
, base::TimeDelta::FromMilliseconds(GetDragSelectionDelay()),
674 this, &Textfield::SelectThroughLastDragLocation
);
680 void Textfield::OnMouseReleased(const ui::MouseEvent
& event
) {
681 OnBeforeUserAction();
682 drag_selection_timer_
.Stop();
683 // Cancel suspected drag initiations, the user was clicking in the selection.
684 if (initiating_drag_
)
685 MoveCursorTo(event
.location(), false);
686 initiating_drag_
= false;
687 UpdateSelectionClipboard();
691 bool Textfield::OnKeyPressed(const ui::KeyEvent
& event
) {
692 int edit_command
= scheduled_edit_command_
;
693 scheduled_edit_command_
= kNoCommand
;
695 // Since HandleKeyEvent() might destroy |this|, get a weak pointer and verify
696 // it isn't null before proceeding.
697 base::WeakPtr
<Textfield
> textfield(weak_ptr_factory_
.GetWeakPtr());
699 bool handled
= controller_
&& controller_
->HandleKeyEvent(this, event
);
704 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
705 ui::TextEditKeyBindingsDelegateAuraLinux
* delegate
=
706 ui::GetTextEditKeyBindingsDelegate();
707 std::vector
<ui::TextEditCommandAuraLinux
> commands
;
708 if (!handled
&& delegate
&& delegate
->MatchEvent(event
, &commands
)) {
709 const bool rtl
= GetTextDirection() == base::i18n::RIGHT_TO_LEFT
;
710 for (size_t i
= 0; i
< commands
.size(); ++i
) {
711 const int command
= GetViewsCommand(commands
[i
], rtl
);
712 if (IsCommandIdEnabled(command
)) {
713 ExecuteCommand(command
);
721 if (edit_command
== kNoCommand
)
722 edit_command
= GetCommandForKeyEvent(event
, HasSelection());
724 if (!handled
&& IsCommandIdEnabled(edit_command
)) {
725 ExecuteCommand(edit_command
);
731 void Textfield::OnGestureEvent(ui::GestureEvent
* event
) {
732 switch (event
->type()) {
733 case ui::ET_GESTURE_TAP_DOWN
:
738 case ui::ET_GESTURE_TAP
:
739 if (event
->details().tap_count() == 1) {
740 // If tap is on the selection and touch handles are not present, handles
741 // should be shown without changing selection. Otherwise, cursor should
742 // be moved to the tap location.
743 if (touch_selection_controller_
||
744 !GetRenderText()->IsPointInSelection(event
->location())) {
745 OnBeforeUserAction();
746 MoveCursorTo(event
->location(), false);
749 } else if (event
->details().tap_count() == 2) {
750 OnBeforeUserAction();
751 SelectWordAt(event
->location());
754 OnBeforeUserAction();
758 CreateTouchSelectionControllerAndNotifyIt();
761 base::win::DisplayVirtualKeyboard();
765 case ui::ET_GESTURE_LONG_PRESS
:
766 if (!GetRenderText()->IsPointInSelection(event
->location())) {
767 // If long-press happens outside selection, select word and try to
768 // activate touch selection.
769 OnBeforeUserAction();
770 SelectWordAt(event
->location());
772 CreateTouchSelectionControllerAndNotifyIt();
773 // If touch selection activated successfully, mark event as handled so
774 // that the regular context menu is not shown.
775 if (touch_selection_controller_
)
778 // If long-press happens on the selection, deactivate touch selection
779 // and try to initiate drag-drop. If drag-drop is not enabled, context
780 // menu will be shown. Event is not marked as handled to let Views
781 // handle drag-drop or context menu.
782 DestroyTouchSelection();
783 initiating_drag_
= switches::IsTouchDragDropEnabled();
786 case ui::ET_GESTURE_LONG_TAP
:
787 // If touch selection is enabled, the context menu on long tap will be
788 // shown by the |touch_selection_controller_|, hence we mark the event
789 // handled so Views does not try to show context menu on it.
790 if (touch_selection_controller_
)
793 case ui::ET_GESTURE_SCROLL_BEGIN
:
794 touch_handles_hidden_due_to_scroll_
= touch_selection_controller_
!= NULL
;
795 DestroyTouchSelection();
796 drag_start_location_
= event
->location();
797 drag_start_display_offset_
=
798 GetRenderText()->GetUpdatedDisplayOffset().x();
801 case ui::ET_GESTURE_SCROLL_UPDATE
: {
802 int new_offset
= drag_start_display_offset_
+ event
->location().x() -
803 drag_start_location_
.x();
804 GetRenderText()->SetDisplayOffset(new_offset
);
809 case ui::ET_GESTURE_SCROLL_END
:
810 case ui::ET_SCROLL_FLING_START
:
811 if (touch_handles_hidden_due_to_scroll_
) {
812 CreateTouchSelectionControllerAndNotifyIt();
813 touch_handles_hidden_due_to_scroll_
= false;
822 // This function is called by BrowserView to execute clipboard commands.
823 bool Textfield::AcceleratorPressed(const ui::Accelerator
& accelerator
) {
824 ui::KeyEvent
event(accelerator
.type(), accelerator
.key_code(),
825 accelerator
.modifiers());
826 ExecuteCommand(GetCommandForKeyEvent(event
, HasSelection()));
830 bool Textfield::CanHandleAccelerators() const {
831 return GetRenderText()->focused() && View::CanHandleAccelerators();
834 void Textfield::AboutToRequestFocusFromTabTraversal(bool reverse
) {
838 bool Textfield::SkipDefaultKeyEventProcessing(const ui::KeyEvent
& event
) {
839 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
840 // Skip any accelerator handling that conflicts with custom keybindings.
841 ui::TextEditKeyBindingsDelegateAuraLinux
* delegate
=
842 ui::GetTextEditKeyBindingsDelegate();
843 std::vector
<ui::TextEditCommandAuraLinux
> commands
;
844 if (delegate
&& delegate
->MatchEvent(event
, &commands
)) {
845 const bool rtl
= GetTextDirection() == base::i18n::RIGHT_TO_LEFT
;
846 for (size_t i
= 0; i
< commands
.size(); ++i
)
847 if (IsCommandIdEnabled(GetViewsCommand(commands
[i
], rtl
)))
852 // Skip backspace accelerator handling; editable textfields handle this key.
853 // Also skip processing Windows [Alt]+<num-pad digit> Unicode alt-codes.
854 const bool is_backspace
= event
.key_code() == ui::VKEY_BACK
;
855 return (is_backspace
&& !read_only()) || event
.IsUnicodeKeyCode();
858 bool Textfield::GetDropFormats(
860 std::set
<OSExchangeData::CustomFormat
>* custom_formats
) {
861 if (!enabled() || read_only())
863 // TODO(msw): Can we support URL, FILENAME, etc.?
864 *formats
= ui::OSExchangeData::STRING
;
866 controller_
->AppendDropFormats(formats
, custom_formats
);
870 bool Textfield::CanDrop(const OSExchangeData
& data
) {
872 std::set
<OSExchangeData::CustomFormat
> custom_formats
;
873 GetDropFormats(&formats
, &custom_formats
);
874 return enabled() && !read_only() &&
875 data
.HasAnyFormat(formats
, custom_formats
);
878 int Textfield::OnDragUpdated(const ui::DropTargetEvent
& event
) {
879 DCHECK(CanDrop(event
.data()));
880 gfx::RenderText
* render_text
= GetRenderText();
881 const gfx::Range
& selection
= render_text
->selection();
882 drop_cursor_position_
= render_text
->FindCursorPosition(event
.location());
883 bool in_selection
= !selection
.is_empty() &&
884 selection
.Contains(gfx::Range(drop_cursor_position_
.caret_pos()));
885 drop_cursor_visible_
= !in_selection
;
886 // TODO(msw): Pan over text when the user drags to the visible text edge.
887 OnCaretBoundsChanged();
890 if (initiating_drag_
) {
892 return ui::DragDropTypes::DRAG_NONE
;
893 return event
.IsControlDown() ? ui::DragDropTypes::DRAG_COPY
:
894 ui::DragDropTypes::DRAG_MOVE
;
896 return ui::DragDropTypes::DRAG_COPY
| ui::DragDropTypes::DRAG_MOVE
;
899 void Textfield::OnDragExited() {
900 drop_cursor_visible_
= false;
904 int Textfield::OnPerformDrop(const ui::DropTargetEvent
& event
) {
905 DCHECK(CanDrop(event
.data()));
906 drop_cursor_visible_
= false;
909 int drag_operation
= controller_
->OnDrop(event
.data());
910 if (drag_operation
!= ui::DragDropTypes::DRAG_NONE
)
911 return drag_operation
;
914 gfx::RenderText
* render_text
= GetRenderText();
915 DCHECK(!initiating_drag_
||
916 !render_text
->IsPointInSelection(event
.location()));
917 OnBeforeUserAction();
918 skip_input_method_cancel_composition_
= true;
920 gfx::SelectionModel drop_destination_model
=
921 render_text
->FindCursorPosition(event
.location());
922 base::string16 new_text
;
923 event
.data().GetString(&new_text
);
925 // Delete the current selection for a drag and drop within this view.
926 const bool move
= initiating_drag_
&& !event
.IsControlDown() &&
927 event
.source_operations() & ui::DragDropTypes::DRAG_MOVE
;
929 // Adjust the drop destination if it is on or after the current selection.
930 size_t pos
= drop_destination_model
.caret_pos();
931 pos
-= render_text
->selection().Intersect(gfx::Range(0, pos
)).length();
932 model_
->DeleteSelectionAndInsertTextAt(new_text
, pos
);
934 model_
->MoveCursorTo(drop_destination_model
);
935 // Drop always inserts text even if the textfield is not in insert mode.
936 model_
->InsertText(new_text
);
938 skip_input_method_cancel_composition_
= false;
939 UpdateAfterChange(true, true);
941 return move
? ui::DragDropTypes::DRAG_MOVE
: ui::DragDropTypes::DRAG_COPY
;
944 void Textfield::OnDragDone() {
945 initiating_drag_
= false;
946 drop_cursor_visible_
= false;
949 void Textfield::GetAccessibleState(ui::AXViewState
* state
) {
950 state
->role
= ui::AX_ROLE_TEXT_FIELD
;
951 state
->name
= accessible_name_
;
953 state
->AddStateFlag(ui::AX_STATE_READ_ONLY
);
954 if (text_input_type_
== ui::TEXT_INPUT_TYPE_PASSWORD
) {
955 state
->AddStateFlag(ui::AX_STATE_PROTECTED
);
956 state
->value
= base::string16(text().size(), '*');
958 state
->value
= text();
960 const gfx::Range range
= GetSelectedRange();
961 state
->selection_start
= range
.start();
962 state
->selection_end
= range
.end();
965 state
->set_value_callback
=
966 base::Bind(&Textfield::AccessibilitySetValue
,
967 weak_ptr_factory_
.GetWeakPtr());
971 void Textfield::OnBoundsChanged(const gfx::Rect
& previous_bounds
) {
972 // Textfield insets include a reasonable amount of whitespace on all sides of
973 // the default font list. Fallback fonts with larger heights may paint over
974 // the vertical whitespace as needed. Alternate solutions involve undesirable
975 // behavior like changing the default font size, shrinking some fallback fonts
976 // beyond their legibility, or enlarging controls dynamically with content.
977 gfx::Rect bounds
= GetContentsBounds();
978 // GetContentsBounds() does not actually use the local GetInsets() override.
979 bounds
.Inset(gfx::Insets(0, kTextPadding
, 0, kTextPadding
));
980 GetRenderText()->SetDisplayRect(bounds
);
981 OnCaretBoundsChanged();
984 bool Textfield::GetNeedsNotificationWhenVisibleBoundsChange() const {
988 void Textfield::OnVisibleBoundsChanged() {
989 if (touch_selection_controller_
)
990 touch_selection_controller_
->SelectionChanged();
993 void Textfield::OnEnabledChanged() {
994 View::OnEnabledChanged();
995 if (GetInputMethod())
996 GetInputMethod()->OnTextInputTypeChanged(this);
1000 void Textfield::OnPaint(gfx::Canvas
* canvas
) {
1001 OnPaintBackground(canvas
);
1002 PaintTextAndCursor(canvas
);
1003 OnPaintBorder(canvas
);
1006 void Textfield::OnFocus() {
1007 GetRenderText()->set_focused(true);
1008 cursor_visible_
= true;
1010 if (GetInputMethod())
1011 GetInputMethod()->SetFocusedTextInputClient(this);
1012 OnCaretBoundsChanged();
1014 const size_t caret_blink_ms
= Textfield::GetCaretBlinkMs();
1015 if (caret_blink_ms
!= 0) {
1016 cursor_repaint_timer_
.Start(FROM_HERE
,
1017 base::TimeDelta::FromMilliseconds(caret_blink_ms
), this,
1018 &Textfield::UpdateCursor
);
1025 void Textfield::OnBlur() {
1026 GetRenderText()->set_focused(false);
1027 if (GetInputMethod())
1028 GetInputMethod()->DetachTextInputClient(this);
1029 cursor_repaint_timer_
.Stop();
1030 if (cursor_visible_
) {
1031 cursor_visible_
= false;
1035 DestroyTouchSelection();
1037 // Border typically draws focus indicator.
1041 gfx::Point
Textfield::GetKeyboardContextMenuLocation() {
1042 return GetCaretBounds().bottom_right();
1045 void Textfield::OnNativeThemeChanged(const ui::NativeTheme
* theme
) {
1046 gfx::RenderText
* render_text
= GetRenderText();
1047 render_text
->SetColor(GetTextColor());
1048 UpdateBackgroundColor();
1049 render_text
->set_cursor_color(GetTextColor());
1050 render_text
->set_selection_color(GetSelectionTextColor());
1051 render_text
->set_selection_background_focused_color(
1052 GetSelectionBackgroundColor());
1055 ////////////////////////////////////////////////////////////////////////////////
1056 // Textfield, TextfieldModel::Delegate overrides:
1058 void Textfield::OnCompositionTextConfirmedOrCleared() {
1059 if (!skip_input_method_cancel_composition_
)
1060 GetInputMethod()->CancelComposition(this);
1063 ////////////////////////////////////////////////////////////////////////////////
1064 // Textfield, ContextMenuController overrides:
1066 void Textfield::ShowContextMenuForView(View
* source
,
1067 const gfx::Point
& point
,
1068 ui::MenuSourceType source_type
) {
1069 UpdateContextMenu();
1070 ignore_result(context_menu_runner_
->RunMenuAt(GetWidget(),
1072 gfx::Rect(point
, gfx::Size()),
1073 MENU_ANCHOR_TOPLEFT
,
1077 ////////////////////////////////////////////////////////////////////////////////
1078 // Textfield, DragController overrides:
1080 void Textfield::WriteDragDataForView(View
* sender
,
1081 const gfx::Point
& press_pt
,
1082 OSExchangeData
* data
) {
1083 const base::string16
& selected_text(GetSelectedText());
1084 data
->SetString(selected_text
);
1085 Label
label(selected_text
, GetFontList());
1086 label
.SetBackgroundColor(GetBackgroundColor());
1087 label
.SetSubpixelRenderingEnabled(false);
1088 gfx::Size
size(label
.GetPreferredSize());
1089 gfx::NativeView native_view
= GetWidget()->GetNativeView();
1090 gfx::Display display
= gfx::Screen::GetScreenFor(native_view
)->
1091 GetDisplayNearestWindow(native_view
);
1092 size
.SetToMin(gfx::Size(display
.size().width(), height()));
1093 label
.SetBoundsRect(gfx::Rect(size
));
1094 scoped_ptr
<gfx::Canvas
> canvas(
1095 GetCanvasForDragImage(GetWidget(), label
.size()));
1096 label
.SetEnabledColor(GetTextColor());
1097 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
1098 // Desktop Linux Aura does not yet support transparency in drag images.
1099 canvas
->DrawColor(GetBackgroundColor());
1101 label
.Paint(ui::CanvasPainter(canvas
.get(), 1.f
).context());
1102 const gfx::Vector2d
kOffset(-15, 0);
1103 drag_utils::SetDragImageOnDataObject(*canvas
, kOffset
, data
);
1105 controller_
->OnWriteDragData(data
);
1108 int Textfield::GetDragOperationsForView(View
* sender
, const gfx::Point
& p
) {
1109 int drag_operations
= ui::DragDropTypes::DRAG_COPY
;
1110 if (!enabled() || text_input_type_
== ui::TEXT_INPUT_TYPE_PASSWORD
||
1111 !GetRenderText()->IsPointInSelection(p
)) {
1112 drag_operations
= ui::DragDropTypes::DRAG_NONE
;
1113 } else if (sender
== this && !read_only()) {
1115 ui::DragDropTypes::DRAG_MOVE
| ui::DragDropTypes::DRAG_COPY
;
1118 controller_
->OnGetDragOperationsForTextfield(&drag_operations
);
1119 return drag_operations
;
1122 bool Textfield::CanStartDragForView(View
* sender
,
1123 const gfx::Point
& press_pt
,
1124 const gfx::Point
& p
) {
1125 return initiating_drag_
&& GetRenderText()->IsPointInSelection(press_pt
);
1128 ////////////////////////////////////////////////////////////////////////////////
1129 // Textfield, ui::TouchEditable overrides:
1131 void Textfield::SelectRect(const gfx::Point
& start
, const gfx::Point
& end
) {
1132 if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE
)
1135 gfx::SelectionModel start_caret
= GetRenderText()->FindCursorPosition(start
);
1136 gfx::SelectionModel end_caret
= GetRenderText()->FindCursorPosition(end
);
1137 gfx::SelectionModel
selection(
1138 gfx::Range(start_caret
.caret_pos(), end_caret
.caret_pos()),
1139 end_caret
.caret_affinity());
1141 OnBeforeUserAction();
1142 SelectSelectionModel(selection
);
1143 OnAfterUserAction();
1146 void Textfield::MoveCaretTo(const gfx::Point
& point
) {
1147 SelectRect(point
, point
);
1150 void Textfield::GetSelectionEndPoints(ui::SelectionBound
* anchor
,
1151 ui::SelectionBound
* focus
) {
1152 gfx::RenderText
* render_text
= GetRenderText();
1153 const gfx::SelectionModel
& sel
= render_text
->selection_model();
1154 gfx::SelectionModel start_sel
=
1155 render_text
->GetSelectionModelForSelectionStart();
1156 gfx::Rect r1
= render_text
->GetCursorBounds(start_sel
, true);
1157 gfx::Rect r2
= render_text
->GetCursorBounds(sel
, true);
1159 anchor
->SetEdge(r1
.origin(), r1
.bottom_left());
1160 focus
->SetEdge(r2
.origin(), r2
.bottom_left());
1162 // Determine the SelectionBound's type for focus and anchor.
1163 // TODO(mfomitchev): Ideally we should have different logical directions for
1164 // start and end to support proper handle direction for mixed LTR/RTL text.
1165 const bool ltr
= GetTextDirection() != base::i18n::RIGHT_TO_LEFT
;
1166 size_t anchor_position_index
= sel
.selection().start();
1167 size_t focus_position_index
= sel
.selection().end();
1169 if (anchor_position_index
== focus_position_index
) {
1170 anchor
->set_type(ui::SelectionBound::CENTER
);
1171 focus
->set_type(ui::SelectionBound::CENTER
);
1172 } else if ((ltr
&& anchor_position_index
< focus_position_index
) ||
1173 (!ltr
&& anchor_position_index
> focus_position_index
)) {
1174 anchor
->set_type(ui::SelectionBound::LEFT
);
1175 focus
->set_type(ui::SelectionBound::RIGHT
);
1177 anchor
->set_type(ui::SelectionBound::RIGHT
);
1178 focus
->set_type(ui::SelectionBound::LEFT
);
1182 gfx::Rect
Textfield::GetBounds() {
1183 return GetLocalBounds();
1186 gfx::NativeView
Textfield::GetNativeView() const {
1187 return GetWidget()->GetNativeView();
1190 void Textfield::ConvertPointToScreen(gfx::Point
* point
) {
1191 View::ConvertPointToScreen(this, point
);
1194 void Textfield::ConvertPointFromScreen(gfx::Point
* point
) {
1195 View::ConvertPointFromScreen(this, point
);
1198 bool Textfield::DrawsHandles() {
1202 void Textfield::OpenContextMenu(const gfx::Point
& anchor
) {
1203 DestroyTouchSelection();
1204 ShowContextMenu(anchor
, ui::MENU_SOURCE_TOUCH_EDIT_MENU
);
1207 void Textfield::DestroyTouchSelection() {
1208 touch_selection_controller_
.reset();
1211 ////////////////////////////////////////////////////////////////////////////////
1212 // Textfield, ui::SimpleMenuModel::Delegate overrides:
1214 bool Textfield::IsCommandIdChecked(int command_id
) const {
1218 bool Textfield::IsCommandIdEnabled(int command_id
) const {
1219 base::string16 result
;
1220 bool editable
= !read_only();
1221 bool readable
= text_input_type_
!= ui::TEXT_INPUT_TYPE_PASSWORD
;
1222 switch (command_id
) {
1224 return editable
&& model_
->CanUndo();
1226 return editable
&& model_
->CanRedo();
1228 return editable
&& readable
&& model_
->HasSelection();
1230 return readable
&& model_
->HasSelection();
1232 ui::Clipboard::GetForCurrentThread()->ReadText(
1233 ui::CLIPBOARD_TYPE_COPY_PASTE
, &result
);
1234 return editable
&& !result
.empty();
1235 case IDS_APP_DELETE
:
1236 return editable
&& model_
->HasSelection();
1237 case IDS_APP_SELECT_ALL
:
1238 return !text().empty();
1239 case IDS_DELETE_FORWARD
:
1240 case IDS_DELETE_BACKWARD
:
1241 case IDS_DELETE_TO_BEGINNING_OF_LINE
:
1242 case IDS_DELETE_TO_END_OF_LINE
:
1243 case IDS_DELETE_WORD_BACKWARD
:
1244 case IDS_DELETE_WORD_FORWARD
:
1247 case IDS_MOVE_LEFT_AND_MODIFY_SELECTION
:
1248 case IDS_MOVE_RIGHT
:
1249 case IDS_MOVE_RIGHT_AND_MODIFY_SELECTION
:
1250 case IDS_MOVE_WORD_LEFT
:
1251 case IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION
:
1252 case IDS_MOVE_WORD_RIGHT
:
1253 case IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION
:
1254 case IDS_MOVE_TO_BEGINNING_OF_LINE
:
1255 case IDS_MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION
:
1256 case IDS_MOVE_TO_END_OF_LINE
:
1257 case IDS_MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION
:
1264 bool Textfield::GetAcceleratorForCommandId(int command_id
,
1265 ui::Accelerator
* accelerator
) {
1266 switch (command_id
) {
1268 *accelerator
= ui::Accelerator(ui::VKEY_Z
, ui::EF_CONTROL_DOWN
);
1272 *accelerator
= ui::Accelerator(ui::VKEY_X
, ui::EF_CONTROL_DOWN
);
1276 *accelerator
= ui::Accelerator(ui::VKEY_C
, ui::EF_CONTROL_DOWN
);
1280 *accelerator
= ui::Accelerator(ui::VKEY_V
, ui::EF_CONTROL_DOWN
);
1283 case IDS_APP_SELECT_ALL
:
1284 *accelerator
= ui::Accelerator(ui::VKEY_A
, ui::EF_CONTROL_DOWN
);
1292 void Textfield::ExecuteCommand(int command_id
, int event_flags
) {
1293 DestroyTouchSelection();
1294 if (!IsCommandIdEnabled(command_id
))
1297 bool text_changed
= false;
1298 bool cursor_changed
= false;
1299 bool rtl
= GetTextDirection() == base::i18n::RIGHT_TO_LEFT
;
1300 gfx::VisualCursorDirection begin
= rtl
? gfx::CURSOR_RIGHT
: gfx::CURSOR_LEFT
;
1301 gfx::VisualCursorDirection end
= rtl
? gfx::CURSOR_LEFT
: gfx::CURSOR_RIGHT
;
1302 gfx::SelectionModel selection_model
= GetSelectionModel();
1304 OnBeforeUserAction();
1305 switch (command_id
) {
1307 text_changed
= cursor_changed
= model_
->Undo();
1310 text_changed
= cursor_changed
= model_
->Redo();
1313 text_changed
= cursor_changed
= Cut();
1319 text_changed
= cursor_changed
= Paste();
1321 case IDS_APP_DELETE
:
1322 text_changed
= cursor_changed
= model_
->Delete();
1324 case IDS_APP_SELECT_ALL
:
1327 case IDS_DELETE_BACKWARD
:
1328 text_changed
= cursor_changed
= model_
->Backspace();
1330 case IDS_DELETE_FORWARD
:
1331 text_changed
= cursor_changed
= model_
->Delete();
1333 case IDS_DELETE_TO_END_OF_LINE
:
1334 model_
->MoveCursor(gfx::LINE_BREAK
, end
, true);
1335 text_changed
= cursor_changed
= model_
->Delete();
1337 case IDS_DELETE_TO_BEGINNING_OF_LINE
:
1338 model_
->MoveCursor(gfx::LINE_BREAK
, begin
, true);
1339 text_changed
= cursor_changed
= model_
->Backspace();
1341 case IDS_DELETE_WORD_BACKWARD
:
1342 model_
->MoveCursor(gfx::WORD_BREAK
, begin
, true);
1343 text_changed
= cursor_changed
= model_
->Backspace();
1345 case IDS_DELETE_WORD_FORWARD
:
1346 model_
->MoveCursor(gfx::WORD_BREAK
, end
, true);
1347 text_changed
= cursor_changed
= model_
->Delete();
1350 model_
->MoveCursor(gfx::CHARACTER_BREAK
, gfx::CURSOR_LEFT
, false);
1352 case IDS_MOVE_LEFT_AND_MODIFY_SELECTION
:
1353 model_
->MoveCursor(gfx::CHARACTER_BREAK
, gfx::CURSOR_LEFT
, true);
1355 case IDS_MOVE_RIGHT
:
1356 model_
->MoveCursor(gfx::CHARACTER_BREAK
, gfx::CURSOR_RIGHT
, false);
1358 case IDS_MOVE_RIGHT_AND_MODIFY_SELECTION
:
1359 model_
->MoveCursor(gfx::CHARACTER_BREAK
, gfx::CURSOR_RIGHT
, true);
1361 case IDS_MOVE_WORD_LEFT
:
1362 model_
->MoveCursor(gfx::WORD_BREAK
, gfx::CURSOR_LEFT
, false);
1364 case IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION
:
1365 model_
->MoveCursor(gfx::WORD_BREAK
, gfx::CURSOR_LEFT
, true);
1367 case IDS_MOVE_WORD_RIGHT
:
1368 model_
->MoveCursor(gfx::WORD_BREAK
, gfx::CURSOR_RIGHT
, false);
1370 case IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION
:
1371 model_
->MoveCursor(gfx::WORD_BREAK
, gfx::CURSOR_RIGHT
, true);
1373 case IDS_MOVE_TO_BEGINNING_OF_LINE
:
1374 model_
->MoveCursor(gfx::LINE_BREAK
, begin
, false);
1376 case IDS_MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION
:
1377 model_
->MoveCursor(gfx::LINE_BREAK
, begin
, true);
1379 case IDS_MOVE_TO_END_OF_LINE
:
1380 model_
->MoveCursor(gfx::LINE_BREAK
, end
, false);
1382 case IDS_MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION
:
1383 model_
->MoveCursor(gfx::LINE_BREAK
, end
, true);
1390 cursor_changed
|= GetSelectionModel() != selection_model
;
1392 UpdateSelectionClipboard();
1393 UpdateAfterChange(text_changed
, cursor_changed
);
1394 OnAfterUserAction();
1397 ////////////////////////////////////////////////////////////////////////////////
1398 // Textfield, ui::TextInputClient overrides:
1400 void Textfield::SetCompositionText(const ui::CompositionText
& composition
) {
1401 if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE
)
1404 OnBeforeUserAction();
1405 skip_input_method_cancel_composition_
= true;
1406 model_
->SetCompositionText(composition
);
1407 skip_input_method_cancel_composition_
= false;
1408 UpdateAfterChange(true, true);
1409 OnAfterUserAction();
1412 void Textfield::ConfirmCompositionText() {
1413 if (!model_
->HasCompositionText())
1416 OnBeforeUserAction();
1417 skip_input_method_cancel_composition_
= true;
1418 model_
->ConfirmCompositionText();
1419 skip_input_method_cancel_composition_
= false;
1420 UpdateAfterChange(true, true);
1421 OnAfterUserAction();
1424 void Textfield::ClearCompositionText() {
1425 if (!model_
->HasCompositionText())
1428 OnBeforeUserAction();
1429 skip_input_method_cancel_composition_
= true;
1430 model_
->CancelCompositionText();
1431 skip_input_method_cancel_composition_
= false;
1432 UpdateAfterChange(true, true);
1433 OnAfterUserAction();
1436 void Textfield::InsertText(const base::string16
& new_text
) {
1437 // TODO(suzhe): Filter invalid characters.
1438 if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE
|| new_text
.empty())
1441 OnBeforeUserAction();
1442 skip_input_method_cancel_composition_
= true;
1443 if (GetRenderText()->insert_mode())
1444 model_
->InsertText(new_text
);
1446 model_
->ReplaceText(new_text
);
1447 skip_input_method_cancel_composition_
= false;
1448 UpdateAfterChange(true, true);
1449 OnAfterUserAction();
1452 void Textfield::InsertChar(base::char16 ch
, int flags
) {
1453 // Filter out all control characters, including tab and new line characters,
1454 // and all characters with Alt modifier (and Search on ChromeOS). But allow
1455 // characters with the AltGr modifier. On Windows AltGr is represented by
1456 // Alt+Ctrl, and on Linux it's a different flag that we don't care about.
1457 const bool should_insert_char
=
1458 ((ch
>= 0x20 && ch
< 0x7F) || ch
> 0x9F) &&
1459 !ui::IsSystemKeyModifier(flags
);
1460 if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE
|| !should_insert_char
)
1465 if (text_input_type_
== ui::TEXT_INPUT_TYPE_PASSWORD
&&
1466 password_reveal_duration_
!= base::TimeDelta()) {
1467 const size_t change_offset
= model_
->GetCursorPosition();
1468 DCHECK_GT(change_offset
, 0u);
1469 RevealPasswordChar(change_offset
- 1);
1473 ui::TextInputType
Textfield::GetTextInputType() const {
1474 if (read_only() || !enabled())
1475 return ui::TEXT_INPUT_TYPE_NONE
;
1476 return text_input_type_
;
1479 ui::TextInputMode
Textfield::GetTextInputMode() const {
1480 return ui::TEXT_INPUT_MODE_DEFAULT
;
1483 int Textfield::GetTextInputFlags() const {
1484 return text_input_flags_
;
1487 bool Textfield::CanComposeInline() const {
1491 gfx::Rect
Textfield::GetCaretBounds() const {
1492 gfx::Rect rect
= GetRenderText()->GetUpdatedCursorBounds();
1493 ConvertRectToScreen(this, &rect
);
1497 bool Textfield::GetCompositionCharacterBounds(uint32 index
,
1498 gfx::Rect
* rect
) const {
1500 if (!HasCompositionText())
1502 gfx::Range composition_range
;
1503 model_
->GetCompositionTextRange(&composition_range
);
1504 DCHECK(!composition_range
.is_empty());
1506 size_t text_index
= composition_range
.start() + index
;
1507 if (composition_range
.end() <= text_index
)
1509 gfx::RenderText
* render_text
= GetRenderText();
1510 if (!render_text
->IsValidCursorIndex(text_index
)) {
1511 text_index
= render_text
->IndexOfAdjacentGrapheme(
1512 text_index
, gfx::CURSOR_BACKWARD
);
1514 if (text_index
< composition_range
.start())
1516 const gfx::SelectionModel
caret(text_index
, gfx::CURSOR_BACKWARD
);
1517 *rect
= render_text
->GetCursorBounds(caret
, false);
1518 ConvertRectToScreen(this, rect
);
1522 bool Textfield::HasCompositionText() const {
1523 return model_
->HasCompositionText();
1526 bool Textfield::GetTextRange(gfx::Range
* range
) const {
1527 if (!ImeEditingAllowed())
1530 model_
->GetTextRange(range
);
1534 bool Textfield::GetCompositionTextRange(gfx::Range
* range
) const {
1535 if (!ImeEditingAllowed())
1538 model_
->GetCompositionTextRange(range
);
1542 bool Textfield::GetSelectionRange(gfx::Range
* range
) const {
1543 if (!ImeEditingAllowed())
1545 *range
= GetRenderText()->selection();
1549 bool Textfield::SetSelectionRange(const gfx::Range
& range
) {
1550 if (!ImeEditingAllowed() || !range
.IsValid())
1552 OnBeforeUserAction();
1554 OnAfterUserAction();
1558 bool Textfield::DeleteRange(const gfx::Range
& range
) {
1559 if (!ImeEditingAllowed() || range
.is_empty())
1562 OnBeforeUserAction();
1563 model_
->SelectRange(range
);
1564 if (model_
->HasSelection()) {
1565 model_
->DeleteSelection();
1566 UpdateAfterChange(true, true);
1568 OnAfterUserAction();
1572 bool Textfield::GetTextFromRange(const gfx::Range
& range
,
1573 base::string16
* range_text
) const {
1574 if (!ImeEditingAllowed() || !range
.IsValid())
1577 gfx::Range text_range
;
1578 if (!GetTextRange(&text_range
) || !text_range
.Contains(range
))
1581 *range_text
= model_
->GetTextFromRange(range
);
1585 void Textfield::OnInputMethodChanged() {}
1587 bool Textfield::ChangeTextDirectionAndLayoutAlignment(
1588 base::i18n::TextDirection direction
) {
1589 // Restore text directionality mode when the indicated direction matches the
1590 // current forced mode; otherwise, force the mode indicated. This helps users
1591 // manage BiDi text layout without getting stuck in forced LTR or RTL modes.
1592 const gfx::DirectionalityMode mode
= direction
== base::i18n::RIGHT_TO_LEFT
?
1593 gfx::DIRECTIONALITY_FORCE_RTL
: gfx::DIRECTIONALITY_FORCE_LTR
;
1594 if (mode
== GetRenderText()->directionality_mode())
1595 GetRenderText()->SetDirectionalityMode(gfx::DIRECTIONALITY_FROM_TEXT
);
1597 GetRenderText()->SetDirectionalityMode(mode
);
1602 void Textfield::ExtendSelectionAndDelete(size_t before
, size_t after
) {
1603 gfx::Range range
= GetRenderText()->selection();
1604 DCHECK_GE(range
.start(), before
);
1606 range
.set_start(range
.start() - before
);
1607 range
.set_end(range
.end() + after
);
1608 gfx::Range text_range
;
1609 if (GetTextRange(&text_range
) && text_range
.Contains(range
))
1613 void Textfield::EnsureCaretInRect(const gfx::Rect
& rect
) {}
1615 bool Textfield::IsEditCommandEnabled(int command_id
) {
1616 return IsCommandIdEnabled(command_id
);
1619 void Textfield::SetEditCommandForNextKeyEvent(int command_id
) {
1620 DCHECK_EQ(kNoCommand
, scheduled_edit_command_
);
1621 scheduled_edit_command_
= command_id
;
1624 ////////////////////////////////////////////////////////////////////////////////
1625 // Textfield, protected:
1627 void Textfield::DoInsertChar(base::char16 ch
) {
1628 OnBeforeUserAction();
1629 skip_input_method_cancel_composition_
= true;
1630 if (GetRenderText()->insert_mode())
1631 model_
->InsertChar(ch
);
1633 model_
->ReplaceChar(ch
);
1634 skip_input_method_cancel_composition_
= false;
1636 UpdateAfterChange(true, true);
1637 OnAfterUserAction();
1640 gfx::RenderText
* Textfield::GetRenderText() const {
1641 return model_
->render_text();
1644 base::string16
Textfield::GetSelectionClipboardText() const {
1645 base::string16 selection_clipboard_text
;
1646 ui::Clipboard::GetForCurrentThread()->ReadText(
1647 ui::CLIPBOARD_TYPE_SELECTION
, &selection_clipboard_text
);
1648 return selection_clipboard_text
;
1651 ////////////////////////////////////////////////////////////////////////////////
1652 // Textfield, private:
1654 void Textfield::AccessibilitySetValue(const base::string16
& new_value
) {
1661 void Textfield::UpdateBackgroundColor() {
1662 const SkColor color
= GetBackgroundColor();
1663 set_background(Background::CreateSolidBackground(color
));
1664 // Disable subpixel rendering when the background color is transparent
1665 // because it draws incorrect colors around the glyphs in that case.
1666 // See crbug.com/115198
1667 GetRenderText()->set_subpixel_rendering_suppressed(
1668 SkColorGetA(color
) != 0xFF);
1672 void Textfield::UpdateAfterChange(bool text_changed
, bool cursor_changed
) {
1675 controller_
->ContentsChanged(this, text());
1676 NotifyAccessibilityEvent(ui::AX_EVENT_TEXT_CHANGED
, true);
1678 if (cursor_changed
) {
1679 cursor_visible_
= true;
1681 if (cursor_repaint_timer_
.IsRunning())
1682 cursor_repaint_timer_
.Reset();
1683 if (!text_changed
) {
1684 // TEXT_CHANGED implies TEXT_SELECTION_CHANGED, so we only need to fire
1685 // this if only the selection changed.
1686 NotifyAccessibilityEvent(ui::AX_EVENT_TEXT_SELECTION_CHANGED
, true);
1689 if (text_changed
|| cursor_changed
) {
1690 OnCaretBoundsChanged();
1695 void Textfield::UpdateCursor() {
1696 const size_t caret_blink_ms
= Textfield::GetCaretBlinkMs();
1697 cursor_visible_
= !cursor_visible_
|| (caret_blink_ms
== 0);
1701 void Textfield::RepaintCursor() {
1702 gfx::Rect
r(GetRenderText()->GetUpdatedCursorBounds());
1703 r
.Inset(-1, -1, -1, -1);
1704 SchedulePaintInRect(r
);
1707 void Textfield::PaintTextAndCursor(gfx::Canvas
* canvas
) {
1708 TRACE_EVENT0("views", "Textfield::PaintTextAndCursor");
1711 // Draw placeholder text if needed.
1712 gfx::RenderText
* render_text
= GetRenderText();
1713 if (text().empty() && !GetPlaceholderText().empty()) {
1714 canvas
->DrawStringRect(GetPlaceholderText(), GetFontList(),
1715 placeholder_text_color(), render_text
->display_rect());
1718 // Draw the text, cursor, and selection.
1719 render_text
->set_cursor_visible(cursor_visible_
&& !drop_cursor_visible_
&&
1721 render_text
->Draw(canvas
);
1723 // Draw the detached drop cursor that marks where the text will be dropped.
1724 if (drop_cursor_visible_
)
1725 render_text
->DrawCursor(canvas
, drop_cursor_position_
);
1730 void Textfield::MoveCursorTo(const gfx::Point
& point
, bool select
) {
1731 if (model_
->MoveCursorTo(point
, select
))
1732 UpdateAfterChange(false, true);
1735 void Textfield::SelectThroughLastDragLocation() {
1736 OnBeforeUserAction();
1737 model_
->MoveCursorTo(last_drag_location_
, true);
1738 if (aggregated_clicks_
== 1) {
1739 model_
->SelectWord();
1740 // Expand the selection so the initially selected word remains selected.
1741 gfx::Range selection
= GetRenderText()->selection();
1742 const size_t min
= std::min(selection
.GetMin(),
1743 double_click_word_
.GetMin());
1744 const size_t max
= std::max(selection
.GetMax(),
1745 double_click_word_
.GetMax());
1746 const bool reversed
= selection
.is_reversed();
1747 selection
.set_start(reversed
? max
: min
);
1748 selection
.set_end(reversed
? min
: max
);
1749 model_
->SelectRange(selection
);
1751 UpdateAfterChange(false, true);
1752 OnAfterUserAction();
1755 void Textfield::OnCaretBoundsChanged() {
1756 if (GetInputMethod())
1757 GetInputMethod()->OnCaretBoundsChanged(this);
1758 if (touch_selection_controller_
)
1759 touch_selection_controller_
->SelectionChanged();
1762 void Textfield::OnBeforeUserAction() {
1763 DCHECK(!performing_user_action_
);
1764 performing_user_action_
= true;
1766 controller_
->OnBeforeUserAction(this);
1769 void Textfield::OnAfterUserAction() {
1771 controller_
->OnAfterUserAction(this);
1772 DCHECK(performing_user_action_
);
1773 performing_user_action_
= false;
1776 bool Textfield::Cut() {
1777 if (!read_only() && text_input_type_
!= ui::TEXT_INPUT_TYPE_PASSWORD
&&
1780 controller_
->OnAfterCutOrCopy(ui::CLIPBOARD_TYPE_COPY_PASTE
);
1786 bool Textfield::Copy() {
1787 if (text_input_type_
!= ui::TEXT_INPUT_TYPE_PASSWORD
&& model_
->Copy()) {
1789 controller_
->OnAfterCutOrCopy(ui::CLIPBOARD_TYPE_COPY_PASTE
);
1795 bool Textfield::Paste() {
1796 if (!read_only() && model_
->Paste()) {
1798 controller_
->OnAfterPaste();
1804 void Textfield::UpdateContextMenu() {
1805 if (!context_menu_contents_
.get()) {
1806 context_menu_contents_
.reset(new ui::SimpleMenuModel(this));
1807 context_menu_contents_
->AddItemWithStringId(IDS_APP_UNDO
, IDS_APP_UNDO
);
1808 context_menu_contents_
->AddSeparator(ui::NORMAL_SEPARATOR
);
1809 context_menu_contents_
->AddItemWithStringId(IDS_APP_CUT
, IDS_APP_CUT
);
1810 context_menu_contents_
->AddItemWithStringId(IDS_APP_COPY
, IDS_APP_COPY
);
1811 context_menu_contents_
->AddItemWithStringId(IDS_APP_PASTE
, IDS_APP_PASTE
);
1812 context_menu_contents_
->AddItemWithStringId(IDS_APP_DELETE
, IDS_APP_DELETE
);
1813 context_menu_contents_
->AddSeparator(ui::NORMAL_SEPARATOR
);
1814 context_menu_contents_
->AddItemWithStringId(IDS_APP_SELECT_ALL
,
1815 IDS_APP_SELECT_ALL
);
1817 controller_
->UpdateContextMenu(context_menu_contents_
.get());
1819 context_menu_runner_
.reset(
1820 new MenuRunner(context_menu_contents_
.get(),
1821 MenuRunner::HAS_MNEMONICS
| MenuRunner::CONTEXT_MENU
));
1824 void Textfield::TrackMouseClicks(const ui::MouseEvent
& event
) {
1825 if (event
.IsOnlyLeftMouseButton()) {
1826 base::TimeDelta time_delta
= event
.time_stamp() - last_click_time_
;
1827 if (time_delta
.InMilliseconds() <= GetDoubleClickInterval() &&
1828 !ExceededDragThreshold(event
.location() - last_click_location_
)) {
1829 // Upon clicking after a triple click, the count should go back to double
1830 // click and alternate between double and triple. This assignment maps
1831 // 0 to 1, 1 to 2, 2 to 1.
1832 aggregated_clicks_
= (aggregated_clicks_
% 2) + 1;
1834 aggregated_clicks_
= 0;
1836 last_click_time_
= event
.time_stamp();
1837 last_click_location_
= event
.location();
1841 bool Textfield::ImeEditingAllowed() const {
1842 // Disallow input method editing of password fields.
1843 ui::TextInputType t
= GetTextInputType();
1844 return (t
!= ui::TEXT_INPUT_TYPE_NONE
&& t
!= ui::TEXT_INPUT_TYPE_PASSWORD
);
1847 void Textfield::RevealPasswordChar(int index
) {
1848 GetRenderText()->SetObscuredRevealIndex(index
);
1852 password_reveal_timer_
.Start(FROM_HERE
, password_reveal_duration_
,
1853 base::Bind(&Textfield::RevealPasswordChar
,
1854 weak_ptr_factory_
.GetWeakPtr(), -1));
1858 void Textfield::CreateTouchSelectionControllerAndNotifyIt() {
1862 if (!touch_selection_controller_
) {
1863 touch_selection_controller_
.reset(
1864 ui::TouchEditingControllerDeprecated::Create(this));
1866 if (touch_selection_controller_
)
1867 touch_selection_controller_
->SelectionChanged();
1870 void Textfield::UpdateSelectionClipboard() const {
1871 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
1872 if (performing_user_action_
&& HasSelection()) {
1873 ui::ScopedClipboardWriter(
1874 ui::CLIPBOARD_TYPE_SELECTION
).WriteText(GetSelectedText());
1876 controller_
->OnAfterCutOrCopy(ui::CLIPBOARD_TYPE_SELECTION
);
1881 void Textfield::PasteSelectionClipboard(const ui::MouseEvent
& event
) {
1882 DCHECK(event
.IsOnlyMiddleMouseButton());
1883 DCHECK(!read_only());
1884 base::string16 selection_clipboard_text
= GetSelectionClipboardText();
1885 OnBeforeUserAction();
1886 const gfx::SelectionModel mouse
=
1887 GetRenderText()->FindCursorPosition(event
.location());
1890 model_
->MoveCursorTo(mouse
);
1891 if (!selection_clipboard_text
.empty()) {
1892 model_
->InsertText(selection_clipboard_text
);
1893 UpdateAfterChange(true, true);
1895 OnAfterUserAction();
1898 } // namespace views