Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 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_model.h" | 5 #include "ui/views/controls/textfield/textfield_model.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 | 8 |
| 9 #include "base/logging.h" | 9 #include "base/logging.h" |
| 10 #include "base/macros.h" | 10 #include "base/macros.h" |
| 11 #include "base/message_loop/message_loop.h" | |
| 11 #include "base/stl_util.h" | 12 #include "base/stl_util.h" |
| 12 #include "base/strings/string_util.h" | 13 #include "base/strings/string_util.h" |
| 13 #include "base/strings/utf_string_conversions.h" | 14 #include "base/strings/utf_string_conversions.h" |
| 14 #include "ui/base/clipboard/clipboard.h" | 15 #include "ui/base/clipboard/clipboard.h" |
| 15 #include "ui/base/clipboard/scoped_clipboard_writer.h" | 16 #include "ui/base/clipboard/scoped_clipboard_writer.h" |
| 16 #include "ui/gfx/range/range.h" | 17 #include "ui/gfx/range/range.h" |
| 17 #include "ui/gfx/utf16_indexing.h" | 18 #include "ui/gfx/utf16_indexing.h" |
| 18 | 19 |
| 19 namespace views { | 20 namespace views { |
| 20 | 21 |
| (...skipping 235 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 256 // there is no such a range. | 257 // there is no such a range. |
| 257 gfx::Range GetFirstEmphasizedRange(const ui::CompositionText& composition) { | 258 gfx::Range GetFirstEmphasizedRange(const ui::CompositionText& composition) { |
| 258 for (size_t i = 0; i < composition.underlines.size(); ++i) { | 259 for (size_t i = 0; i < composition.underlines.size(); ++i) { |
| 259 const ui::CompositionUnderline& underline = composition.underlines[i]; | 260 const ui::CompositionUnderline& underline = composition.underlines[i]; |
| 260 if (underline.thick) | 261 if (underline.thick) |
| 261 return gfx::Range(underline.start_offset, underline.end_offset); | 262 return gfx::Range(underline.start_offset, underline.end_offset); |
| 262 } | 263 } |
| 263 return gfx::Range::InvalidRange(); | 264 return gfx::Range::InvalidRange(); |
| 264 } | 265 } |
| 265 | 266 |
| 267 // Returns a reference to the kill buffer which holds the text to be inserted on | |
| 268 // executing yank command. Singleton since it needs to be persisted across | |
| 269 // multiple textfields. | |
| 270 // On Mac, the size of the kill ring (no. of buffers) is controlled by | |
| 271 // NSTextKillRingSize, a text system default. However to keep things simple, | |
| 272 // the default kill ring size of 1 (i.e. a single buffer) is assumed. | |
| 273 base::string16& GetKillBuffer() { | |
|
sky
2016/07/22 15:22:49
return a pointer (references are generally restric
karandeepb
2016/07/25 06:38:11
Done.
| |
| 274 CR_DEFINE_STATIC_LOCAL(base::string16, kill_buffer, ()); | |
| 275 DCHECK(base::MessageLoopForUI::IsCurrent()); | |
| 276 return kill_buffer; | |
| 277 } | |
| 278 | |
| 279 // Helper method to set the kill buffer. | |
| 280 void SetKillBuffer(const base::string16& buffer) { | |
| 281 base::string16& kill_buffer = GetKillBuffer(); | |
| 282 kill_buffer = buffer; | |
| 283 } | |
| 284 | |
| 266 } // namespace | 285 } // namespace |
| 267 | 286 |
| 268 using internal::Edit; | 287 using internal::Edit; |
| 269 using internal::DeleteEdit; | 288 using internal::DeleteEdit; |
| 270 using internal::InsertEdit; | 289 using internal::InsertEdit; |
| 271 using internal::ReplaceEdit; | 290 using internal::ReplaceEdit; |
| 272 using internal::MergeType; | 291 using internal::MergeType; |
| 273 using internal::DO_NOT_MERGE; | 292 using internal::DO_NOT_MERGE; |
| 274 using internal::FORCE_MERGE; | 293 using internal::FORCE_MERGE; |
| 275 using internal::MERGEABLE; | 294 using internal::MERGEABLE; |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 318 ConfirmCompositionText(); | 337 ConfirmCompositionText(); |
| 319 size_t save = GetCursorPosition(); | 338 size_t save = GetCursorPosition(); |
| 320 MoveCursor(gfx::LINE_BREAK, | 339 MoveCursor(gfx::LINE_BREAK, |
| 321 render_text_->GetVisualDirectionOfLogicalEnd(), | 340 render_text_->GetVisualDirectionOfLogicalEnd(), |
| 322 false); | 341 false); |
| 323 InsertText(new_text); | 342 InsertText(new_text); |
| 324 render_text_->SetCursorPosition(save); | 343 render_text_->SetCursorPosition(save); |
| 325 ClearSelection(); | 344 ClearSelection(); |
| 326 } | 345 } |
| 327 | 346 |
| 328 bool TextfieldModel::Delete() { | 347 bool TextfieldModel::Delete(bool add_to_kill_buffer) { |
| 329 if (HasCompositionText()) { | 348 if (HasCompositionText()) { |
| 330 // No undo/redo for composition text. | 349 // No undo/redo for composition text. |
| 331 CancelCompositionText(); | 350 CancelCompositionText(); |
| 332 return true; | 351 return true; |
| 333 } | 352 } |
| 334 if (HasSelection()) { | 353 if (HasSelection()) { |
| 354 if (add_to_kill_buffer) | |
| 355 SetKillBuffer(GetSelectedText()); | |
| 335 DeleteSelection(); | 356 DeleteSelection(); |
| 336 return true; | 357 return true; |
| 337 } | 358 } |
| 338 if (text().length() > GetCursorPosition()) { | 359 if (text().length() > GetCursorPosition()) { |
| 339 size_t cursor_position = GetCursorPosition(); | 360 size_t cursor_position = GetCursorPosition(); |
| 340 size_t next_grapheme_index = render_text_->IndexOfAdjacentGrapheme( | 361 size_t next_grapheme_index = render_text_->IndexOfAdjacentGrapheme( |
| 341 cursor_position, gfx::CURSOR_FORWARD); | 362 cursor_position, gfx::CURSOR_FORWARD); |
| 342 ExecuteAndRecordDelete(gfx::Range(cursor_position, next_grapheme_index), | 363 gfx::Range range_to_delete(cursor_position, next_grapheme_index); |
| 343 true); | 364 if (add_to_kill_buffer) |
| 365 SetKillBuffer(GetTextFromRange(range_to_delete)); | |
| 366 ExecuteAndRecordDelete(range_to_delete, true); | |
| 344 return true; | 367 return true; |
| 345 } | 368 } |
| 346 return false; | 369 return false; |
| 347 } | 370 } |
| 348 | 371 |
| 349 bool TextfieldModel::Backspace() { | 372 bool TextfieldModel::Backspace(bool add_to_kill_buffer) { |
| 350 if (HasCompositionText()) { | 373 if (HasCompositionText()) { |
| 351 // No undo/redo for composition text. | 374 // No undo/redo for composition text. |
| 352 CancelCompositionText(); | 375 CancelCompositionText(); |
| 353 return true; | 376 return true; |
| 354 } | 377 } |
| 355 if (HasSelection()) { | 378 if (HasSelection()) { |
| 379 if (add_to_kill_buffer) | |
| 380 SetKillBuffer(GetSelectedText()); | |
| 356 DeleteSelection(); | 381 DeleteSelection(); |
| 357 return true; | 382 return true; |
| 358 } | 383 } |
| 359 size_t cursor_position = GetCursorPosition(); | 384 size_t cursor_position = GetCursorPosition(); |
| 360 if (cursor_position > 0) { | 385 if (cursor_position > 0) { |
| 361 // Delete one code point, which may be two UTF-16 words. | 386 // Delete one code point, which may be two UTF-16 words. |
| 362 size_t previous_char = gfx::UTF16OffsetToIndex(text(), cursor_position, -1); | 387 size_t previous_char = gfx::UTF16OffsetToIndex(text(), cursor_position, -1); |
|
msw
2016/07/22 17:44:03
optional nit: |previous_grapheme_index| for consis
karandeepb
2016/07/25 06:38:11
Done.
| |
| 363 ExecuteAndRecordDelete(gfx::Range(cursor_position, previous_char), true); | 388 gfx::Range range_to_delete(cursor_position, previous_char); |
| 389 if (add_to_kill_buffer) | |
| 390 SetKillBuffer(GetTextFromRange(range_to_delete)); | |
| 391 ExecuteAndRecordDelete(range_to_delete, true); | |
| 364 return true; | 392 return true; |
| 365 } | 393 } |
| 366 return false; | 394 return false; |
| 367 } | 395 } |
| 368 | 396 |
| 369 size_t TextfieldModel::GetCursorPosition() const { | 397 size_t TextfieldModel::GetCursorPosition() const { |
| 370 return render_text_->cursor_position(); | 398 return render_text_->cursor_position(); |
| 371 } | 399 } |
| 372 | 400 |
| 373 void TextfieldModel::MoveCursor(gfx::BreakType break_type, | 401 void TextfieldModel::MoveCursor(gfx::BreakType break_type, |
| (...skipping 176 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 550 | 578 |
| 551 SelectRange(gfx::Range(prev, next)); | 579 SelectRange(gfx::Range(prev, next)); |
| 552 base::string16 text = GetSelectedText(); | 580 base::string16 text = GetSelectedText(); |
| 553 base::string16 transposed_text = | 581 base::string16 transposed_text = |
| 554 text.substr(cur - prev) + text.substr(0, cur - prev); | 582 text.substr(cur - prev) + text.substr(0, cur - prev); |
| 555 | 583 |
| 556 InsertTextInternal(transposed_text, false); | 584 InsertTextInternal(transposed_text, false); |
| 557 return true; | 585 return true; |
| 558 } | 586 } |
| 559 | 587 |
| 588 bool TextfieldModel::Yank() { | |
| 589 const base::string16& kill_buffer = GetKillBuffer(); | |
| 590 if (!kill_buffer.empty() || HasSelection()) { | |
|
msw
2016/07/22 17:44:03
Why would this try to insert an empty string if th
karandeepb
2016/07/25 06:38:11
That's how Yank works on Mac/Cocoa. Open a new Chr
msw
2016/07/25 18:35:49
Ah, so it's just doing a delete in that case? Okay
karandeepb
2016/07/26 03:45:41
Yeah!
| |
| 591 InsertTextInternal(kill_buffer, false); | |
| 592 return true; | |
| 593 } | |
| 594 return false; | |
| 595 } | |
| 596 | |
| 560 bool TextfieldModel::HasSelection() const { | 597 bool TextfieldModel::HasSelection() const { |
| 561 return !render_text_->selection().is_empty(); | 598 return !render_text_->selection().is_empty(); |
| 562 } | 599 } |
| 563 | 600 |
| 564 void TextfieldModel::DeleteSelection() { | 601 void TextfieldModel::DeleteSelection() { |
| 565 DCHECK(!HasCompositionText()); | 602 DCHECK(!HasCompositionText()); |
| 566 DCHECK(HasSelection()); | 603 DCHECK(HasSelection()); |
| 567 ExecuteAndRecordDelete(render_text_->selection(), false); | 604 ExecuteAndRecordDelete(render_text_->selection(), false); |
| 568 } | 605 } |
| 569 | 606 |
| (...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 665 | 702 |
| 666 bool TextfieldModel::HasCompositionText() const { | 703 bool TextfieldModel::HasCompositionText() const { |
| 667 return !composition_range_.is_empty(); | 704 return !composition_range_.is_empty(); |
| 668 } | 705 } |
| 669 | 706 |
| 670 void TextfieldModel::ClearEditHistory() { | 707 void TextfieldModel::ClearEditHistory() { |
| 671 STLDeleteElements(&edit_history_); | 708 STLDeleteElements(&edit_history_); |
| 672 current_edit_ = edit_history_.end(); | 709 current_edit_ = edit_history_.end(); |
| 673 } | 710 } |
| 674 | 711 |
| 712 // static | |
| 713 void TextfieldModel::ClearKillBufferForTesting() { | |
| 714 SetKillBuffer(base::string16()); | |
| 715 } | |
| 716 | |
| 675 ///////////////////////////////////////////////////////////////// | 717 ///////////////////////////////////////////////////////////////// |
| 676 // TextfieldModel: private | 718 // TextfieldModel: private |
| 677 | 719 |
| 678 void TextfieldModel::InsertTextInternal(const base::string16& new_text, | 720 void TextfieldModel::InsertTextInternal(const base::string16& new_text, |
| 679 bool mergeable) { | 721 bool mergeable) { |
| 680 if (HasCompositionText()) { | 722 if (HasCompositionText()) { |
| 681 CancelCompositionText(); | 723 CancelCompositionText(); |
| 682 ExecuteAndRecordInsert(new_text, mergeable); | 724 ExecuteAndRecordInsert(new_text, mergeable); |
| 683 } else if (HasSelection()) { | 725 } else if (HasSelection()) { |
| 684 ExecuteAndRecordReplaceSelection(mergeable ? MERGEABLE : DO_NOT_MERGE, | 726 ExecuteAndRecordReplaceSelection(mergeable ? MERGEABLE : DO_NOT_MERGE, |
| (...skipping 119 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 804 ClearComposition(); | 846 ClearComposition(); |
| 805 if (delete_from != delete_to) | 847 if (delete_from != delete_to) |
| 806 render_text_->SetText(old_text.erase(delete_from, delete_to - delete_from)); | 848 render_text_->SetText(old_text.erase(delete_from, delete_to - delete_from)); |
| 807 if (!new_text.empty()) | 849 if (!new_text.empty()) |
| 808 render_text_->SetText(old_text.insert(new_text_insert_at, new_text)); | 850 render_text_->SetText(old_text.insert(new_text_insert_at, new_text)); |
| 809 render_text_->SetCursorPosition(new_cursor_pos); | 851 render_text_->SetCursorPosition(new_cursor_pos); |
| 810 // TODO(oshima): Select text that was just undone, like Mac (but not GTK). | 852 // TODO(oshima): Select text that was just undone, like Mac (but not GTK). |
| 811 } | 853 } |
| 812 | 854 |
| 813 } // namespace views | 855 } // namespace views |
| OLD | NEW |