Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(137)

Side by Side Diff: ui/views/controls/textfield/textfield.cc

Issue 211593006: Make Views Textfield key handling execute commands. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Add GetCommandForKeyEvent helper; fix tests. Created 6 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « ui/base/strings/ui_strings.grd ('k') | ui/views/controls/textfield/textfield_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "ui/views/controls/textfield/textfield.h" 5 #include "ui/views/controls/textfield/textfield.h"
6 6
7 #include <string> 7 #include <string>
8 8
9 #include "base/debug/trace_event.h" 9 #include "base/debug/trace_event.h"
10 #include "grit/ui_strings.h" 10 #include "grit/ui_strings.h"
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
48 const SkColor kDefaultPlaceholderTextColor = SK_ColorLTGRAY; 48 const SkColor kDefaultPlaceholderTextColor = SK_ColorLTGRAY;
49 49
50 void ConvertRectToScreen(const View* src, gfx::Rect* r) { 50 void ConvertRectToScreen(const View* src, gfx::Rect* r) {
51 DCHECK(src); 51 DCHECK(src);
52 52
53 gfx::Point new_origin = r->origin(); 53 gfx::Point new_origin = r->origin();
54 View::ConvertPointToScreen(src, &new_origin); 54 View::ConvertPointToScreen(src, &new_origin);
55 r->set_origin(new_origin); 55 r->set_origin(new_origin);
56 } 56 }
57 57
58 int GetCommandForKeyEvent(const ui::KeyEvent& event, bool has_selection) {
59 if (event.type() != ui::ET_KEY_PRESSED || event.IsUnicodeKeyCode())
60 return 0;
oshima 2014/03/28 23:23:27 can you define a cont value for 0 (like NO_COMMAND
msw 2014/03/28 23:36:12 Done.
61
62 const bool shift = event.IsShiftDown();
63 const bool control = event.IsControlDown();
64 const bool alt = event.IsAltDown() || event.IsAltGrDown();
65 switch (event.key_code()) {
66 case ui::VKEY_Z:
67 if (control && !shift && !alt)
68 return IDS_APP_UNDO;
69 return (control && shift && !alt) ? IDS_APP_REDO : 0;
70 case ui::VKEY_Y:
71 return (control && !alt) ? IDS_APP_REDO : 0;
72 case ui::VKEY_A:
73 return (control && !alt) ? IDS_APP_SELECT_ALL : 0;
74 case ui::VKEY_X:
75 return (control && !alt) ? IDS_APP_CUT : 0;
76 case ui::VKEY_C:
77 return (control && !alt) ? IDS_APP_COPY : 0;
78 case ui::VKEY_V:
79 return (control && !alt) ? IDS_APP_PASTE : 0;
80 case ui::VKEY_RIGHT:
81 // Ignore alt+right, which may be a browser navigation shortcut.
82 if (alt)
83 return 0;
84 if (!shift)
85 return control ? IDS_MOVE_WORD_RIGHT : IDS_MOVE_RIGHT;
86 return control ? IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION :
87 IDS_MOVE_RIGHT_AND_MODIFY_SELECTION;
88 case ui::VKEY_LEFT:
89 // Ignore alt+left, which may be a browser navigation shortcut.
90 if (alt)
91 return 0;
92 if (!shift)
93 return control ? IDS_MOVE_WORD_LEFT : IDS_MOVE_LEFT;
94 return control ? IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION :
95 IDS_MOVE_LEFT_AND_MODIFY_SELECTION;
96 case ui::VKEY_HOME:
97 return shift ? IDS_MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION :
98 IDS_MOVE_TO_BEGINNING_OF_LINE;
99 case ui::VKEY_END:
100 return shift ? IDS_MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION :
101 IDS_MOVE_TO_END_OF_LINE;
102 case ui::VKEY_BACK:
103 if (!control || has_selection)
104 return IDS_DELETE_BACKWARD;
105 #if defined(OS_LINUX)
106 // Only erase by line break on Linux and ChromeOS.
107 if (shift)
108 return IDS_DELETE_TO_BEGINNING_OF_LINE;
109 #endif
110 return IDS_DELETE_WORD_BACKWARD;
111 case ui::VKEY_DELETE:
112 if (!control || has_selection)
113 return (shift && has_selection) ? IDS_APP_CUT : IDS_DELETE_FORWARD;
114 #if defined(OS_LINUX)
115 // Only erase by line break on Linux and ChromeOS.
116 if (shift)
117 return IDS_DELETE_TO_END_OF_LINE;
118 #endif
119 return IDS_DELETE_WORD_FORWARD;
120 case ui::VKEY_INSERT:
121 if (control && !shift)
122 return IDS_APP_COPY;
123 return (shift && !control) ? IDS_APP_PASTE : 0;
124 default:
125 return 0;
126 }
127 return 0;
128 }
129
58 } // namespace 130 } // namespace
59 131
60 // static 132 // static
61 const char Textfield::kViewClassName[] = "Textfield"; 133 const char Textfield::kViewClassName[] = "Textfield";
62 134
63 // static 135 // static
64 size_t Textfield::GetCaretBlinkMs() { 136 size_t Textfield::GetCaretBlinkMs() {
65 static const size_t default_value = 500; 137 static const size_t default_value = 500;
66 #if defined(OS_WIN) 138 #if defined(OS_WIN)
67 static const size_t system_value = ::GetCaretBlinkTime(); 139 static const size_t system_value = ::GetCaretBlinkTime();
(...skipping 349 matching lines...) Expand 10 before | Expand all | Expand 10 after
417 UpdateSelectionClipboard(); 489 UpdateSelectionClipboard();
418 OnAfterUserAction(); 490 OnAfterUserAction();
419 } 491 }
420 492
421 bool Textfield::OnKeyPressed(const ui::KeyEvent& event) { 493 bool Textfield::OnKeyPressed(const ui::KeyEvent& event) {
422 bool handled = controller_ && controller_->HandleKeyEvent(this, event); 494 bool handled = controller_ && controller_->HandleKeyEvent(this, event);
423 touch_selection_controller_.reset(); 495 touch_selection_controller_.reset();
424 if (handled) 496 if (handled)
425 return true; 497 return true;
426 498
427 // TODO(oshima): Refactor and consolidate with ExecuteCommand. 499 int command = GetCommandForKeyEvent(event, HasSelection());
428 if (event.type() == ui::ET_KEY_PRESSED) { 500 if (command != 0) {
429 ui::KeyboardCode key_code = event.key_code(); 501 ExecuteCommand(command);
430 if (key_code == ui::VKEY_TAB || event.IsUnicodeKeyCode()) 502 return true;
oshima 2014/03/28 23:23:27 Is this correct? This method used to return false
msw 2014/03/28 23:36:12 I think this is actually a better approach; if a c
431 return false;
432
433 gfx::RenderText* render_text = GetRenderText();
434 const bool editable = !read_only();
435 const bool readable = text_input_type_ != ui::TEXT_INPUT_TYPE_PASSWORD;
436 const bool shift = event.IsShiftDown();
437 const bool control = event.IsControlDown();
438 const bool alt = event.IsAltDown() || event.IsAltGrDown();
439 bool text_changed = false;
440 bool cursor_changed = false;
441
442 OnBeforeUserAction();
443 switch (key_code) {
444 case ui::VKEY_Z:
445 if (control && !shift && !alt && editable)
446 cursor_changed = text_changed = model_->Undo();
447 else if (control && shift && !alt && editable)
448 cursor_changed = text_changed = model_->Redo();
449 break;
450 case ui::VKEY_Y:
451 if (control && !alt && editable)
452 cursor_changed = text_changed = model_->Redo();
453 break;
454 case ui::VKEY_A:
455 if (control && !alt) {
456 model_->SelectAll(false);
457 UpdateSelectionClipboard();
458 cursor_changed = true;
459 }
460 break;
461 case ui::VKEY_X:
462 if (control && !alt && editable && readable)
463 cursor_changed = text_changed = Cut();
464 break;
465 case ui::VKEY_C:
466 if (control && !alt && readable)
467 Copy();
468 break;
469 case ui::VKEY_V:
470 if (control && !alt && editable)
471 cursor_changed = text_changed = Paste();
472 break;
473 case ui::VKEY_RIGHT:
474 case ui::VKEY_LEFT: {
475 // We should ignore the alt-left/right keys because alt key doesn't make
476 // any special effects for them and they can be shortcut keys such like
477 // forward/back of the browser history.
478 if (alt)
479 break;
480 const gfx::Range selection_range = render_text->selection();
481 model_->MoveCursor(
482 control ? gfx::WORD_BREAK : gfx::CHARACTER_BREAK,
483 (key_code == ui::VKEY_RIGHT) ? gfx::CURSOR_RIGHT : gfx::CURSOR_LEFT,
484 shift);
485 UpdateSelectionClipboard();
486 cursor_changed = render_text->selection() != selection_range;
487 break;
488 }
489 case ui::VKEY_END:
490 case ui::VKEY_HOME:
491 if ((key_code == ui::VKEY_HOME) ==
492 (render_text->GetTextDirection() == base::i18n::RIGHT_TO_LEFT))
493 model_->MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_RIGHT, shift);
494 else
495 model_->MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_LEFT, shift);
496 UpdateSelectionClipboard();
497 cursor_changed = true;
498 break;
499 case ui::VKEY_BACK:
500 case ui::VKEY_DELETE:
501 if (!editable)
502 break;
503 if (!model_->HasSelection()) {
504 gfx::VisualCursorDirection direction = (key_code == ui::VKEY_DELETE) ?
505 gfx::CURSOR_RIGHT : gfx::CURSOR_LEFT;
506 if (shift && control) {
507 // If shift and control are pressed, erase up to the next line break
508 // on Linux and ChromeOS. Otherwise, do nothing.
509 #if defined(OS_LINUX)
510 model_->MoveCursor(gfx::LINE_BREAK, direction, true);
511 #else
512 break;
513 #endif
514 } else if (control) {
515 // If only control is pressed, then erase the previous/next word.
516 model_->MoveCursor(gfx::WORD_BREAK, direction, true);
517 }
518 }
519 if (key_code == ui::VKEY_BACK)
520 model_->Backspace();
521 else if (shift && model_->HasSelection() && readable)
522 Cut();
523 else
524 model_->Delete();
525
526 // Consume backspace and delete keys even if the edit did nothing. This
527 // prevents potential unintended side-effects of further event handling.
528 text_changed = true;
529 break;
530 case ui::VKEY_INSERT:
531 if (control && !shift && readable)
532 Copy();
533 else if (shift && !control && editable)
534 cursor_changed = text_changed = Paste();
535 break;
536 default:
537 break;
538 }
539
540 // We must have input method in order to support text input.
541 DCHECK(GetInputMethod());
542 UpdateAfterChange(text_changed, cursor_changed);
543 OnAfterUserAction();
544 return (text_changed || cursor_changed);
545 } 503 }
546 return false; 504 return false;
547 } 505 }
548 506
549 ui::TextInputClient* Textfield::GetTextInputClient() { 507 ui::TextInputClient* Textfield::GetTextInputClient() {
550 return read_only_ ? NULL : this; 508 return read_only_ ? NULL : this;
551 } 509 }
552 510
553 void Textfield::OnGestureEvent(ui::GestureEvent* event) { 511 void Textfield::OnGestureEvent(ui::GestureEvent* event) {
554 switch (event->type()) { 512 switch (event->type()) {
(...skipping 398 matching lines...) Expand 10 before | Expand all | Expand 10 after
953 return true; 911 return true;
954 } 912 }
955 913
956 bool Textfield::IsCommandIdEnabled(int command_id) const { 914 bool Textfield::IsCommandIdEnabled(int command_id) const {
957 base::string16 result; 915 base::string16 result;
958 bool editable = !read_only(); 916 bool editable = !read_only();
959 bool readable = text_input_type_ != ui::TEXT_INPUT_TYPE_PASSWORD; 917 bool readable = text_input_type_ != ui::TEXT_INPUT_TYPE_PASSWORD;
960 switch (command_id) { 918 switch (command_id) {
961 case IDS_APP_UNDO: 919 case IDS_APP_UNDO:
962 return editable && model_->CanUndo(); 920 return editable && model_->CanUndo();
921 case IDS_APP_REDO:
922 return editable && model_->CanRedo();
963 case IDS_APP_CUT: 923 case IDS_APP_CUT:
964 return editable && readable && model_->HasSelection(); 924 return editable && readable && model_->HasSelection();
965 case IDS_APP_COPY: 925 case IDS_APP_COPY:
966 return readable && model_->HasSelection(); 926 return readable && model_->HasSelection();
967 case IDS_APP_PASTE: 927 case IDS_APP_PASTE:
968 ui::Clipboard::GetForCurrentThread()->ReadText( 928 ui::Clipboard::GetForCurrentThread()->ReadText(
969 ui::CLIPBOARD_TYPE_COPY_PASTE, &result); 929 ui::CLIPBOARD_TYPE_COPY_PASTE, &result);
970 return editable && !result.empty(); 930 return editable && !result.empty();
971 case IDS_APP_DELETE: 931 case IDS_APP_DELETE:
972 return editable && model_->HasSelection(); 932 return editable && model_->HasSelection();
973 case IDS_APP_SELECT_ALL: 933 case IDS_APP_SELECT_ALL:
974 return !text().empty(); 934 return !text().empty();
935 case IDS_DELETE_FORWARD:
936 case IDS_DELETE_BACKWARD:
937 case IDS_DELETE_TO_BEGINNING_OF_LINE:
938 case IDS_DELETE_TO_END_OF_LINE:
939 case IDS_DELETE_WORD_BACKWARD:
940 case IDS_DELETE_WORD_FORWARD:
941 return editable;
942 case IDS_MOVE_LEFT:
943 case IDS_MOVE_LEFT_AND_MODIFY_SELECTION:
944 case IDS_MOVE_RIGHT:
945 case IDS_MOVE_RIGHT_AND_MODIFY_SELECTION:
946 case IDS_MOVE_WORD_LEFT:
947 case IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION:
948 case IDS_MOVE_WORD_RIGHT:
949 case IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION:
950 case IDS_MOVE_TO_BEGINNING_OF_LINE:
951 case IDS_MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION:
952 case IDS_MOVE_TO_END_OF_LINE:
953 case IDS_MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION:
954 return true;
975 default: 955 default:
976 return false; 956 return false;
977 } 957 }
978 } 958 }
979 959
980 bool Textfield::GetAcceleratorForCommandId(int command_id, 960 bool Textfield::GetAcceleratorForCommandId(int command_id,
981 ui::Accelerator* accelerator) { 961 ui::Accelerator* accelerator) {
982 return false; 962 return false;
983 } 963 }
984 964
985 void Textfield::ExecuteCommand(int command_id, int event_flags) { 965 void Textfield::ExecuteCommand(int command_id, int event_flags) {
986 touch_selection_controller_.reset(); 966 touch_selection_controller_.reset();
987 if (!IsCommandIdEnabled(command_id)) 967 if (!IsCommandIdEnabled(command_id))
988 return; 968 return;
989 969
990 bool text_changed = false; 970 bool text_changed = false;
971 bool cursor_changed = false;
972 bool rtl = GetTextDirection() == base::i18n::RIGHT_TO_LEFT;
973 gfx::VisualCursorDirection begin = rtl ? gfx::CURSOR_RIGHT : gfx::CURSOR_LEFT;
974 gfx::VisualCursorDirection end = rtl ? gfx::CURSOR_LEFT : gfx::CURSOR_RIGHT;
975 gfx::Range selection_range = GetSelectedRange();
976
991 OnBeforeUserAction(); 977 OnBeforeUserAction();
992 switch (command_id) { 978 switch (command_id) {
993 case IDS_APP_UNDO: 979 case IDS_APP_UNDO:
994 text_changed = model_->Undo(); 980 text_changed = cursor_changed = model_->Undo();
981 break;
982 case IDS_APP_REDO:
983 text_changed = cursor_changed = model_->Redo();
995 break; 984 break;
996 case IDS_APP_CUT: 985 case IDS_APP_CUT:
997 text_changed = Cut(); 986 text_changed = cursor_changed = Cut();
998 break; 987 break;
999 case IDS_APP_COPY: 988 case IDS_APP_COPY:
1000 Copy(); 989 Copy();
1001 break; 990 break;
1002 case IDS_APP_PASTE: 991 case IDS_APP_PASTE:
1003 text_changed = Paste(); 992 text_changed = cursor_changed = Paste();
1004 break; 993 break;
1005 case IDS_APP_DELETE: 994 case IDS_APP_DELETE:
1006 text_changed = model_->Delete(); 995 text_changed = cursor_changed = model_->Delete();
1007 break; 996 break;
1008 case IDS_APP_SELECT_ALL: 997 case IDS_APP_SELECT_ALL:
1009 SelectAll(false); 998 SelectAll(false);
1010 break; 999 break;
1000 case IDS_DELETE_BACKWARD:
1001 text_changed = cursor_changed = model_->Backspace();
1002 break;
1003 case IDS_DELETE_FORWARD:
1004 text_changed = cursor_changed = model_->Delete();
1005 break;
1006 case IDS_DELETE_TO_END_OF_LINE:
1007 model_->MoveCursor(gfx::LINE_BREAK, end, true);
1008 text_changed = cursor_changed = model_->Delete();
1009 break;
1010 case IDS_DELETE_TO_BEGINNING_OF_LINE:
1011 model_->MoveCursor(gfx::LINE_BREAK, begin, true);
1012 text_changed = cursor_changed = model_->Backspace();
1013 break;
1014 case IDS_DELETE_WORD_BACKWARD:
1015 model_->MoveCursor(gfx::WORD_BREAK, begin, true);
1016 text_changed = cursor_changed = model_->Backspace();
1017 break;
1018 case IDS_DELETE_WORD_FORWARD:
1019 model_->MoveCursor(gfx::WORD_BREAK, end, true);
1020 text_changed = cursor_changed = model_->Delete();
1021 break;
1022 case IDS_MOVE_LEFT:
1023 model_->MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_LEFT, false);
1024 break;
1025 case IDS_MOVE_LEFT_AND_MODIFY_SELECTION:
1026 model_->MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_LEFT, true);
1027 break;
1028 case IDS_MOVE_RIGHT:
1029 model_->MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT, false);
1030 break;
1031 case IDS_MOVE_RIGHT_AND_MODIFY_SELECTION:
1032 model_->MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT, true);
1033 break;
1034 case IDS_MOVE_WORD_LEFT:
1035 model_->MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_LEFT, false);
1036 break;
1037 case IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION:
1038 model_->MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_LEFT, true);
1039 break;
1040 case IDS_MOVE_WORD_RIGHT:
1041 model_->MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT, false);
1042 break;
1043 case IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION:
1044 model_->MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT, true);
1045 break;
1046 case IDS_MOVE_TO_BEGINNING_OF_LINE:
1047 model_->MoveCursor(gfx::LINE_BREAK, begin, false);
1048 break;
1049 case IDS_MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION:
1050 model_->MoveCursor(gfx::LINE_BREAK, begin, true);
1051 break;
1052 case IDS_MOVE_TO_END_OF_LINE:
1053 model_->MoveCursor(gfx::LINE_BREAK, end, false);
1054 break;
1055 case IDS_MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION:
1056 model_->MoveCursor(gfx::LINE_BREAK, end, true);
1057 break;
1011 default: 1058 default:
1012 NOTREACHED(); 1059 NOTREACHED();
1013 break; 1060 break;
1014 } 1061 }
1015 UpdateAfterChange(text_changed, text_changed); 1062
1063 cursor_changed |= GetSelectedRange() != selection_range;
1064 if (cursor_changed)
1065 UpdateSelectionClipboard();
1066 UpdateAfterChange(text_changed, cursor_changed);
1016 OnAfterUserAction(); 1067 OnAfterUserAction();
1017 } 1068 }
1018 1069
1019 //////////////////////////////////////////////////////////////////////////////// 1070 ////////////////////////////////////////////////////////////////////////////////
1020 // Textfield, ui::TextInputClient overrides: 1071 // Textfield, ui::TextInputClient overrides:
1021 1072
1022 void Textfield::SetCompositionText(const ui::CompositionText& composition) { 1073 void Textfield::SetCompositionText(const ui::CompositionText& composition) {
1023 if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE) 1074 if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE)
1024 return; 1075 return;
1025 1076
(...skipping 470 matching lines...) Expand 10 before | Expand all | Expand 10 after
1496 const size_t length = selection_clipboard_text.length(); 1547 const size_t length = selection_clipboard_text.length();
1497 range = gfx::Range(range.start() + length, range.end() + length); 1548 range = gfx::Range(range.start() + length, range.end() + length);
1498 } 1549 }
1499 model_->MoveCursorTo(gfx::SelectionModel(range, affinity)); 1550 model_->MoveCursorTo(gfx::SelectionModel(range, affinity));
1500 UpdateAfterChange(true, true); 1551 UpdateAfterChange(true, true);
1501 OnAfterUserAction(); 1552 OnAfterUserAction();
1502 } 1553 }
1503 } 1554 }
1504 1555
1505 } // namespace views 1556 } // namespace views
OLDNEW
« no previous file with comments | « ui/base/strings/ui_strings.grd ('k') | ui/views/controls/textfield/textfield_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698