| OLD | NEW |
| (Empty) |
| 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. | |
| 4 | |
| 5 #include "ui/views/controls/textfield/textfield.h" | |
| 6 | |
| 7 #include <string> | |
| 8 | |
| 9 #include "base/debug/trace_event.h" | |
| 10 #include "ui/accessibility/ax_view_state.h" | |
| 11 #include "ui/base/clipboard/scoped_clipboard_writer.h" | |
| 12 #include "ui/base/cursor/cursor.h" | |
| 13 #include "ui/base/dragdrop/drag_drop_types.h" | |
| 14 #include "ui/base/dragdrop/drag_utils.h" | |
| 15 #include "ui/base/ui_base_switches_util.h" | |
| 16 #include "ui/compositor/scoped_animation_duration_scale_mode.h" | |
| 17 #include "ui/events/event.h" | |
| 18 #include "ui/events/keycodes/keyboard_codes.h" | |
| 19 #include "ui/gfx/canvas.h" | |
| 20 #include "ui/gfx/display.h" | |
| 21 #include "ui/gfx/insets.h" | |
| 22 #include "ui/gfx/screen.h" | |
| 23 #include "ui/native_theme/native_theme.h" | |
| 24 #include "ui/strings/grit/ui_strings.h" | |
| 25 #include "ui/views/background.h" | |
| 26 #include "ui/views/controls/focusable_border.h" | |
| 27 #include "ui/views/controls/label.h" | |
| 28 #include "ui/views/controls/menu/menu_runner.h" | |
| 29 #include "ui/views/controls/native/native_view_host.h" | |
| 30 #include "ui/views/controls/textfield/textfield_controller.h" | |
| 31 #include "ui/views/drag_utils.h" | |
| 32 #include "ui/views/ime/input_method.h" | |
| 33 #include "ui/views/metrics.h" | |
| 34 #include "ui/views/native_cursor.h" | |
| 35 #include "ui/views/painter.h" | |
| 36 #include "ui/views/views_delegate.h" | |
| 37 #include "ui/views/widget/widget.h" | |
| 38 | |
| 39 #if defined(OS_WIN) | |
| 40 #include "base/win/win_util.h" | |
| 41 #endif | |
| 42 | |
| 43 #if defined(OS_LINUX) && !defined(OS_CHROMEOS) | |
| 44 #include "base/strings/utf_string_conversions.h" | |
| 45 #include "ui/events/linux/text_edit_command_auralinux.h" | |
| 46 #include "ui/events/linux/text_edit_key_bindings_delegate_auralinux.h" | |
| 47 #endif | |
| 48 | |
| 49 namespace views { | |
| 50 | |
| 51 namespace { | |
| 52 | |
| 53 // Default placeholder text color. | |
| 54 const SkColor kDefaultPlaceholderTextColor = SK_ColorLTGRAY; | |
| 55 | |
| 56 const int kNoCommand = 0; | |
| 57 | |
| 58 void ConvertRectToScreen(const View* src, gfx::Rect* r) { | |
| 59 DCHECK(src); | |
| 60 | |
| 61 gfx::Point new_origin = r->origin(); | |
| 62 View::ConvertPointToScreen(src, &new_origin); | |
| 63 r->set_origin(new_origin); | |
| 64 } | |
| 65 | |
| 66 // Get the drag selection timer delay, respecting animation scaling for testing. | |
| 67 int GetDragSelectionDelay() { | |
| 68 switch (ui::ScopedAnimationDurationScaleMode::duration_scale_mode()) { | |
| 69 case ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION: return 100; | |
| 70 case ui::ScopedAnimationDurationScaleMode::FAST_DURATION: return 25; | |
| 71 case ui::ScopedAnimationDurationScaleMode::SLOW_DURATION: return 400; | |
| 72 case ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION: return 1; | |
| 73 case ui::ScopedAnimationDurationScaleMode::ZERO_DURATION: return 0; | |
| 74 } | |
| 75 return 100; | |
| 76 } | |
| 77 | |
| 78 // Get the default command for a given key |event| and selection state. | |
| 79 int GetCommandForKeyEvent(const ui::KeyEvent& event, bool has_selection) { | |
| 80 if (event.type() != ui::ET_KEY_PRESSED || event.IsUnicodeKeyCode()) | |
| 81 return kNoCommand; | |
| 82 | |
| 83 const bool shift = event.IsShiftDown(); | |
| 84 const bool control = event.IsControlDown(); | |
| 85 const bool alt = event.IsAltDown() || event.IsAltGrDown(); | |
| 86 switch (event.key_code()) { | |
| 87 case ui::VKEY_Z: | |
| 88 if (control && !shift && !alt) | |
| 89 return IDS_APP_UNDO; | |
| 90 return (control && shift && !alt) ? IDS_APP_REDO : kNoCommand; | |
| 91 case ui::VKEY_Y: | |
| 92 return (control && !alt) ? IDS_APP_REDO : kNoCommand; | |
| 93 case ui::VKEY_A: | |
| 94 return (control && !alt) ? IDS_APP_SELECT_ALL : kNoCommand; | |
| 95 case ui::VKEY_X: | |
| 96 return (control && !alt) ? IDS_APP_CUT : kNoCommand; | |
| 97 case ui::VKEY_C: | |
| 98 return (control && !alt) ? IDS_APP_COPY : kNoCommand; | |
| 99 case ui::VKEY_V: | |
| 100 return (control && !alt) ? IDS_APP_PASTE : kNoCommand; | |
| 101 case ui::VKEY_RIGHT: | |
| 102 // Ignore alt+right, which may be a browser navigation shortcut. | |
| 103 if (alt) | |
| 104 return kNoCommand; | |
| 105 if (!shift) | |
| 106 return control ? IDS_MOVE_WORD_RIGHT : IDS_MOVE_RIGHT; | |
| 107 return control ? IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION : | |
| 108 IDS_MOVE_RIGHT_AND_MODIFY_SELECTION; | |
| 109 case ui::VKEY_LEFT: | |
| 110 // Ignore alt+left, which may be a browser navigation shortcut. | |
| 111 if (alt) | |
| 112 return kNoCommand; | |
| 113 if (!shift) | |
| 114 return control ? IDS_MOVE_WORD_LEFT : IDS_MOVE_LEFT; | |
| 115 return control ? IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION : | |
| 116 IDS_MOVE_LEFT_AND_MODIFY_SELECTION; | |
| 117 case ui::VKEY_HOME: | |
| 118 return shift ? IDS_MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION : | |
| 119 IDS_MOVE_TO_BEGINNING_OF_LINE; | |
| 120 case ui::VKEY_END: | |
| 121 return shift ? IDS_MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION : | |
| 122 IDS_MOVE_TO_END_OF_LINE; | |
| 123 case ui::VKEY_BACK: | |
| 124 if (!control || has_selection) | |
| 125 return IDS_DELETE_BACKWARD; | |
| 126 #if defined(OS_LINUX) | |
| 127 // Only erase by line break on Linux and ChromeOS. | |
| 128 if (shift) | |
| 129 return IDS_DELETE_TO_BEGINNING_OF_LINE; | |
| 130 #endif | |
| 131 return IDS_DELETE_WORD_BACKWARD; | |
| 132 case ui::VKEY_DELETE: | |
| 133 if (!control || has_selection) | |
| 134 return (shift && has_selection) ? IDS_APP_CUT : IDS_DELETE_FORWARD; | |
| 135 #if defined(OS_LINUX) | |
| 136 // Only erase by line break on Linux and ChromeOS. | |
| 137 if (shift) | |
| 138 return IDS_DELETE_TO_END_OF_LINE; | |
| 139 #endif | |
| 140 return IDS_DELETE_WORD_FORWARD; | |
| 141 case ui::VKEY_INSERT: | |
| 142 if (control && !shift) | |
| 143 return IDS_APP_COPY; | |
| 144 return (shift && !control) ? IDS_APP_PASTE : kNoCommand; | |
| 145 default: | |
| 146 return kNoCommand; | |
| 147 } | |
| 148 } | |
| 149 | |
| 150 #if defined(OS_LINUX) && !defined(OS_CHROMEOS) | |
| 151 // Convert a custom text edit |command| to the equivalent views command ID. | |
| 152 int GetViewsCommand(const ui::TextEditCommandAuraLinux& command, bool rtl) { | |
| 153 const bool select = command.extend_selection(); | |
| 154 switch (command.command_id()) { | |
| 155 case ui::TextEditCommandAuraLinux::COPY: | |
| 156 return IDS_APP_COPY; | |
| 157 case ui::TextEditCommandAuraLinux::CUT: | |
| 158 return IDS_APP_CUT; | |
| 159 case ui::TextEditCommandAuraLinux::DELETE_BACKWARD: | |
| 160 return IDS_DELETE_BACKWARD; | |
| 161 case ui::TextEditCommandAuraLinux::DELETE_FORWARD: | |
| 162 return IDS_DELETE_FORWARD; | |
| 163 case ui::TextEditCommandAuraLinux::DELETE_TO_BEGINING_OF_LINE: | |
| 164 case ui::TextEditCommandAuraLinux::DELETE_TO_BEGINING_OF_PARAGRAPH: | |
| 165 return IDS_DELETE_TO_BEGINNING_OF_LINE; | |
| 166 case ui::TextEditCommandAuraLinux::DELETE_TO_END_OF_LINE: | |
| 167 case ui::TextEditCommandAuraLinux::DELETE_TO_END_OF_PARAGRAPH: | |
| 168 return IDS_DELETE_TO_END_OF_LINE; | |
| 169 case ui::TextEditCommandAuraLinux::DELETE_WORD_BACKWARD: | |
| 170 return IDS_DELETE_WORD_BACKWARD; | |
| 171 case ui::TextEditCommandAuraLinux::DELETE_WORD_FORWARD: | |
| 172 return IDS_DELETE_WORD_FORWARD; | |
| 173 case ui::TextEditCommandAuraLinux::INSERT_TEXT: | |
| 174 return kNoCommand; | |
| 175 case ui::TextEditCommandAuraLinux::MOVE_BACKWARD: | |
| 176 if (rtl) | |
| 177 return select ? IDS_MOVE_RIGHT_AND_MODIFY_SELECTION : IDS_MOVE_RIGHT; | |
| 178 return select ? IDS_MOVE_LEFT_AND_MODIFY_SELECTION : IDS_MOVE_LEFT; | |
| 179 case ui::TextEditCommandAuraLinux::MOVE_DOWN: | |
| 180 return IDS_MOVE_DOWN; | |
| 181 case ui::TextEditCommandAuraLinux::MOVE_FORWARD: | |
| 182 if (rtl) | |
| 183 return select ? IDS_MOVE_LEFT_AND_MODIFY_SELECTION : IDS_MOVE_LEFT; | |
| 184 return select ? IDS_MOVE_RIGHT_AND_MODIFY_SELECTION : IDS_MOVE_RIGHT; | |
| 185 case ui::TextEditCommandAuraLinux::MOVE_LEFT: | |
| 186 return select ? IDS_MOVE_LEFT_AND_MODIFY_SELECTION : IDS_MOVE_LEFT; | |
| 187 case ui::TextEditCommandAuraLinux::MOVE_PAGE_DOWN: | |
| 188 case ui::TextEditCommandAuraLinux::MOVE_PAGE_UP: | |
| 189 return kNoCommand; | |
| 190 case ui::TextEditCommandAuraLinux::MOVE_RIGHT: | |
| 191 return select ? IDS_MOVE_RIGHT_AND_MODIFY_SELECTION : IDS_MOVE_RIGHT; | |
| 192 case ui::TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_DOCUMENT: | |
| 193 case ui::TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_LINE: | |
| 194 case ui::TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_PARAGRAPH: | |
| 195 return select ? IDS_MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION : | |
| 196 IDS_MOVE_TO_BEGINNING_OF_LINE; | |
| 197 case ui::TextEditCommandAuraLinux::MOVE_TO_END_OF_DOCUMENT: | |
| 198 case ui::TextEditCommandAuraLinux::MOVE_TO_END_OF_LINE: | |
| 199 case ui::TextEditCommandAuraLinux::MOVE_TO_END_OF_PARAGRAPH: | |
| 200 return select ? IDS_MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION : | |
| 201 IDS_MOVE_TO_END_OF_LINE; | |
| 202 case ui::TextEditCommandAuraLinux::MOVE_UP: | |
| 203 return IDS_MOVE_UP; | |
| 204 case ui::TextEditCommandAuraLinux::MOVE_WORD_BACKWARD: | |
| 205 if (rtl) { | |
| 206 return select ? IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION : | |
| 207 IDS_MOVE_WORD_RIGHT; | |
| 208 } | |
| 209 return select ? IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION : | |
| 210 IDS_MOVE_WORD_LEFT; | |
| 211 case ui::TextEditCommandAuraLinux::MOVE_WORD_FORWARD: | |
| 212 if (rtl) { | |
| 213 return select ? IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION : | |
| 214 IDS_MOVE_WORD_LEFT; | |
| 215 } | |
| 216 return select ? IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION : | |
| 217 IDS_MOVE_WORD_RIGHT; | |
| 218 case ui::TextEditCommandAuraLinux::MOVE_WORD_LEFT: | |
| 219 return select ? IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION : | |
| 220 IDS_MOVE_WORD_LEFT; | |
| 221 case ui::TextEditCommandAuraLinux::MOVE_WORD_RIGHT: | |
| 222 return select ? IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION : | |
| 223 IDS_MOVE_WORD_RIGHT; | |
| 224 case ui::TextEditCommandAuraLinux::PASTE: | |
| 225 return IDS_APP_PASTE; | |
| 226 case ui::TextEditCommandAuraLinux::SELECT_ALL: | |
| 227 return IDS_APP_SELECT_ALL; | |
| 228 case ui::TextEditCommandAuraLinux::SET_MARK: | |
| 229 case ui::TextEditCommandAuraLinux::UNSELECT: | |
| 230 case ui::TextEditCommandAuraLinux::INVALID_COMMAND: | |
| 231 return kNoCommand; | |
| 232 } | |
| 233 return kNoCommand; | |
| 234 } | |
| 235 #endif | |
| 236 | |
| 237 } // namespace | |
| 238 | |
| 239 // static | |
| 240 const char Textfield::kViewClassName[] = "Textfield"; | |
| 241 const int Textfield::kTextPadding = 3; | |
| 242 | |
| 243 // static | |
| 244 size_t Textfield::GetCaretBlinkMs() { | |
| 245 static const size_t default_value = 500; | |
| 246 #if defined(OS_WIN) | |
| 247 static const size_t system_value = ::GetCaretBlinkTime(); | |
| 248 if (system_value != 0) | |
| 249 return (system_value == INFINITE) ? 0 : system_value; | |
| 250 #endif | |
| 251 return default_value; | |
| 252 } | |
| 253 | |
| 254 Textfield::Textfield() | |
| 255 : model_(new TextfieldModel(this)), | |
| 256 controller_(NULL), | |
| 257 read_only_(false), | |
| 258 default_width_in_chars_(0), | |
| 259 use_default_text_color_(true), | |
| 260 use_default_background_color_(true), | |
| 261 use_default_selection_text_color_(true), | |
| 262 use_default_selection_background_color_(true), | |
| 263 text_color_(SK_ColorBLACK), | |
| 264 background_color_(SK_ColorWHITE), | |
| 265 selection_text_color_(SK_ColorWHITE), | |
| 266 selection_background_color_(SK_ColorBLUE), | |
| 267 placeholder_text_color_(kDefaultPlaceholderTextColor), | |
| 268 text_input_type_(ui::TEXT_INPUT_TYPE_TEXT), | |
| 269 performing_user_action_(false), | |
| 270 skip_input_method_cancel_composition_(false), | |
| 271 cursor_visible_(false), | |
| 272 drop_cursor_visible_(false), | |
| 273 initiating_drag_(false), | |
| 274 aggregated_clicks_(0), | |
| 275 drag_start_display_offset_(0), | |
| 276 touch_handles_hidden_due_to_scroll_(false), | |
| 277 weak_ptr_factory_(this) { | |
| 278 set_context_menu_controller(this); | |
| 279 set_drag_controller(this); | |
| 280 SetBorder(scoped_ptr<Border>(new FocusableBorder())); | |
| 281 SetFocusable(true); | |
| 282 | |
| 283 if (ViewsDelegate::views_delegate) { | |
| 284 password_reveal_duration_ = ViewsDelegate::views_delegate-> | |
| 285 GetDefaultTextfieldObscuredRevealDuration(); | |
| 286 } | |
| 287 } | |
| 288 | |
| 289 Textfield::~Textfield() {} | |
| 290 | |
| 291 void Textfield::SetReadOnly(bool read_only) { | |
| 292 // Update read-only without changing the focusable state (or active, etc.). | |
| 293 read_only_ = read_only; | |
| 294 if (GetInputMethod()) | |
| 295 GetInputMethod()->OnTextInputTypeChanged(this); | |
| 296 SetColor(GetTextColor()); | |
| 297 UpdateBackgroundColor(); | |
| 298 } | |
| 299 | |
| 300 void Textfield::SetTextInputType(ui::TextInputType type) { | |
| 301 GetRenderText()->SetObscured(type == ui::TEXT_INPUT_TYPE_PASSWORD); | |
| 302 text_input_type_ = type; | |
| 303 OnCaretBoundsChanged(); | |
| 304 if (GetInputMethod()) | |
| 305 GetInputMethod()->OnTextInputTypeChanged(this); | |
| 306 SchedulePaint(); | |
| 307 } | |
| 308 | |
| 309 void Textfield::SetText(const base::string16& new_text) { | |
| 310 model_->SetText(new_text); | |
| 311 OnCaretBoundsChanged(); | |
| 312 SchedulePaint(); | |
| 313 NotifyAccessibilityEvent(ui::AX_EVENT_TEXT_CHANGED, true); | |
| 314 } | |
| 315 | |
| 316 void Textfield::AppendText(const base::string16& new_text) { | |
| 317 if (new_text.empty()) | |
| 318 return; | |
| 319 model_->Append(new_text); | |
| 320 OnCaretBoundsChanged(); | |
| 321 SchedulePaint(); | |
| 322 } | |
| 323 | |
| 324 void Textfield::InsertOrReplaceText(const base::string16& new_text) { | |
| 325 if (new_text.empty()) | |
| 326 return; | |
| 327 model_->InsertText(new_text); | |
| 328 OnCaretBoundsChanged(); | |
| 329 SchedulePaint(); | |
| 330 } | |
| 331 | |
| 332 base::i18n::TextDirection Textfield::GetTextDirection() const { | |
| 333 return GetRenderText()->GetTextDirection(); | |
| 334 } | |
| 335 | |
| 336 base::string16 Textfield::GetSelectedText() const { | |
| 337 return model_->GetSelectedText(); | |
| 338 } | |
| 339 | |
| 340 void Textfield::SelectAll(bool reversed) { | |
| 341 model_->SelectAll(reversed); | |
| 342 UpdateSelectionClipboard(); | |
| 343 UpdateAfterChange(false, true); | |
| 344 } | |
| 345 | |
| 346 void Textfield::SelectWordAt(const gfx::Point& point) { | |
| 347 model_->MoveCursorTo(point, false); | |
| 348 model_->SelectWord(); | |
| 349 UpdateAfterChange(false, true); | |
| 350 } | |
| 351 | |
| 352 void Textfield::ClearSelection() { | |
| 353 model_->ClearSelection(); | |
| 354 UpdateAfterChange(false, true); | |
| 355 } | |
| 356 | |
| 357 bool Textfield::HasSelection() const { | |
| 358 return !GetSelectedRange().is_empty(); | |
| 359 } | |
| 360 | |
| 361 SkColor Textfield::GetTextColor() const { | |
| 362 if (!use_default_text_color_) | |
| 363 return text_color_; | |
| 364 | |
| 365 return GetNativeTheme()->GetSystemColor(read_only() ? | |
| 366 ui::NativeTheme::kColorId_TextfieldReadOnlyColor : | |
| 367 ui::NativeTheme::kColorId_TextfieldDefaultColor); | |
| 368 } | |
| 369 | |
| 370 void Textfield::SetTextColor(SkColor color) { | |
| 371 text_color_ = color; | |
| 372 use_default_text_color_ = false; | |
| 373 SetColor(color); | |
| 374 } | |
| 375 | |
| 376 void Textfield::UseDefaultTextColor() { | |
| 377 use_default_text_color_ = true; | |
| 378 SetColor(GetTextColor()); | |
| 379 } | |
| 380 | |
| 381 SkColor Textfield::GetBackgroundColor() const { | |
| 382 if (!use_default_background_color_) | |
| 383 return background_color_; | |
| 384 | |
| 385 return GetNativeTheme()->GetSystemColor(read_only() ? | |
| 386 ui::NativeTheme::kColorId_TextfieldReadOnlyBackground : | |
| 387 ui::NativeTheme::kColorId_TextfieldDefaultBackground); | |
| 388 } | |
| 389 | |
| 390 void Textfield::SetBackgroundColor(SkColor color) { | |
| 391 background_color_ = color; | |
| 392 use_default_background_color_ = false; | |
| 393 UpdateBackgroundColor(); | |
| 394 } | |
| 395 | |
| 396 void Textfield::UseDefaultBackgroundColor() { | |
| 397 use_default_background_color_ = true; | |
| 398 UpdateBackgroundColor(); | |
| 399 } | |
| 400 | |
| 401 SkColor Textfield::GetSelectionTextColor() const { | |
| 402 return use_default_selection_text_color_ ? | |
| 403 GetNativeTheme()->GetSystemColor( | |
| 404 ui::NativeTheme::kColorId_TextfieldSelectionColor) : | |
| 405 selection_text_color_; | |
| 406 } | |
| 407 | |
| 408 void Textfield::SetSelectionTextColor(SkColor color) { | |
| 409 selection_text_color_ = color; | |
| 410 use_default_selection_text_color_ = false; | |
| 411 GetRenderText()->set_selection_color(GetSelectionTextColor()); | |
| 412 SchedulePaint(); | |
| 413 } | |
| 414 | |
| 415 void Textfield::UseDefaultSelectionTextColor() { | |
| 416 use_default_selection_text_color_ = true; | |
| 417 GetRenderText()->set_selection_color(GetSelectionTextColor()); | |
| 418 SchedulePaint(); | |
| 419 } | |
| 420 | |
| 421 void Textfield::SetShadows(const gfx::ShadowValues& shadows) { | |
| 422 GetRenderText()->set_shadows(shadows); | |
| 423 SchedulePaint(); | |
| 424 } | |
| 425 | |
| 426 SkColor Textfield::GetSelectionBackgroundColor() const { | |
| 427 return use_default_selection_background_color_ ? | |
| 428 GetNativeTheme()->GetSystemColor( | |
| 429 ui::NativeTheme::kColorId_TextfieldSelectionBackgroundFocused) : | |
| 430 selection_background_color_; | |
| 431 } | |
| 432 | |
| 433 void Textfield::SetSelectionBackgroundColor(SkColor color) { | |
| 434 selection_background_color_ = color; | |
| 435 use_default_selection_background_color_ = false; | |
| 436 GetRenderText()->set_selection_background_focused_color( | |
| 437 GetSelectionBackgroundColor()); | |
| 438 SchedulePaint(); | |
| 439 } | |
| 440 | |
| 441 void Textfield::UseDefaultSelectionBackgroundColor() { | |
| 442 use_default_selection_background_color_ = true; | |
| 443 GetRenderText()->set_selection_background_focused_color( | |
| 444 GetSelectionBackgroundColor()); | |
| 445 SchedulePaint(); | |
| 446 } | |
| 447 | |
| 448 bool Textfield::GetCursorEnabled() const { | |
| 449 return GetRenderText()->cursor_enabled(); | |
| 450 } | |
| 451 | |
| 452 void Textfield::SetCursorEnabled(bool enabled) { | |
| 453 GetRenderText()->SetCursorEnabled(enabled); | |
| 454 } | |
| 455 | |
| 456 const gfx::FontList& Textfield::GetFontList() const { | |
| 457 return GetRenderText()->font_list(); | |
| 458 } | |
| 459 | |
| 460 void Textfield::SetFontList(const gfx::FontList& font_list) { | |
| 461 GetRenderText()->SetFontList(font_list); | |
| 462 OnCaretBoundsChanged(); | |
| 463 PreferredSizeChanged(); | |
| 464 } | |
| 465 | |
| 466 base::string16 Textfield::GetPlaceholderText() const { | |
| 467 return placeholder_text_; | |
| 468 } | |
| 469 | |
| 470 gfx::HorizontalAlignment Textfield::GetHorizontalAlignment() const { | |
| 471 return GetRenderText()->horizontal_alignment(); | |
| 472 } | |
| 473 | |
| 474 void Textfield::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) { | |
| 475 GetRenderText()->SetHorizontalAlignment(alignment); | |
| 476 } | |
| 477 | |
| 478 void Textfield::ShowImeIfNeeded() { | |
| 479 if (enabled() && !read_only()) | |
| 480 GetInputMethod()->ShowImeIfNeeded(); | |
| 481 } | |
| 482 | |
| 483 bool Textfield::IsIMEComposing() const { | |
| 484 return model_->HasCompositionText(); | |
| 485 } | |
| 486 | |
| 487 const gfx::Range& Textfield::GetSelectedRange() const { | |
| 488 return GetRenderText()->selection(); | |
| 489 } | |
| 490 | |
| 491 void Textfield::SelectRange(const gfx::Range& range) { | |
| 492 model_->SelectRange(range); | |
| 493 UpdateAfterChange(false, true); | |
| 494 } | |
| 495 | |
| 496 const gfx::SelectionModel& Textfield::GetSelectionModel() const { | |
| 497 return GetRenderText()->selection_model(); | |
| 498 } | |
| 499 | |
| 500 void Textfield::SelectSelectionModel(const gfx::SelectionModel& sel) { | |
| 501 model_->SelectSelectionModel(sel); | |
| 502 UpdateAfterChange(false, true); | |
| 503 } | |
| 504 | |
| 505 size_t Textfield::GetCursorPosition() const { | |
| 506 return model_->GetCursorPosition(); | |
| 507 } | |
| 508 | |
| 509 void Textfield::SetColor(SkColor value) { | |
| 510 GetRenderText()->SetColor(value); | |
| 511 SchedulePaint(); | |
| 512 } | |
| 513 | |
| 514 void Textfield::ApplyColor(SkColor value, const gfx::Range& range) { | |
| 515 GetRenderText()->ApplyColor(value, range); | |
| 516 SchedulePaint(); | |
| 517 } | |
| 518 | |
| 519 void Textfield::SetStyle(gfx::TextStyle style, bool value) { | |
| 520 GetRenderText()->SetStyle(style, value); | |
| 521 SchedulePaint(); | |
| 522 } | |
| 523 | |
| 524 void Textfield::ApplyStyle(gfx::TextStyle style, | |
| 525 bool value, | |
| 526 const gfx::Range& range) { | |
| 527 GetRenderText()->ApplyStyle(style, value, range); | |
| 528 SchedulePaint(); | |
| 529 } | |
| 530 | |
| 531 void Textfield::ClearEditHistory() { | |
| 532 model_->ClearEditHistory(); | |
| 533 } | |
| 534 | |
| 535 void Textfield::SetAccessibleName(const base::string16& name) { | |
| 536 accessible_name_ = name; | |
| 537 } | |
| 538 | |
| 539 void Textfield::ExecuteCommand(int command_id) { | |
| 540 ExecuteCommand(command_id, ui::EF_NONE); | |
| 541 } | |
| 542 | |
| 543 void Textfield::SetFocusPainter(scoped_ptr<Painter> focus_painter) { | |
| 544 focus_painter_ = focus_painter.Pass(); | |
| 545 } | |
| 546 | |
| 547 bool Textfield::HasTextBeingDragged() { | |
| 548 return initiating_drag_; | |
| 549 } | |
| 550 | |
| 551 //////////////////////////////////////////////////////////////////////////////// | |
| 552 // Textfield, View overrides: | |
| 553 | |
| 554 gfx::Insets Textfield::GetInsets() const { | |
| 555 gfx::Insets insets = View::GetInsets(); | |
| 556 insets += gfx::Insets(kTextPadding, kTextPadding, kTextPadding, kTextPadding); | |
| 557 return insets; | |
| 558 } | |
| 559 | |
| 560 int Textfield::GetBaseline() const { | |
| 561 return GetInsets().top() + GetRenderText()->GetBaseline(); | |
| 562 } | |
| 563 | |
| 564 gfx::Size Textfield::GetPreferredSize() const { | |
| 565 const gfx::Insets& insets = GetInsets(); | |
| 566 return gfx::Size(GetFontList().GetExpectedTextWidth(default_width_in_chars_) + | |
| 567 insets.width(), GetFontList().GetHeight() + insets.height()); | |
| 568 } | |
| 569 | |
| 570 const char* Textfield::GetClassName() const { | |
| 571 return kViewClassName; | |
| 572 } | |
| 573 | |
| 574 gfx::NativeCursor Textfield::GetCursor(const ui::MouseEvent& event) { | |
| 575 bool in_selection = GetRenderText()->IsPointInSelection(event.location()); | |
| 576 bool drag_event = event.type() == ui::ET_MOUSE_DRAGGED; | |
| 577 bool text_cursor = !initiating_drag_ && (drag_event || !in_selection); | |
| 578 return text_cursor ? GetNativeIBeamCursor() : gfx::kNullCursor; | |
| 579 } | |
| 580 | |
| 581 bool Textfield::OnMousePressed(const ui::MouseEvent& event) { | |
| 582 TrackMouseClicks(event); | |
| 583 | |
| 584 if (!controller_ || !controller_->HandleMouseEvent(this, event)) { | |
| 585 if (event.IsOnlyLeftMouseButton() || event.IsOnlyRightMouseButton()) { | |
| 586 RequestFocus(); | |
| 587 ShowImeIfNeeded(); | |
| 588 } | |
| 589 | |
| 590 if (event.IsOnlyLeftMouseButton()) { | |
| 591 OnBeforeUserAction(); | |
| 592 initiating_drag_ = false; | |
| 593 switch (aggregated_clicks_) { | |
| 594 case 0: | |
| 595 if (GetRenderText()->IsPointInSelection(event.location())) | |
| 596 initiating_drag_ = true; | |
| 597 else | |
| 598 MoveCursorTo(event.location(), event.IsShiftDown()); | |
| 599 break; | |
| 600 case 1: | |
| 601 SelectWordAt(event.location()); | |
| 602 double_click_word_ = GetRenderText()->selection(); | |
| 603 break; | |
| 604 case 2: | |
| 605 SelectAll(false); | |
| 606 break; | |
| 607 default: | |
| 608 NOTREACHED(); | |
| 609 } | |
| 610 OnAfterUserAction(); | |
| 611 } | |
| 612 | |
| 613 #if defined(OS_LINUX) && !defined(OS_CHROMEOS) | |
| 614 if (event.IsOnlyMiddleMouseButton()) { | |
| 615 if (GetRenderText()->IsPointInSelection(event.location())) { | |
| 616 OnBeforeUserAction(); | |
| 617 ClearSelection(); | |
| 618 ui::ScopedClipboardWriter( | |
| 619 ui::CLIPBOARD_TYPE_SELECTION).WriteText(base::string16()); | |
| 620 OnAfterUserAction(); | |
| 621 } else if (!read_only()) { | |
| 622 PasteSelectionClipboard(event); | |
| 623 } | |
| 624 } | |
| 625 #endif | |
| 626 } | |
| 627 | |
| 628 return true; | |
| 629 } | |
| 630 | |
| 631 bool Textfield::OnMouseDragged(const ui::MouseEvent& event) { | |
| 632 last_drag_location_ = event.location(); | |
| 633 | |
| 634 // Don't adjust the cursor on a potential drag and drop, or if the mouse | |
| 635 // movement from the last mouse click does not exceed the drag threshold. | |
| 636 if (initiating_drag_ || !event.IsOnlyLeftMouseButton() || | |
| 637 !ExceededDragThreshold(last_drag_location_ - last_click_location_)) { | |
| 638 return true; | |
| 639 } | |
| 640 | |
| 641 // A timer is used to continuously scroll while selecting beyond side edges. | |
| 642 if ((event.location().x() > 0 && event.location().x() < size().width()) || | |
| 643 GetDragSelectionDelay() == 0) { | |
| 644 drag_selection_timer_.Stop(); | |
| 645 SelectThroughLastDragLocation(); | |
| 646 } else if (!drag_selection_timer_.IsRunning()) { | |
| 647 drag_selection_timer_.Start( | |
| 648 FROM_HERE, base::TimeDelta::FromMilliseconds(GetDragSelectionDelay()), | |
| 649 this, &Textfield::SelectThroughLastDragLocation); | |
| 650 } | |
| 651 | |
| 652 return true; | |
| 653 } | |
| 654 | |
| 655 void Textfield::OnMouseReleased(const ui::MouseEvent& event) { | |
| 656 OnBeforeUserAction(); | |
| 657 drag_selection_timer_.Stop(); | |
| 658 // Cancel suspected drag initiations, the user was clicking in the selection. | |
| 659 if (initiating_drag_) | |
| 660 MoveCursorTo(event.location(), false); | |
| 661 initiating_drag_ = false; | |
| 662 UpdateSelectionClipboard(); | |
| 663 OnAfterUserAction(); | |
| 664 } | |
| 665 | |
| 666 bool Textfield::OnKeyPressed(const ui::KeyEvent& event) { | |
| 667 // Since HandleKeyEvent() might destroy |this|, get a weak pointer and verify | |
| 668 // it isn't null before proceeding. | |
| 669 base::WeakPtr<Textfield> textfield(weak_ptr_factory_.GetWeakPtr()); | |
| 670 | |
| 671 bool handled = controller_ && controller_->HandleKeyEvent(this, event); | |
| 672 | |
| 673 if (!textfield) | |
| 674 return handled; | |
| 675 | |
| 676 #if defined(OS_LINUX) && !defined(OS_CHROMEOS) | |
| 677 ui::TextEditKeyBindingsDelegateAuraLinux* delegate = | |
| 678 ui::GetTextEditKeyBindingsDelegate(); | |
| 679 std::vector<ui::TextEditCommandAuraLinux> commands; | |
| 680 if (!handled && delegate && delegate->MatchEvent(event, &commands)) { | |
| 681 const bool rtl = GetTextDirection() == base::i18n::RIGHT_TO_LEFT; | |
| 682 for (size_t i = 0; i < commands.size(); ++i) { | |
| 683 const int command = GetViewsCommand(commands[i], rtl); | |
| 684 if (IsCommandIdEnabled(command)) { | |
| 685 ExecuteCommand(command); | |
| 686 handled = true; | |
| 687 } | |
| 688 } | |
| 689 return handled; | |
| 690 } | |
| 691 #endif | |
| 692 | |
| 693 const int command = GetCommandForKeyEvent(event, HasSelection()); | |
| 694 if (!handled && IsCommandIdEnabled(command)) { | |
| 695 ExecuteCommand(command); | |
| 696 handled = true; | |
| 697 } | |
| 698 return handled; | |
| 699 } | |
| 700 | |
| 701 ui::TextInputClient* Textfield::GetTextInputClient() { | |
| 702 return read_only_ ? NULL : this; | |
| 703 } | |
| 704 | |
| 705 void Textfield::OnGestureEvent(ui::GestureEvent* event) { | |
| 706 switch (event->type()) { | |
| 707 case ui::ET_GESTURE_TAP_DOWN: | |
| 708 RequestFocus(); | |
| 709 ShowImeIfNeeded(); | |
| 710 event->SetHandled(); | |
| 711 break; | |
| 712 case ui::ET_GESTURE_TAP: | |
| 713 if (event->details().tap_count() == 1) { | |
| 714 if (!GetRenderText()->IsPointInSelection(event->location())) { | |
| 715 OnBeforeUserAction(); | |
| 716 MoveCursorTo(event->location(), false); | |
| 717 OnAfterUserAction(); | |
| 718 } | |
| 719 } else if (event->details().tap_count() == 2) { | |
| 720 OnBeforeUserAction(); | |
| 721 SelectWordAt(event->location()); | |
| 722 OnAfterUserAction(); | |
| 723 } else { | |
| 724 OnBeforeUserAction(); | |
| 725 SelectAll(false); | |
| 726 OnAfterUserAction(); | |
| 727 } | |
| 728 CreateTouchSelectionControllerAndNotifyIt(); | |
| 729 #if defined(OS_WIN) | |
| 730 if (!read_only()) | |
| 731 base::win::DisplayVirtualKeyboard(); | |
| 732 #endif | |
| 733 event->SetHandled(); | |
| 734 break; | |
| 735 case ui::ET_GESTURE_LONG_PRESS: | |
| 736 if (!GetRenderText()->IsPointInSelection(event->location())) { | |
| 737 // If long-press happens outside selection, select word and try to | |
| 738 // activate touch selection. | |
| 739 OnBeforeUserAction(); | |
| 740 SelectWordAt(event->location()); | |
| 741 OnAfterUserAction(); | |
| 742 CreateTouchSelectionControllerAndNotifyIt(); | |
| 743 // If touch selection activated successfully, mark event as handled so | |
| 744 // that the regular context menu is not shown. | |
| 745 if (touch_selection_controller_) | |
| 746 event->SetHandled(); | |
| 747 } else { | |
| 748 // If long-press happens on the selection, deactivate touch selection | |
| 749 // and try to initiate drag-drop. If drag-drop is not enabled, context | |
| 750 // menu will be shown. Event is not marked as handled to let Views | |
| 751 // handle drag-drop or context menu. | |
| 752 DestroyTouchSelection(); | |
| 753 initiating_drag_ = switches::IsTouchDragDropEnabled(); | |
| 754 } | |
| 755 break; | |
| 756 case ui::ET_GESTURE_LONG_TAP: | |
| 757 // If touch selection is enabled, the context menu on long tap will be | |
| 758 // shown by the |touch_selection_controller_|, hence we mark the event | |
| 759 // handled so Views does not try to show context menu on it. | |
| 760 if (touch_selection_controller_) | |
| 761 event->SetHandled(); | |
| 762 break; | |
| 763 case ui::ET_GESTURE_SCROLL_BEGIN: | |
| 764 touch_handles_hidden_due_to_scroll_ = touch_selection_controller_ != NULL; | |
| 765 DestroyTouchSelection(); | |
| 766 drag_start_location_ = event->location(); | |
| 767 drag_start_display_offset_ = | |
| 768 GetRenderText()->GetUpdatedDisplayOffset().x(); | |
| 769 event->SetHandled(); | |
| 770 break; | |
| 771 case ui::ET_GESTURE_SCROLL_UPDATE: { | |
| 772 int new_offset = drag_start_display_offset_ + event->location().x() - | |
| 773 drag_start_location_.x(); | |
| 774 GetRenderText()->SetDisplayOffset(new_offset); | |
| 775 SchedulePaint(); | |
| 776 event->SetHandled(); | |
| 777 break; | |
| 778 } | |
| 779 case ui::ET_GESTURE_SCROLL_END: | |
| 780 case ui::ET_SCROLL_FLING_START: | |
| 781 if (touch_handles_hidden_due_to_scroll_) { | |
| 782 CreateTouchSelectionControllerAndNotifyIt(); | |
| 783 touch_handles_hidden_due_to_scroll_ = false; | |
| 784 } | |
| 785 event->SetHandled(); | |
| 786 break; | |
| 787 default: | |
| 788 return; | |
| 789 } | |
| 790 } | |
| 791 | |
| 792 void Textfield::AboutToRequestFocusFromTabTraversal(bool reverse) { | |
| 793 SelectAll(false); | |
| 794 } | |
| 795 | |
| 796 bool Textfield::SkipDefaultKeyEventProcessing(const ui::KeyEvent& event) { | |
| 797 #if defined(OS_LINUX) && !defined(OS_CHROMEOS) | |
| 798 // Skip any accelerator handling that conflicts with custom keybindings. | |
| 799 ui::TextEditKeyBindingsDelegateAuraLinux* delegate = | |
| 800 ui::GetTextEditKeyBindingsDelegate(); | |
| 801 std::vector<ui::TextEditCommandAuraLinux> commands; | |
| 802 if (delegate && delegate->MatchEvent(event, &commands)) { | |
| 803 const bool rtl = GetTextDirection() == base::i18n::RIGHT_TO_LEFT; | |
| 804 for (size_t i = 0; i < commands.size(); ++i) | |
| 805 if (IsCommandIdEnabled(GetViewsCommand(commands[i], rtl))) | |
| 806 return true; | |
| 807 } | |
| 808 #endif | |
| 809 | |
| 810 // Skip backspace accelerator handling; editable textfields handle this key. | |
| 811 // Also skip processing Windows [Alt]+<num-pad digit> Unicode alt-codes. | |
| 812 const bool is_backspace = event.key_code() == ui::VKEY_BACK; | |
| 813 return (is_backspace && !read_only()) || event.IsUnicodeKeyCode(); | |
| 814 } | |
| 815 | |
| 816 bool Textfield::GetDropFormats( | |
| 817 int* formats, | |
| 818 std::set<OSExchangeData::CustomFormat>* custom_formats) { | |
| 819 if (!enabled() || read_only()) | |
| 820 return false; | |
| 821 // TODO(msw): Can we support URL, FILENAME, etc.? | |
| 822 *formats = ui::OSExchangeData::STRING; | |
| 823 if (controller_) | |
| 824 controller_->AppendDropFormats(formats, custom_formats); | |
| 825 return true; | |
| 826 } | |
| 827 | |
| 828 bool Textfield::CanDrop(const OSExchangeData& data) { | |
| 829 int formats; | |
| 830 std::set<OSExchangeData::CustomFormat> custom_formats; | |
| 831 GetDropFormats(&formats, &custom_formats); | |
| 832 return enabled() && !read_only() && | |
| 833 data.HasAnyFormat(formats, custom_formats); | |
| 834 } | |
| 835 | |
| 836 int Textfield::OnDragUpdated(const ui::DropTargetEvent& event) { | |
| 837 DCHECK(CanDrop(event.data())); | |
| 838 gfx::RenderText* render_text = GetRenderText(); | |
| 839 const gfx::Range& selection = render_text->selection(); | |
| 840 drop_cursor_position_ = render_text->FindCursorPosition(event.location()); | |
| 841 bool in_selection = !selection.is_empty() && | |
| 842 selection.Contains(gfx::Range(drop_cursor_position_.caret_pos())); | |
| 843 drop_cursor_visible_ = !in_selection; | |
| 844 // TODO(msw): Pan over text when the user drags to the visible text edge. | |
| 845 OnCaretBoundsChanged(); | |
| 846 SchedulePaint(); | |
| 847 | |
| 848 if (initiating_drag_) { | |
| 849 if (in_selection) | |
| 850 return ui::DragDropTypes::DRAG_NONE; | |
| 851 return event.IsControlDown() ? ui::DragDropTypes::DRAG_COPY : | |
| 852 ui::DragDropTypes::DRAG_MOVE; | |
| 853 } | |
| 854 return ui::DragDropTypes::DRAG_COPY | ui::DragDropTypes::DRAG_MOVE; | |
| 855 } | |
| 856 | |
| 857 void Textfield::OnDragExited() { | |
| 858 drop_cursor_visible_ = false; | |
| 859 SchedulePaint(); | |
| 860 } | |
| 861 | |
| 862 int Textfield::OnPerformDrop(const ui::DropTargetEvent& event) { | |
| 863 DCHECK(CanDrop(event.data())); | |
| 864 drop_cursor_visible_ = false; | |
| 865 | |
| 866 if (controller_) { | |
| 867 int drag_operation = controller_->OnDrop(event.data()); | |
| 868 if (drag_operation != ui::DragDropTypes::DRAG_NONE) | |
| 869 return drag_operation; | |
| 870 } | |
| 871 | |
| 872 gfx::RenderText* render_text = GetRenderText(); | |
| 873 DCHECK(!initiating_drag_ || | |
| 874 !render_text->IsPointInSelection(event.location())); | |
| 875 OnBeforeUserAction(); | |
| 876 skip_input_method_cancel_composition_ = true; | |
| 877 | |
| 878 gfx::SelectionModel drop_destination_model = | |
| 879 render_text->FindCursorPosition(event.location()); | |
| 880 base::string16 new_text; | |
| 881 event.data().GetString(&new_text); | |
| 882 | |
| 883 // Delete the current selection for a drag and drop within this view. | |
| 884 const bool move = initiating_drag_ && !event.IsControlDown() && | |
| 885 event.source_operations() & ui::DragDropTypes::DRAG_MOVE; | |
| 886 if (move) { | |
| 887 // Adjust the drop destination if it is on or after the current selection. | |
| 888 size_t pos = drop_destination_model.caret_pos(); | |
| 889 pos -= render_text->selection().Intersect(gfx::Range(0, pos)).length(); | |
| 890 model_->DeleteSelectionAndInsertTextAt(new_text, pos); | |
| 891 } else { | |
| 892 model_->MoveCursorTo(drop_destination_model); | |
| 893 // Drop always inserts text even if the textfield is not in insert mode. | |
| 894 model_->InsertText(new_text); | |
| 895 } | |
| 896 skip_input_method_cancel_composition_ = false; | |
| 897 UpdateAfterChange(true, true); | |
| 898 OnAfterUserAction(); | |
| 899 return move ? ui::DragDropTypes::DRAG_MOVE : ui::DragDropTypes::DRAG_COPY; | |
| 900 } | |
| 901 | |
| 902 void Textfield::OnDragDone() { | |
| 903 initiating_drag_ = false; | |
| 904 drop_cursor_visible_ = false; | |
| 905 } | |
| 906 | |
| 907 void Textfield::GetAccessibleState(ui::AXViewState* state) { | |
| 908 state->role = ui::AX_ROLE_TEXT_FIELD; | |
| 909 state->name = accessible_name_; | |
| 910 if (read_only()) | |
| 911 state->AddStateFlag(ui::AX_STATE_READ_ONLY); | |
| 912 if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD) | |
| 913 state->AddStateFlag(ui::AX_STATE_PROTECTED); | |
| 914 state->value = text(); | |
| 915 | |
| 916 const gfx::Range range = GetSelectedRange(); | |
| 917 state->selection_start = range.start(); | |
| 918 state->selection_end = range.end(); | |
| 919 | |
| 920 if (!read_only()) { | |
| 921 state->set_value_callback = | |
| 922 base::Bind(&Textfield::AccessibilitySetValue, | |
| 923 weak_ptr_factory_.GetWeakPtr()); | |
| 924 } | |
| 925 } | |
| 926 | |
| 927 void Textfield::OnBoundsChanged(const gfx::Rect& previous_bounds) { | |
| 928 // Textfield insets include a reasonable amount of whitespace on all sides of | |
| 929 // the default font list. Fallback fonts with larger heights may paint over | |
| 930 // the vertical whitespace as needed. Alternate solutions involve undesirable | |
| 931 // behavior like changing the default font size, shrinking some fallback fonts | |
| 932 // beyond their legibility, or enlarging controls dynamically with content. | |
| 933 gfx::Rect bounds = GetContentsBounds(); | |
| 934 // GetContentsBounds() does not actually use the local GetInsets() override. | |
| 935 bounds.Inset(gfx::Insets(0, kTextPadding, 0, kTextPadding)); | |
| 936 GetRenderText()->SetDisplayRect(bounds); | |
| 937 OnCaretBoundsChanged(); | |
| 938 } | |
| 939 | |
| 940 bool Textfield::GetNeedsNotificationWhenVisibleBoundsChange() const { | |
| 941 return true; | |
| 942 } | |
| 943 | |
| 944 void Textfield::OnVisibleBoundsChanged() { | |
| 945 if (touch_selection_controller_) | |
| 946 touch_selection_controller_->SelectionChanged(); | |
| 947 } | |
| 948 | |
| 949 void Textfield::OnEnabledChanged() { | |
| 950 View::OnEnabledChanged(); | |
| 951 if (GetInputMethod()) | |
| 952 GetInputMethod()->OnTextInputTypeChanged(this); | |
| 953 SchedulePaint(); | |
| 954 } | |
| 955 | |
| 956 void Textfield::OnPaint(gfx::Canvas* canvas) { | |
| 957 OnPaintBackground(canvas); | |
| 958 PaintTextAndCursor(canvas); | |
| 959 OnPaintBorder(canvas); | |
| 960 } | |
| 961 | |
| 962 void Textfield::OnFocus() { | |
| 963 GetRenderText()->set_focused(true); | |
| 964 cursor_visible_ = true; | |
| 965 SchedulePaint(); | |
| 966 GetInputMethod()->OnFocus(); | |
| 967 OnCaretBoundsChanged(); | |
| 968 | |
| 969 const size_t caret_blink_ms = Textfield::GetCaretBlinkMs(); | |
| 970 if (caret_blink_ms != 0) { | |
| 971 cursor_repaint_timer_.Start(FROM_HERE, | |
| 972 base::TimeDelta::FromMilliseconds(caret_blink_ms), this, | |
| 973 &Textfield::UpdateCursor); | |
| 974 } | |
| 975 | |
| 976 View::OnFocus(); | |
| 977 SchedulePaint(); | |
| 978 } | |
| 979 | |
| 980 void Textfield::OnBlur() { | |
| 981 GetRenderText()->set_focused(false); | |
| 982 GetInputMethod()->OnBlur(); | |
| 983 cursor_repaint_timer_.Stop(); | |
| 984 if (cursor_visible_) { | |
| 985 cursor_visible_ = false; | |
| 986 RepaintCursor(); | |
| 987 } | |
| 988 | |
| 989 DestroyTouchSelection(); | |
| 990 | |
| 991 // Border typically draws focus indicator. | |
| 992 SchedulePaint(); | |
| 993 } | |
| 994 | |
| 995 gfx::Point Textfield::GetKeyboardContextMenuLocation() { | |
| 996 return GetCaretBounds().bottom_right(); | |
| 997 } | |
| 998 | |
| 999 void Textfield::OnNativeThemeChanged(const ui::NativeTheme* theme) { | |
| 1000 gfx::RenderText* render_text = GetRenderText(); | |
| 1001 render_text->SetColor(GetTextColor()); | |
| 1002 UpdateBackgroundColor(); | |
| 1003 render_text->set_cursor_color(GetTextColor()); | |
| 1004 render_text->set_selection_color(GetSelectionTextColor()); | |
| 1005 render_text->set_selection_background_focused_color( | |
| 1006 GetSelectionBackgroundColor()); | |
| 1007 } | |
| 1008 | |
| 1009 //////////////////////////////////////////////////////////////////////////////// | |
| 1010 // Textfield, TextfieldModel::Delegate overrides: | |
| 1011 | |
| 1012 void Textfield::OnCompositionTextConfirmedOrCleared() { | |
| 1013 if (!skip_input_method_cancel_composition_) | |
| 1014 GetInputMethod()->CancelComposition(this); | |
| 1015 } | |
| 1016 | |
| 1017 //////////////////////////////////////////////////////////////////////////////// | |
| 1018 // Textfield, ContextMenuController overrides: | |
| 1019 | |
| 1020 void Textfield::ShowContextMenuForView(View* source, | |
| 1021 const gfx::Point& point, | |
| 1022 ui::MenuSourceType source_type) { | |
| 1023 UpdateContextMenu(); | |
| 1024 ignore_result(context_menu_runner_->RunMenuAt(GetWidget(), | |
| 1025 NULL, | |
| 1026 gfx::Rect(point, gfx::Size()), | |
| 1027 MENU_ANCHOR_TOPLEFT, | |
| 1028 source_type)); | |
| 1029 } | |
| 1030 | |
| 1031 //////////////////////////////////////////////////////////////////////////////// | |
| 1032 // Textfield, DragController overrides: | |
| 1033 | |
| 1034 void Textfield::WriteDragDataForView(View* sender, | |
| 1035 const gfx::Point& press_pt, | |
| 1036 OSExchangeData* data) { | |
| 1037 const base::string16& selected_text(GetSelectedText()); | |
| 1038 data->SetString(selected_text); | |
| 1039 Label label(selected_text, GetFontList()); | |
| 1040 label.SetBackgroundColor(GetBackgroundColor()); | |
| 1041 label.SetSubpixelRenderingEnabled(false); | |
| 1042 gfx::Size size(label.GetPreferredSize()); | |
| 1043 gfx::NativeView native_view = GetWidget()->GetNativeView(); | |
| 1044 gfx::Display display = gfx::Screen::GetScreenFor(native_view)-> | |
| 1045 GetDisplayNearestWindow(native_view); | |
| 1046 size.SetToMin(gfx::Size(display.size().width(), height())); | |
| 1047 label.SetBoundsRect(gfx::Rect(size)); | |
| 1048 scoped_ptr<gfx::Canvas> canvas( | |
| 1049 GetCanvasForDragImage(GetWidget(), label.size())); | |
| 1050 label.SetEnabledColor(GetTextColor()); | |
| 1051 #if defined(OS_LINUX) && !defined(OS_CHROMEOS) | |
| 1052 // Desktop Linux Aura does not yet support transparency in drag images. | |
| 1053 canvas->DrawColor(GetBackgroundColor()); | |
| 1054 #endif | |
| 1055 label.Paint(canvas.get(), views::CullSet()); | |
| 1056 const gfx::Vector2d kOffset(-15, 0); | |
| 1057 drag_utils::SetDragImageOnDataObject(*canvas, kOffset, data); | |
| 1058 if (controller_) | |
| 1059 controller_->OnWriteDragData(data); | |
| 1060 } | |
| 1061 | |
| 1062 int Textfield::GetDragOperationsForView(View* sender, const gfx::Point& p) { | |
| 1063 int drag_operations = ui::DragDropTypes::DRAG_COPY; | |
| 1064 if (!enabled() || text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD || | |
| 1065 !GetRenderText()->IsPointInSelection(p)) { | |
| 1066 drag_operations = ui::DragDropTypes::DRAG_NONE; | |
| 1067 } else if (sender == this && !read_only()) { | |
| 1068 drag_operations = | |
| 1069 ui::DragDropTypes::DRAG_MOVE | ui::DragDropTypes::DRAG_COPY; | |
| 1070 } | |
| 1071 if (controller_) | |
| 1072 controller_->OnGetDragOperationsForTextfield(&drag_operations); | |
| 1073 return drag_operations; | |
| 1074 } | |
| 1075 | |
| 1076 bool Textfield::CanStartDragForView(View* sender, | |
| 1077 const gfx::Point& press_pt, | |
| 1078 const gfx::Point& p) { | |
| 1079 return initiating_drag_ && GetRenderText()->IsPointInSelection(press_pt); | |
| 1080 } | |
| 1081 | |
| 1082 //////////////////////////////////////////////////////////////////////////////// | |
| 1083 // Textfield, ui::TouchEditable overrides: | |
| 1084 | |
| 1085 void Textfield::SelectRect(const gfx::Point& start, const gfx::Point& end) { | |
| 1086 if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE) | |
| 1087 return; | |
| 1088 | |
| 1089 gfx::SelectionModel start_caret = GetRenderText()->FindCursorPosition(start); | |
| 1090 gfx::SelectionModel end_caret = GetRenderText()->FindCursorPosition(end); | |
| 1091 gfx::SelectionModel selection( | |
| 1092 gfx::Range(start_caret.caret_pos(), end_caret.caret_pos()), | |
| 1093 end_caret.caret_affinity()); | |
| 1094 | |
| 1095 OnBeforeUserAction(); | |
| 1096 SelectSelectionModel(selection); | |
| 1097 OnAfterUserAction(); | |
| 1098 } | |
| 1099 | |
| 1100 void Textfield::MoveCaretTo(const gfx::Point& point) { | |
| 1101 SelectRect(point, point); | |
| 1102 } | |
| 1103 | |
| 1104 void Textfield::GetSelectionEndPoints(gfx::Rect* p1, gfx::Rect* p2) { | |
| 1105 gfx::RenderText* render_text = GetRenderText(); | |
| 1106 const gfx::SelectionModel& sel = render_text->selection_model(); | |
| 1107 gfx::SelectionModel start_sel = | |
| 1108 render_text->GetSelectionModelForSelectionStart(); | |
| 1109 *p1 = render_text->GetCursorBounds(start_sel, true); | |
| 1110 *p2 = render_text->GetCursorBounds(sel, true); | |
| 1111 } | |
| 1112 | |
| 1113 gfx::Rect Textfield::GetBounds() { | |
| 1114 return GetLocalBounds(); | |
| 1115 } | |
| 1116 | |
| 1117 gfx::NativeView Textfield::GetNativeView() const { | |
| 1118 return GetWidget()->GetNativeView(); | |
| 1119 } | |
| 1120 | |
| 1121 void Textfield::ConvertPointToScreen(gfx::Point* point) { | |
| 1122 View::ConvertPointToScreen(this, point); | |
| 1123 } | |
| 1124 | |
| 1125 void Textfield::ConvertPointFromScreen(gfx::Point* point) { | |
| 1126 View::ConvertPointFromScreen(this, point); | |
| 1127 } | |
| 1128 | |
| 1129 bool Textfield::DrawsHandles() { | |
| 1130 return false; | |
| 1131 } | |
| 1132 | |
| 1133 void Textfield::OpenContextMenu(const gfx::Point& anchor) { | |
| 1134 DestroyTouchSelection(); | |
| 1135 ShowContextMenu(anchor, ui::MENU_SOURCE_TOUCH_EDIT_MENU); | |
| 1136 } | |
| 1137 | |
| 1138 void Textfield::DestroyTouchSelection() { | |
| 1139 touch_selection_controller_.reset(); | |
| 1140 } | |
| 1141 | |
| 1142 //////////////////////////////////////////////////////////////////////////////// | |
| 1143 // Textfield, ui::SimpleMenuModel::Delegate overrides: | |
| 1144 | |
| 1145 bool Textfield::IsCommandIdChecked(int command_id) const { | |
| 1146 return true; | |
| 1147 } | |
| 1148 | |
| 1149 bool Textfield::IsCommandIdEnabled(int command_id) const { | |
| 1150 base::string16 result; | |
| 1151 bool editable = !read_only(); | |
| 1152 bool readable = text_input_type_ != ui::TEXT_INPUT_TYPE_PASSWORD; | |
| 1153 switch (command_id) { | |
| 1154 case IDS_APP_UNDO: | |
| 1155 return editable && model_->CanUndo(); | |
| 1156 case IDS_APP_REDO: | |
| 1157 return editable && model_->CanRedo(); | |
| 1158 case IDS_APP_CUT: | |
| 1159 return editable && readable && model_->HasSelection(); | |
| 1160 case IDS_APP_COPY: | |
| 1161 return readable && model_->HasSelection(); | |
| 1162 case IDS_APP_PASTE: | |
| 1163 ui::Clipboard::GetForCurrentThread()->ReadText( | |
| 1164 ui::CLIPBOARD_TYPE_COPY_PASTE, &result); | |
| 1165 return editable && !result.empty(); | |
| 1166 case IDS_APP_DELETE: | |
| 1167 return editable && model_->HasSelection(); | |
| 1168 case IDS_APP_SELECT_ALL: | |
| 1169 return !text().empty(); | |
| 1170 case IDS_DELETE_FORWARD: | |
| 1171 case IDS_DELETE_BACKWARD: | |
| 1172 case IDS_DELETE_TO_BEGINNING_OF_LINE: | |
| 1173 case IDS_DELETE_TO_END_OF_LINE: | |
| 1174 case IDS_DELETE_WORD_BACKWARD: | |
| 1175 case IDS_DELETE_WORD_FORWARD: | |
| 1176 return editable; | |
| 1177 case IDS_MOVE_LEFT: | |
| 1178 case IDS_MOVE_LEFT_AND_MODIFY_SELECTION: | |
| 1179 case IDS_MOVE_RIGHT: | |
| 1180 case IDS_MOVE_RIGHT_AND_MODIFY_SELECTION: | |
| 1181 case IDS_MOVE_WORD_LEFT: | |
| 1182 case IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION: | |
| 1183 case IDS_MOVE_WORD_RIGHT: | |
| 1184 case IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION: | |
| 1185 case IDS_MOVE_TO_BEGINNING_OF_LINE: | |
| 1186 case IDS_MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION: | |
| 1187 case IDS_MOVE_TO_END_OF_LINE: | |
| 1188 case IDS_MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION: | |
| 1189 return true; | |
| 1190 default: | |
| 1191 return false; | |
| 1192 } | |
| 1193 } | |
| 1194 | |
| 1195 bool Textfield::GetAcceleratorForCommandId(int command_id, | |
| 1196 ui::Accelerator* accelerator) { | |
| 1197 return false; | |
| 1198 } | |
| 1199 | |
| 1200 void Textfield::ExecuteCommand(int command_id, int event_flags) { | |
| 1201 DestroyTouchSelection(); | |
| 1202 if (!IsCommandIdEnabled(command_id)) | |
| 1203 return; | |
| 1204 | |
| 1205 bool text_changed = false; | |
| 1206 bool cursor_changed = false; | |
| 1207 bool rtl = GetTextDirection() == base::i18n::RIGHT_TO_LEFT; | |
| 1208 gfx::VisualCursorDirection begin = rtl ? gfx::CURSOR_RIGHT : gfx::CURSOR_LEFT; | |
| 1209 gfx::VisualCursorDirection end = rtl ? gfx::CURSOR_LEFT : gfx::CURSOR_RIGHT; | |
| 1210 gfx::SelectionModel selection_model = GetSelectionModel(); | |
| 1211 | |
| 1212 OnBeforeUserAction(); | |
| 1213 switch (command_id) { | |
| 1214 case IDS_APP_UNDO: | |
| 1215 text_changed = cursor_changed = model_->Undo(); | |
| 1216 break; | |
| 1217 case IDS_APP_REDO: | |
| 1218 text_changed = cursor_changed = model_->Redo(); | |
| 1219 break; | |
| 1220 case IDS_APP_CUT: | |
| 1221 text_changed = cursor_changed = Cut(); | |
| 1222 break; | |
| 1223 case IDS_APP_COPY: | |
| 1224 Copy(); | |
| 1225 break; | |
| 1226 case IDS_APP_PASTE: | |
| 1227 text_changed = cursor_changed = Paste(); | |
| 1228 break; | |
| 1229 case IDS_APP_DELETE: | |
| 1230 text_changed = cursor_changed = model_->Delete(); | |
| 1231 break; | |
| 1232 case IDS_APP_SELECT_ALL: | |
| 1233 SelectAll(false); | |
| 1234 break; | |
| 1235 case IDS_DELETE_BACKWARD: | |
| 1236 text_changed = cursor_changed = model_->Backspace(); | |
| 1237 break; | |
| 1238 case IDS_DELETE_FORWARD: | |
| 1239 text_changed = cursor_changed = model_->Delete(); | |
| 1240 break; | |
| 1241 case IDS_DELETE_TO_END_OF_LINE: | |
| 1242 model_->MoveCursor(gfx::LINE_BREAK, end, true); | |
| 1243 text_changed = cursor_changed = model_->Delete(); | |
| 1244 break; | |
| 1245 case IDS_DELETE_TO_BEGINNING_OF_LINE: | |
| 1246 model_->MoveCursor(gfx::LINE_BREAK, begin, true); | |
| 1247 text_changed = cursor_changed = model_->Backspace(); | |
| 1248 break; | |
| 1249 case IDS_DELETE_WORD_BACKWARD: | |
| 1250 model_->MoveCursor(gfx::WORD_BREAK, begin, true); | |
| 1251 text_changed = cursor_changed = model_->Backspace(); | |
| 1252 break; | |
| 1253 case IDS_DELETE_WORD_FORWARD: | |
| 1254 model_->MoveCursor(gfx::WORD_BREAK, end, true); | |
| 1255 text_changed = cursor_changed = model_->Delete(); | |
| 1256 break; | |
| 1257 case IDS_MOVE_LEFT: | |
| 1258 model_->MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_LEFT, false); | |
| 1259 break; | |
| 1260 case IDS_MOVE_LEFT_AND_MODIFY_SELECTION: | |
| 1261 model_->MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_LEFT, true); | |
| 1262 break; | |
| 1263 case IDS_MOVE_RIGHT: | |
| 1264 model_->MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT, false); | |
| 1265 break; | |
| 1266 case IDS_MOVE_RIGHT_AND_MODIFY_SELECTION: | |
| 1267 model_->MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT, true); | |
| 1268 break; | |
| 1269 case IDS_MOVE_WORD_LEFT: | |
| 1270 model_->MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_LEFT, false); | |
| 1271 break; | |
| 1272 case IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION: | |
| 1273 model_->MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_LEFT, true); | |
| 1274 break; | |
| 1275 case IDS_MOVE_WORD_RIGHT: | |
| 1276 model_->MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT, false); | |
| 1277 break; | |
| 1278 case IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION: | |
| 1279 model_->MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT, true); | |
| 1280 break; | |
| 1281 case IDS_MOVE_TO_BEGINNING_OF_LINE: | |
| 1282 model_->MoveCursor(gfx::LINE_BREAK, begin, false); | |
| 1283 break; | |
| 1284 case IDS_MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION: | |
| 1285 model_->MoveCursor(gfx::LINE_BREAK, begin, true); | |
| 1286 break; | |
| 1287 case IDS_MOVE_TO_END_OF_LINE: | |
| 1288 model_->MoveCursor(gfx::LINE_BREAK, end, false); | |
| 1289 break; | |
| 1290 case IDS_MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION: | |
| 1291 model_->MoveCursor(gfx::LINE_BREAK, end, true); | |
| 1292 break; | |
| 1293 default: | |
| 1294 NOTREACHED(); | |
| 1295 break; | |
| 1296 } | |
| 1297 | |
| 1298 cursor_changed |= GetSelectionModel() != selection_model; | |
| 1299 if (cursor_changed) | |
| 1300 UpdateSelectionClipboard(); | |
| 1301 UpdateAfterChange(text_changed, cursor_changed); | |
| 1302 OnAfterUserAction(); | |
| 1303 } | |
| 1304 | |
| 1305 //////////////////////////////////////////////////////////////////////////////// | |
| 1306 // Textfield, ui::TextInputClient overrides: | |
| 1307 | |
| 1308 void Textfield::SetCompositionText(const ui::CompositionText& composition) { | |
| 1309 if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE) | |
| 1310 return; | |
| 1311 | |
| 1312 OnBeforeUserAction(); | |
| 1313 skip_input_method_cancel_composition_ = true; | |
| 1314 model_->SetCompositionText(composition); | |
| 1315 skip_input_method_cancel_composition_ = false; | |
| 1316 UpdateAfterChange(true, true); | |
| 1317 OnAfterUserAction(); | |
| 1318 } | |
| 1319 | |
| 1320 void Textfield::ConfirmCompositionText() { | |
| 1321 if (!model_->HasCompositionText()) | |
| 1322 return; | |
| 1323 | |
| 1324 OnBeforeUserAction(); | |
| 1325 skip_input_method_cancel_composition_ = true; | |
| 1326 model_->ConfirmCompositionText(); | |
| 1327 skip_input_method_cancel_composition_ = false; | |
| 1328 UpdateAfterChange(true, true); | |
| 1329 OnAfterUserAction(); | |
| 1330 } | |
| 1331 | |
| 1332 void Textfield::ClearCompositionText() { | |
| 1333 if (!model_->HasCompositionText()) | |
| 1334 return; | |
| 1335 | |
| 1336 OnBeforeUserAction(); | |
| 1337 skip_input_method_cancel_composition_ = true; | |
| 1338 model_->CancelCompositionText(); | |
| 1339 skip_input_method_cancel_composition_ = false; | |
| 1340 UpdateAfterChange(true, true); | |
| 1341 OnAfterUserAction(); | |
| 1342 } | |
| 1343 | |
| 1344 void Textfield::InsertText(const base::string16& new_text) { | |
| 1345 // TODO(suzhe): Filter invalid characters. | |
| 1346 if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE || new_text.empty()) | |
| 1347 return; | |
| 1348 | |
| 1349 OnBeforeUserAction(); | |
| 1350 skip_input_method_cancel_composition_ = true; | |
| 1351 if (GetRenderText()->insert_mode()) | |
| 1352 model_->InsertText(new_text); | |
| 1353 else | |
| 1354 model_->ReplaceText(new_text); | |
| 1355 skip_input_method_cancel_composition_ = false; | |
| 1356 UpdateAfterChange(true, true); | |
| 1357 OnAfterUserAction(); | |
| 1358 } | |
| 1359 | |
| 1360 void Textfield::InsertChar(base::char16 ch, int flags) { | |
| 1361 const int kControlModifierMask = ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN | | |
| 1362 ui::EF_COMMAND_DOWN | ui::EF_ALTGR_DOWN | | |
| 1363 ui::EF_MOD3_DOWN; | |
| 1364 | |
| 1365 // Filter out all control characters, including tab and new line characters, | |
| 1366 // and all characters with Alt modifier. But allow characters with the AltGr | |
| 1367 // modifier. On Windows AltGr is represented by Alt+Ctrl, and on Linux it's a | |
| 1368 // different flag that we don't care about. | |
| 1369 const bool should_insert_char = | |
| 1370 ((ch >= 0x20 && ch < 0x7F) || ch > 0x9F) && | |
| 1371 (flags & kControlModifierMask) != ui::EF_ALT_DOWN; | |
| 1372 if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE || !should_insert_char) | |
| 1373 return; | |
| 1374 | |
| 1375 OnBeforeUserAction(); | |
| 1376 skip_input_method_cancel_composition_ = true; | |
| 1377 if (GetRenderText()->insert_mode()) | |
| 1378 model_->InsertChar(ch); | |
| 1379 else | |
| 1380 model_->ReplaceChar(ch); | |
| 1381 skip_input_method_cancel_composition_ = false; | |
| 1382 | |
| 1383 UpdateAfterChange(true, true); | |
| 1384 OnAfterUserAction(); | |
| 1385 | |
| 1386 if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD && | |
| 1387 password_reveal_duration_ != base::TimeDelta()) { | |
| 1388 const size_t change_offset = model_->GetCursorPosition(); | |
| 1389 DCHECK_GT(change_offset, 0u); | |
| 1390 RevealPasswordChar(change_offset - 1); | |
| 1391 } | |
| 1392 } | |
| 1393 | |
| 1394 gfx::NativeWindow Textfield::GetAttachedWindow() const { | |
| 1395 // Imagine the following hierarchy. | |
| 1396 // [NativeWidget A] - FocusManager | |
| 1397 // [View] | |
| 1398 // [NativeWidget B] | |
| 1399 // [View] | |
| 1400 // [View X] | |
| 1401 // An important thing is that [NativeWidget A] owns Win32 input focus even | |
| 1402 // when [View X] is logically focused by FocusManager. As a result, an Win32 | |
| 1403 // IME may want to interact with the native view of [NativeWidget A] rather | |
| 1404 // than that of [NativeWidget B]. This is why we need to call | |
| 1405 // GetTopLevelWidget() here. | |
| 1406 return GetWidget()->GetTopLevelWidget()->GetNativeWindow(); | |
| 1407 } | |
| 1408 | |
| 1409 ui::TextInputType Textfield::GetTextInputType() const { | |
| 1410 if (read_only() || !enabled()) | |
| 1411 return ui::TEXT_INPUT_TYPE_NONE; | |
| 1412 return text_input_type_; | |
| 1413 } | |
| 1414 | |
| 1415 ui::TextInputMode Textfield::GetTextInputMode() const { | |
| 1416 return ui::TEXT_INPUT_MODE_DEFAULT; | |
| 1417 } | |
| 1418 | |
| 1419 bool Textfield::CanComposeInline() const { | |
| 1420 return true; | |
| 1421 } | |
| 1422 | |
| 1423 gfx::Rect Textfield::GetCaretBounds() const { | |
| 1424 gfx::Rect rect = GetRenderText()->GetUpdatedCursorBounds(); | |
| 1425 ConvertRectToScreen(this, &rect); | |
| 1426 return rect; | |
| 1427 } | |
| 1428 | |
| 1429 bool Textfield::GetCompositionCharacterBounds(uint32 index, | |
| 1430 gfx::Rect* rect) const { | |
| 1431 DCHECK(rect); | |
| 1432 if (!HasCompositionText()) | |
| 1433 return false; | |
| 1434 gfx::RenderText* render_text = GetRenderText(); | |
| 1435 const gfx::Range& composition_range = render_text->GetCompositionRange(); | |
| 1436 DCHECK(!composition_range.is_empty()); | |
| 1437 | |
| 1438 size_t text_index = composition_range.start() + index; | |
| 1439 if (composition_range.end() <= text_index) | |
| 1440 return false; | |
| 1441 if (!render_text->IsValidCursorIndex(text_index)) { | |
| 1442 text_index = render_text->IndexOfAdjacentGrapheme( | |
| 1443 text_index, gfx::CURSOR_BACKWARD); | |
| 1444 } | |
| 1445 if (text_index < composition_range.start()) | |
| 1446 return false; | |
| 1447 const gfx::SelectionModel caret(text_index, gfx::CURSOR_BACKWARD); | |
| 1448 *rect = render_text->GetCursorBounds(caret, false); | |
| 1449 ConvertRectToScreen(this, rect); | |
| 1450 return true; | |
| 1451 } | |
| 1452 | |
| 1453 bool Textfield::HasCompositionText() const { | |
| 1454 return model_->HasCompositionText(); | |
| 1455 } | |
| 1456 | |
| 1457 bool Textfield::GetTextRange(gfx::Range* range) const { | |
| 1458 if (!ImeEditingAllowed()) | |
| 1459 return false; | |
| 1460 | |
| 1461 model_->GetTextRange(range); | |
| 1462 return true; | |
| 1463 } | |
| 1464 | |
| 1465 bool Textfield::GetCompositionTextRange(gfx::Range* range) const { | |
| 1466 if (!ImeEditingAllowed()) | |
| 1467 return false; | |
| 1468 | |
| 1469 model_->GetCompositionTextRange(range); | |
| 1470 return true; | |
| 1471 } | |
| 1472 | |
| 1473 bool Textfield::GetSelectionRange(gfx::Range* range) const { | |
| 1474 if (!ImeEditingAllowed()) | |
| 1475 return false; | |
| 1476 *range = GetRenderText()->selection(); | |
| 1477 return true; | |
| 1478 } | |
| 1479 | |
| 1480 bool Textfield::SetSelectionRange(const gfx::Range& range) { | |
| 1481 if (!ImeEditingAllowed() || !range.IsValid()) | |
| 1482 return false; | |
| 1483 OnBeforeUserAction(); | |
| 1484 SelectRange(range); | |
| 1485 OnAfterUserAction(); | |
| 1486 return true; | |
| 1487 } | |
| 1488 | |
| 1489 bool Textfield::DeleteRange(const gfx::Range& range) { | |
| 1490 if (!ImeEditingAllowed() || range.is_empty()) | |
| 1491 return false; | |
| 1492 | |
| 1493 OnBeforeUserAction(); | |
| 1494 model_->SelectRange(range); | |
| 1495 if (model_->HasSelection()) { | |
| 1496 model_->DeleteSelection(); | |
| 1497 UpdateAfterChange(true, true); | |
| 1498 } | |
| 1499 OnAfterUserAction(); | |
| 1500 return true; | |
| 1501 } | |
| 1502 | |
| 1503 bool Textfield::GetTextFromRange(const gfx::Range& range, | |
| 1504 base::string16* range_text) const { | |
| 1505 if (!ImeEditingAllowed() || !range.IsValid()) | |
| 1506 return false; | |
| 1507 | |
| 1508 gfx::Range text_range; | |
| 1509 if (!GetTextRange(&text_range) || !text_range.Contains(range)) | |
| 1510 return false; | |
| 1511 | |
| 1512 *range_text = model_->GetTextFromRange(range); | |
| 1513 return true; | |
| 1514 } | |
| 1515 | |
| 1516 void Textfield::OnInputMethodChanged() {} | |
| 1517 | |
| 1518 bool Textfield::ChangeTextDirectionAndLayoutAlignment( | |
| 1519 base::i18n::TextDirection direction) { | |
| 1520 // Restore text directionality mode when the indicated direction matches the | |
| 1521 // current forced mode; otherwise, force the mode indicated. This helps users | |
| 1522 // manage BiDi text layout without getting stuck in forced LTR or RTL modes. | |
| 1523 const gfx::DirectionalityMode mode = direction == base::i18n::RIGHT_TO_LEFT ? | |
| 1524 gfx::DIRECTIONALITY_FORCE_RTL : gfx::DIRECTIONALITY_FORCE_LTR; | |
| 1525 if (mode == GetRenderText()->directionality_mode()) | |
| 1526 GetRenderText()->SetDirectionalityMode(gfx::DIRECTIONALITY_FROM_TEXT); | |
| 1527 else | |
| 1528 GetRenderText()->SetDirectionalityMode(mode); | |
| 1529 SchedulePaint(); | |
| 1530 return true; | |
| 1531 } | |
| 1532 | |
| 1533 void Textfield::ExtendSelectionAndDelete(size_t before, size_t after) { | |
| 1534 gfx::Range range = GetRenderText()->selection(); | |
| 1535 DCHECK_GE(range.start(), before); | |
| 1536 | |
| 1537 range.set_start(range.start() - before); | |
| 1538 range.set_end(range.end() + after); | |
| 1539 gfx::Range text_range; | |
| 1540 if (GetTextRange(&text_range) && text_range.Contains(range)) | |
| 1541 DeleteRange(range); | |
| 1542 } | |
| 1543 | |
| 1544 void Textfield::EnsureCaretInRect(const gfx::Rect& rect) {} | |
| 1545 | |
| 1546 void Textfield::OnCandidateWindowShown() {} | |
| 1547 | |
| 1548 void Textfield::OnCandidateWindowUpdated() {} | |
| 1549 | |
| 1550 void Textfield::OnCandidateWindowHidden() {} | |
| 1551 | |
| 1552 bool Textfield::IsEditingCommandEnabled(int command_id) { | |
| 1553 return IsCommandIdEnabled(command_id); | |
| 1554 } | |
| 1555 | |
| 1556 void Textfield::ExecuteEditingCommand(int command_id) { | |
| 1557 ExecuteCommand(command_id); | |
| 1558 } | |
| 1559 | |
| 1560 //////////////////////////////////////////////////////////////////////////////// | |
| 1561 // Textfield, protected: | |
| 1562 | |
| 1563 gfx::RenderText* Textfield::GetRenderText() const { | |
| 1564 return model_->render_text(); | |
| 1565 } | |
| 1566 | |
| 1567 base::string16 Textfield::GetSelectionClipboardText() const { | |
| 1568 base::string16 selection_clipboard_text; | |
| 1569 ui::Clipboard::GetForCurrentThread()->ReadText( | |
| 1570 ui::CLIPBOARD_TYPE_SELECTION, &selection_clipboard_text); | |
| 1571 return selection_clipboard_text; | |
| 1572 } | |
| 1573 | |
| 1574 //////////////////////////////////////////////////////////////////////////////// | |
| 1575 // Textfield, private: | |
| 1576 | |
| 1577 void Textfield::AccessibilitySetValue(const base::string16& new_value) { | |
| 1578 if (!read_only()) { | |
| 1579 SetText(new_value); | |
| 1580 ClearSelection(); | |
| 1581 } | |
| 1582 } | |
| 1583 | |
| 1584 void Textfield::UpdateBackgroundColor() { | |
| 1585 const SkColor color = GetBackgroundColor(); | |
| 1586 set_background(Background::CreateSolidBackground(color)); | |
| 1587 GetRenderText()->set_background_is_transparent(SkColorGetA(color) != 0xFF); | |
| 1588 SchedulePaint(); | |
| 1589 } | |
| 1590 | |
| 1591 void Textfield::UpdateAfterChange(bool text_changed, bool cursor_changed) { | |
| 1592 if (text_changed) { | |
| 1593 if (controller_) | |
| 1594 controller_->ContentsChanged(this, text()); | |
| 1595 NotifyAccessibilityEvent(ui::AX_EVENT_TEXT_CHANGED, true); | |
| 1596 } | |
| 1597 if (cursor_changed) { | |
| 1598 cursor_visible_ = true; | |
| 1599 RepaintCursor(); | |
| 1600 if (cursor_repaint_timer_.IsRunning()) | |
| 1601 cursor_repaint_timer_.Reset(); | |
| 1602 if (!text_changed) { | |
| 1603 // TEXT_CHANGED implies TEXT_SELECTION_CHANGED, so we only need to fire | |
| 1604 // this if only the selection changed. | |
| 1605 NotifyAccessibilityEvent(ui::AX_EVENT_TEXT_SELECTION_CHANGED, true); | |
| 1606 } | |
| 1607 } | |
| 1608 if (text_changed || cursor_changed) { | |
| 1609 OnCaretBoundsChanged(); | |
| 1610 SchedulePaint(); | |
| 1611 } | |
| 1612 } | |
| 1613 | |
| 1614 void Textfield::UpdateCursor() { | |
| 1615 const size_t caret_blink_ms = Textfield::GetCaretBlinkMs(); | |
| 1616 cursor_visible_ = !cursor_visible_ || (caret_blink_ms == 0); | |
| 1617 RepaintCursor(); | |
| 1618 } | |
| 1619 | |
| 1620 void Textfield::RepaintCursor() { | |
| 1621 gfx::Rect r(GetRenderText()->GetUpdatedCursorBounds()); | |
| 1622 r.Inset(-1, -1, -1, -1); | |
| 1623 SchedulePaintInRect(r); | |
| 1624 } | |
| 1625 | |
| 1626 void Textfield::PaintTextAndCursor(gfx::Canvas* canvas) { | |
| 1627 TRACE_EVENT0("views", "Textfield::PaintTextAndCursor"); | |
| 1628 canvas->Save(); | |
| 1629 | |
| 1630 // Draw placeholder text if needed. | |
| 1631 gfx::RenderText* render_text = GetRenderText(); | |
| 1632 if (text().empty() && !GetPlaceholderText().empty()) { | |
| 1633 canvas->DrawStringRect(GetPlaceholderText(), GetFontList(), | |
| 1634 placeholder_text_color(), render_text->display_rect()); | |
| 1635 } | |
| 1636 | |
| 1637 // Draw the text, cursor, and selection. | |
| 1638 render_text->set_cursor_visible(cursor_visible_ && !drop_cursor_visible_ && | |
| 1639 !HasSelection()); | |
| 1640 render_text->Draw(canvas); | |
| 1641 | |
| 1642 // Draw the detached drop cursor that marks where the text will be dropped. | |
| 1643 if (drop_cursor_visible_) | |
| 1644 render_text->DrawCursor(canvas, drop_cursor_position_); | |
| 1645 | |
| 1646 canvas->Restore(); | |
| 1647 } | |
| 1648 | |
| 1649 void Textfield::MoveCursorTo(const gfx::Point& point, bool select) { | |
| 1650 if (model_->MoveCursorTo(point, select)) | |
| 1651 UpdateAfterChange(false, true); | |
| 1652 } | |
| 1653 | |
| 1654 void Textfield::SelectThroughLastDragLocation() { | |
| 1655 OnBeforeUserAction(); | |
| 1656 model_->MoveCursorTo(last_drag_location_, true); | |
| 1657 if (aggregated_clicks_ == 1) { | |
| 1658 model_->SelectWord(); | |
| 1659 // Expand the selection so the initially selected word remains selected. | |
| 1660 gfx::Range selection = GetRenderText()->selection(); | |
| 1661 const size_t min = std::min(selection.GetMin(), | |
| 1662 double_click_word_.GetMin()); | |
| 1663 const size_t max = std::max(selection.GetMax(), | |
| 1664 double_click_word_.GetMax()); | |
| 1665 const bool reversed = selection.is_reversed(); | |
| 1666 selection.set_start(reversed ? max : min); | |
| 1667 selection.set_end(reversed ? min : max); | |
| 1668 model_->SelectRange(selection); | |
| 1669 } | |
| 1670 UpdateAfterChange(false, true); | |
| 1671 OnAfterUserAction(); | |
| 1672 } | |
| 1673 | |
| 1674 void Textfield::OnCaretBoundsChanged() { | |
| 1675 if (GetInputMethod()) | |
| 1676 GetInputMethod()->OnCaretBoundsChanged(this); | |
| 1677 if (touch_selection_controller_) | |
| 1678 touch_selection_controller_->SelectionChanged(); | |
| 1679 } | |
| 1680 | |
| 1681 void Textfield::OnBeforeUserAction() { | |
| 1682 DCHECK(!performing_user_action_); | |
| 1683 performing_user_action_ = true; | |
| 1684 if (controller_) | |
| 1685 controller_->OnBeforeUserAction(this); | |
| 1686 } | |
| 1687 | |
| 1688 void Textfield::OnAfterUserAction() { | |
| 1689 if (controller_) | |
| 1690 controller_->OnAfterUserAction(this); | |
| 1691 DCHECK(performing_user_action_); | |
| 1692 performing_user_action_ = false; | |
| 1693 } | |
| 1694 | |
| 1695 bool Textfield::Cut() { | |
| 1696 if (!read_only() && text_input_type_ != ui::TEXT_INPUT_TYPE_PASSWORD && | |
| 1697 model_->Cut()) { | |
| 1698 if (controller_) | |
| 1699 controller_->OnAfterCutOrCopy(ui::CLIPBOARD_TYPE_COPY_PASTE); | |
| 1700 return true; | |
| 1701 } | |
| 1702 return false; | |
| 1703 } | |
| 1704 | |
| 1705 bool Textfield::Copy() { | |
| 1706 if (text_input_type_ != ui::TEXT_INPUT_TYPE_PASSWORD && model_->Copy()) { | |
| 1707 if (controller_) | |
| 1708 controller_->OnAfterCutOrCopy(ui::CLIPBOARD_TYPE_COPY_PASTE); | |
| 1709 return true; | |
| 1710 } | |
| 1711 return false; | |
| 1712 } | |
| 1713 | |
| 1714 bool Textfield::Paste() { | |
| 1715 if (!read_only() && model_->Paste()) { | |
| 1716 if (controller_) | |
| 1717 controller_->OnAfterPaste(); | |
| 1718 return true; | |
| 1719 } | |
| 1720 return false; | |
| 1721 } | |
| 1722 | |
| 1723 void Textfield::UpdateContextMenu() { | |
| 1724 if (!context_menu_contents_.get()) { | |
| 1725 context_menu_contents_.reset(new ui::SimpleMenuModel(this)); | |
| 1726 context_menu_contents_->AddItemWithStringId(IDS_APP_UNDO, IDS_APP_UNDO); | |
| 1727 context_menu_contents_->AddSeparator(ui::NORMAL_SEPARATOR); | |
| 1728 context_menu_contents_->AddItemWithStringId(IDS_APP_CUT, IDS_APP_CUT); | |
| 1729 context_menu_contents_->AddItemWithStringId(IDS_APP_COPY, IDS_APP_COPY); | |
| 1730 context_menu_contents_->AddItemWithStringId(IDS_APP_PASTE, IDS_APP_PASTE); | |
| 1731 context_menu_contents_->AddItemWithStringId(IDS_APP_DELETE, IDS_APP_DELETE); | |
| 1732 context_menu_contents_->AddSeparator(ui::NORMAL_SEPARATOR); | |
| 1733 context_menu_contents_->AddItemWithStringId(IDS_APP_SELECT_ALL, | |
| 1734 IDS_APP_SELECT_ALL); | |
| 1735 if (controller_) | |
| 1736 controller_->UpdateContextMenu(context_menu_contents_.get()); | |
| 1737 } | |
| 1738 context_menu_runner_.reset( | |
| 1739 new MenuRunner(context_menu_contents_.get(), | |
| 1740 MenuRunner::HAS_MNEMONICS | MenuRunner::CONTEXT_MENU)); | |
| 1741 } | |
| 1742 | |
| 1743 void Textfield::TrackMouseClicks(const ui::MouseEvent& event) { | |
| 1744 if (event.IsOnlyLeftMouseButton()) { | |
| 1745 base::TimeDelta time_delta = event.time_stamp() - last_click_time_; | |
| 1746 if (time_delta.InMilliseconds() <= GetDoubleClickInterval() && | |
| 1747 !ExceededDragThreshold(event.location() - last_click_location_)) { | |
| 1748 // Upon clicking after a triple click, the count should go back to double | |
| 1749 // click and alternate between double and triple. This assignment maps | |
| 1750 // 0 to 1, 1 to 2, 2 to 1. | |
| 1751 aggregated_clicks_ = (aggregated_clicks_ % 2) + 1; | |
| 1752 } else { | |
| 1753 aggregated_clicks_ = 0; | |
| 1754 } | |
| 1755 last_click_time_ = event.time_stamp(); | |
| 1756 last_click_location_ = event.location(); | |
| 1757 } | |
| 1758 } | |
| 1759 | |
| 1760 bool Textfield::ImeEditingAllowed() const { | |
| 1761 // Disallow input method editing of password fields. | |
| 1762 ui::TextInputType t = GetTextInputType(); | |
| 1763 return (t != ui::TEXT_INPUT_TYPE_NONE && t != ui::TEXT_INPUT_TYPE_PASSWORD); | |
| 1764 } | |
| 1765 | |
| 1766 void Textfield::RevealPasswordChar(int index) { | |
| 1767 GetRenderText()->SetObscuredRevealIndex(index); | |
| 1768 SchedulePaint(); | |
| 1769 | |
| 1770 if (index != -1) { | |
| 1771 password_reveal_timer_.Start(FROM_HERE, password_reveal_duration_, | |
| 1772 base::Bind(&Textfield::RevealPasswordChar, | |
| 1773 weak_ptr_factory_.GetWeakPtr(), -1)); | |
| 1774 } | |
| 1775 } | |
| 1776 | |
| 1777 void Textfield::CreateTouchSelectionControllerAndNotifyIt() { | |
| 1778 if (!HasFocus()) | |
| 1779 return; | |
| 1780 | |
| 1781 if (!touch_selection_controller_) { | |
| 1782 touch_selection_controller_.reset( | |
| 1783 ui::TouchSelectionController::create(this)); | |
| 1784 } | |
| 1785 if (touch_selection_controller_) | |
| 1786 touch_selection_controller_->SelectionChanged(); | |
| 1787 } | |
| 1788 | |
| 1789 void Textfield::UpdateSelectionClipboard() const { | |
| 1790 #if defined(OS_LINUX) && !defined(OS_CHROMEOS) | |
| 1791 if (performing_user_action_ && HasSelection()) { | |
| 1792 ui::ScopedClipboardWriter( | |
| 1793 ui::CLIPBOARD_TYPE_SELECTION).WriteText(GetSelectedText()); | |
| 1794 if (controller_) | |
| 1795 controller_->OnAfterCutOrCopy(ui::CLIPBOARD_TYPE_SELECTION); | |
| 1796 } | |
| 1797 #endif | |
| 1798 } | |
| 1799 | |
| 1800 void Textfield::PasteSelectionClipboard(const ui::MouseEvent& event) { | |
| 1801 DCHECK(event.IsOnlyMiddleMouseButton()); | |
| 1802 DCHECK(!read_only()); | |
| 1803 base::string16 selection_clipboard_text = GetSelectionClipboardText(); | |
| 1804 if (!selection_clipboard_text.empty()) { | |
| 1805 OnBeforeUserAction(); | |
| 1806 gfx::Range range = GetSelectionModel().selection(); | |
| 1807 gfx::LogicalCursorDirection affinity = GetSelectionModel().caret_affinity(); | |
| 1808 const gfx::SelectionModel mouse = | |
| 1809 GetRenderText()->FindCursorPosition(event.location()); | |
| 1810 model_->MoveCursorTo(mouse); | |
| 1811 model_->InsertText(selection_clipboard_text); | |
| 1812 // Update the new selection range as needed. | |
| 1813 if (range.GetMin() >= mouse.caret_pos()) { | |
| 1814 const size_t length = selection_clipboard_text.length(); | |
| 1815 range = gfx::Range(range.start() + length, range.end() + length); | |
| 1816 } | |
| 1817 model_->MoveCursorTo(gfx::SelectionModel(range, affinity)); | |
| 1818 UpdateAfterChange(true, true); | |
| 1819 OnAfterUserAction(); | |
| 1820 } | |
| 1821 } | |
| 1822 | |
| 1823 } // namespace views | |
| OLD | NEW |