| OLD | NEW |
| 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/gfx/render_text.h" | 5 #include "ui/gfx/render_text.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 #include <climits> | 8 #include <climits> |
| 9 | 9 |
| 10 #include "base/i18n/break_iterator.h" | 10 #include "base/i18n/break_iterator.h" |
| (...skipping 483 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 494 } | 494 } |
| 495 } | 495 } |
| 496 | 496 |
| 497 void RenderText::SetCursorPosition(size_t position) { | 497 void RenderText::SetCursorPosition(size_t position) { |
| 498 MoveCursorTo(position, false); | 498 MoveCursorTo(position, false); |
| 499 } | 499 } |
| 500 | 500 |
| 501 void RenderText::MoveCursor(BreakType break_type, | 501 void RenderText::MoveCursor(BreakType break_type, |
| 502 VisualCursorDirection direction, | 502 VisualCursorDirection direction, |
| 503 bool select) { | 503 bool select) { |
| 504 SelectionModel cursor(cursor_position(), selection_model_.caret_affinity()); | 504 SelectionModel position(cursor_position(), selection_model_.caret_affinity()); |
| 505 // Cancelling a selection moves to the edge of the selection. | 505 // Cancelling a selection moves to the edge of the selection. |
| 506 if (break_type != LINE_BREAK && !selection().is_empty() && !select) { | 506 if (break_type != LINE_BREAK && !selection().is_empty() && !select) { |
| 507 SelectionModel selection_start = GetSelectionModelForSelectionStart(); | 507 SelectionModel selection_start = GetSelectionModelForSelectionStart(); |
| 508 int start_x = GetCursorBounds(selection_start, true).x(); | 508 int start_x = GetCursorBounds(selection_start, true).x(); |
| 509 int cursor_x = GetCursorBounds(cursor, true).x(); | 509 int cursor_x = GetCursorBounds(position, true).x(); |
| 510 // Use the selection start if it is left (when |direction| is CURSOR_LEFT) | 510 // Use the selection start if it is left (when |direction| is CURSOR_LEFT) |
| 511 // or right (when |direction| is CURSOR_RIGHT) of the selection end. | 511 // or right (when |direction| is CURSOR_RIGHT) of the selection end. |
| 512 if (direction == CURSOR_RIGHT ? start_x > cursor_x : start_x < cursor_x) | 512 if (direction == CURSOR_RIGHT ? start_x > cursor_x : start_x < cursor_x) |
| 513 cursor = selection_start; | 513 position = selection_start; |
| 514 // Use the nearest word boundary in the proper |direction| for word breaks. | 514 // For word breaks, use the nearest word boundary in the appropriate |
| 515 // |direction|. |
| 515 if (break_type == WORD_BREAK) | 516 if (break_type == WORD_BREAK) |
| 516 cursor = GetAdjacentSelectionModel(cursor, break_type, direction); | 517 position = GetAdjacentSelectionModel(position, break_type, direction); |
| 517 // Use an adjacent selection model if the cursor is not at a valid position. | |
| 518 if (!IsValidCursorIndex(cursor.caret_pos())) | |
| 519 cursor = GetAdjacentSelectionModel(cursor, CHARACTER_BREAK, direction); | |
| 520 } else { | 518 } else { |
| 521 cursor = GetAdjacentSelectionModel(cursor, break_type, direction); | 519 position = GetAdjacentSelectionModel(position, break_type, direction); |
| 522 } | 520 } |
| 523 if (select) | 521 if (select) |
| 524 cursor.set_selection_start(selection().start()); | 522 position.set_selection_start(selection().start()); |
| 525 MoveCursorTo(cursor); | 523 MoveCursorTo(position); |
| 526 } | 524 } |
| 527 | 525 |
| 528 bool RenderText::MoveCursorTo(const SelectionModel& model) { | 526 bool RenderText::MoveCursorTo(const SelectionModel& model) { |
| 529 // Enforce valid selection model components. | 527 // Enforce valid selection model components. |
| 530 size_t text_length = text().length(); | 528 size_t text_length = text().length(); |
| 531 Range range(std::min(model.selection().start(), text_length), | 529 Range range(std::min(model.selection().start(), text_length), |
| 532 std::min(model.caret_pos(), text_length)); | 530 std::min(model.caret_pos(), text_length)); |
| 533 // The current model only supports caret positions at valid cursor indices. | 531 // The current model only supports caret positions at valid character indices. |
| 534 if (!IsValidCursorIndex(range.start()) || !IsValidCursorIndex(range.end())) | 532 if (!IsCursorablePosition(range.start()) || |
| 533 !IsCursorablePosition(range.end())) |
| 535 return false; | 534 return false; |
| 536 SelectionModel sel(range, model.caret_affinity()); | 535 SelectionModel sel(range, model.caret_affinity()); |
| 537 bool changed = sel != selection_model_; | 536 bool changed = sel != selection_model_; |
| 538 SetSelectionModel(sel); | 537 SetSelectionModel(sel); |
| 539 return changed; | 538 return changed; |
| 540 } | 539 } |
| 541 | 540 |
| 542 bool RenderText::MoveCursorTo(const Point& point, bool select) { | 541 bool RenderText::MoveCursorTo(const Point& point, bool select) { |
| 543 SelectionModel position = FindCursorPosition(point); | 542 SelectionModel position = FindCursorPosition(point); |
| 544 if (select) | 543 if (select) |
| 545 position.set_selection_start(selection().start()); | 544 position.set_selection_start(selection().start()); |
| 546 return MoveCursorTo(position); | 545 return MoveCursorTo(position); |
| 547 } | 546 } |
| 548 | 547 |
| 549 bool RenderText::SelectRange(const Range& range) { | 548 bool RenderText::SelectRange(const Range& range) { |
| 550 Range sel(std::min(range.start(), text().length()), | 549 Range sel(std::min(range.start(), text().length()), |
| 551 std::min(range.end(), text().length())); | 550 std::min(range.end(), text().length())); |
| 552 // Allow selection bounds at valid indicies amid multi-character graphemes. | 551 if (!IsCursorablePosition(sel.start()) || !IsCursorablePosition(sel.end())) |
| 553 if (!IsValidLogicalIndex(sel.start()) || !IsValidLogicalIndex(sel.end())) | |
| 554 return false; | 552 return false; |
| 555 LogicalCursorDirection affinity = | 553 LogicalCursorDirection affinity = |
| 556 (sel.is_reversed() || sel.is_empty()) ? CURSOR_FORWARD : CURSOR_BACKWARD; | 554 (sel.is_reversed() || sel.is_empty()) ? CURSOR_FORWARD : CURSOR_BACKWARD; |
| 557 SetSelectionModel(SelectionModel(sel, affinity)); | 555 SetSelectionModel(SelectionModel(sel, affinity)); |
| 558 return true; | 556 return true; |
| 559 } | 557 } |
| 560 | 558 |
| 561 bool RenderText::IsPointInSelection(const Point& point) { | 559 bool RenderText::IsPointInSelection(const Point& point) { |
| 562 if (selection().is_empty()) | 560 if (selection().is_empty()) |
| 563 return false; | 561 return false; |
| (...skipping 198 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 762 if (clip_to_display_rect()) | 760 if (clip_to_display_rect()) |
| 763 canvas->Restore(); | 761 canvas->Restore(); |
| 764 } | 762 } |
| 765 | 763 |
| 766 void RenderText::DrawCursor(Canvas* canvas, const SelectionModel& position) { | 764 void RenderText::DrawCursor(Canvas* canvas, const SelectionModel& position) { |
| 767 // Paint cursor. Replace cursor is drawn as rectangle for now. | 765 // Paint cursor. Replace cursor is drawn as rectangle for now. |
| 768 // TODO(msw): Draw a better cursor with a better indication of association. | 766 // TODO(msw): Draw a better cursor with a better indication of association. |
| 769 canvas->FillRect(GetCursorBounds(position, true), cursor_color_); | 767 canvas->FillRect(GetCursorBounds(position, true), cursor_color_); |
| 770 } | 768 } |
| 771 | 769 |
| 772 bool RenderText::IsValidLogicalIndex(size_t index) { | |
| 773 // Check that the index is at a valid code point (not mid-surrgate-pair) and | |
| 774 // that it's not truncated from the layout text (its glyph may be shown). | |
| 775 // | |
| 776 // Indices within truncated text are disallowed so users can easily interact | |
| 777 // with the underlying truncated text using the ellipsis as a proxy. This lets | |
| 778 // users select all text, select the truncated text, and transition from the | |
| 779 // last rendered glyph to the end of the text without getting invisible cursor | |
| 780 // positions nor needing unbounded arrow key presses to traverse the ellipsis. | |
| 781 return index == 0 || index == text().length() || | |
| 782 (index < text().length() && | |
| 783 (truncate_length_ == 0 || index < truncate_length_) && | |
| 784 IsValidCodePointIndex(text(), index)); | |
| 785 } | |
| 786 | |
| 787 Rect RenderText::GetCursorBounds(const SelectionModel& caret, | 770 Rect RenderText::GetCursorBounds(const SelectionModel& caret, |
| 788 bool insert_mode) { | 771 bool insert_mode) { |
| 789 // TODO(ckocagil): Support multiline. This function should return the height | 772 // TODO(ckocagil): Support multiline. This function should return the height |
| 790 // of the line the cursor is on. |GetStringSize()| now returns | 773 // of the line the cursor is on. |GetStringSize()| now returns |
| 791 // the multiline size, eliminate its use here. | 774 // the multiline size, eliminate its use here. |
| 792 | 775 |
| 793 EnsureLayout(); | 776 EnsureLayout(); |
| 777 |
| 794 size_t caret_pos = caret.caret_pos(); | 778 size_t caret_pos = caret.caret_pos(); |
| 795 DCHECK(IsValidLogicalIndex(caret_pos)); | 779 DCHECK(IsCursorablePosition(caret_pos)); |
| 796 // In overtype mode, ignore the affinity and always indicate that we will | 780 // In overtype mode, ignore the affinity and always indicate that we will |
| 797 // overtype the next character. | 781 // overtype the next character. |
| 798 LogicalCursorDirection caret_affinity = | 782 LogicalCursorDirection caret_affinity = |
| 799 insert_mode ? caret.caret_affinity() : CURSOR_FORWARD; | 783 insert_mode ? caret.caret_affinity() : CURSOR_FORWARD; |
| 800 int x = 0, width = 1; | 784 int x = 0, width = 1; |
| 801 Size size = GetStringSize(); | 785 Size size = GetStringSize(); |
| 802 if (caret_pos == (caret_affinity == CURSOR_BACKWARD ? 0 : text().length())) { | 786 if (caret_pos == (caret_affinity == CURSOR_BACKWARD ? 0 : text().length())) { |
| 803 // The caret is attached to the boundary. Always return a 1-dip width caret, | 787 // The caret is attached to the boundary. Always return a 1-dip width caret, |
| 804 // since there is nothing to overtype. | 788 // since there is nothing to overtype. |
| 805 if ((GetTextDirection() == base::i18n::RIGHT_TO_LEFT) == (caret_pos == 0)) | 789 if ((GetTextDirection() == base::i18n::RIGHT_TO_LEFT) == (caret_pos == 0)) |
| (...skipping 20 matching lines...) Expand all Loading... |
| 826 size_t RenderText::IndexOfAdjacentGrapheme(size_t index, | 810 size_t RenderText::IndexOfAdjacentGrapheme(size_t index, |
| 827 LogicalCursorDirection direction) { | 811 LogicalCursorDirection direction) { |
| 828 if (index > text().length()) | 812 if (index > text().length()) |
| 829 return text().length(); | 813 return text().length(); |
| 830 | 814 |
| 831 EnsureLayout(); | 815 EnsureLayout(); |
| 832 | 816 |
| 833 if (direction == CURSOR_FORWARD) { | 817 if (direction == CURSOR_FORWARD) { |
| 834 while (index < text().length()) { | 818 while (index < text().length()) { |
| 835 index++; | 819 index++; |
| 836 if (IsValidCursorIndex(index)) | 820 if (IsCursorablePosition(index)) |
| 837 return index; | 821 return index; |
| 838 } | 822 } |
| 839 return text().length(); | 823 return text().length(); |
| 840 } | 824 } |
| 841 | 825 |
| 842 while (index > 0) { | 826 while (index > 0) { |
| 843 index--; | 827 index--; |
| 844 if (IsValidCursorIndex(index)) | 828 if (IsCursorablePosition(index)) |
| 845 return index; | 829 return index; |
| 846 } | 830 } |
| 847 return 0; | 831 return 0; |
| 848 } | 832 } |
| 849 | 833 |
| 850 SelectionModel RenderText::GetSelectionModelForSelectionStart() { | 834 SelectionModel RenderText::GetSelectionModelForSelectionStart() { |
| 851 const Range& sel = selection(); | 835 const Range& sel = selection(); |
| 852 if (sel.is_empty()) | 836 if (sel.is_empty()) |
| 853 return selection_model_; | 837 return selection_model_; |
| 854 return SelectionModel(sel.start(), | 838 return SelectionModel(sel.start(), |
| (...skipping 262 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1117 size_t caret_pos, | 1101 size_t caret_pos, |
| 1118 LogicalCursorDirection caret_affinity) { | 1102 LogicalCursorDirection caret_affinity) { |
| 1119 // NB: exploits unsigned wraparound (WG14/N1124 section 6.2.5 paragraph 9). | 1103 // NB: exploits unsigned wraparound (WG14/N1124 section 6.2.5 paragraph 9). |
| 1120 size_t adjacent = (caret_affinity == CURSOR_BACKWARD) ? | 1104 size_t adjacent = (caret_affinity == CURSOR_BACKWARD) ? |
| 1121 caret_pos - 1 : caret_pos + 1; | 1105 caret_pos - 1 : caret_pos + 1; |
| 1122 return range.Contains(Range(caret_pos, adjacent)); | 1106 return range.Contains(Range(caret_pos, adjacent)); |
| 1123 } | 1107 } |
| 1124 | 1108 |
| 1125 void RenderText::MoveCursorTo(size_t position, bool select) { | 1109 void RenderText::MoveCursorTo(size_t position, bool select) { |
| 1126 size_t cursor = std::min(position, text().length()); | 1110 size_t cursor = std::min(position, text().length()); |
| 1127 if (IsValidCursorIndex(cursor)) | 1111 if (IsCursorablePosition(cursor)) |
| 1128 SetSelectionModel(SelectionModel( | 1112 SetSelectionModel(SelectionModel( |
| 1129 Range(select ? selection().start() : cursor, cursor), | 1113 Range(select ? selection().start() : cursor, cursor), |
| 1130 (cursor == 0) ? CURSOR_FORWARD : CURSOR_BACKWARD)); | 1114 (cursor == 0) ? CURSOR_FORWARD : CURSOR_BACKWARD)); |
| 1131 } | 1115 } |
| 1132 | 1116 |
| 1133 void RenderText::UpdateLayoutText() { | 1117 void RenderText::UpdateLayoutText() { |
| 1134 layout_text_.clear(); | 1118 layout_text_.clear(); |
| 1135 line_breaks_.SetMax(0); | 1119 line_breaks_.SetMax(0); |
| 1136 | 1120 |
| 1137 if (obscured_) { | 1121 if (obscured_) { |
| (...skipping 179 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1317 cursor_bounds_ += delta_offset; | 1301 cursor_bounds_ += delta_offset; |
| 1318 } | 1302 } |
| 1319 | 1303 |
| 1320 void RenderText::DrawSelection(Canvas* canvas) { | 1304 void RenderText::DrawSelection(Canvas* canvas) { |
| 1321 const std::vector<Rect> sel = GetSubstringBounds(selection()); | 1305 const std::vector<Rect> sel = GetSubstringBounds(selection()); |
| 1322 for (std::vector<Rect>::const_iterator i = sel.begin(); i < sel.end(); ++i) | 1306 for (std::vector<Rect>::const_iterator i = sel.begin(); i < sel.end(); ++i) |
| 1323 canvas->FillRect(*i, selection_background_focused_color_); | 1307 canvas->FillRect(*i, selection_background_focused_color_); |
| 1324 } | 1308 } |
| 1325 | 1309 |
| 1326 } // namespace gfx | 1310 } // namespace gfx |
| OLD | NEW |