Commit 53c0b1ba authored by xji@google.com's avatar xji@google.com

This is a reapply of

http://src.chromium.org/viewvc/chrome?view=rev&revision=102006

fix know issues in RenderText

1. add tests.
2. change SelectWord() to use BreakIterator, so it works for Chinese and Complex script.
3. DELETE/ReplaceChar delete/replace a whole grapheme. ReplaceTextInternal should only replace one grapheme when there is no selection.
4. pointing to position outside of text returns HOME/END position.
5. based on Chrome Linux omnibox and gedit, given 
"abc|   def", double click should select "   " instead of "abc". Change test expectation.

BUG=90426
TEST=compile with touchui=1 test omnibox. run views_unittests.NativeTextfieldViewsTest/TextfieldViewsModelTest
Review URL: http://codereview.chromium.org/7841056

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@102160 0039d316-1c4b-4281-b951-d872f2087c98
parent df09e1b4
...@@ -92,6 +92,27 @@ bool BreakIterator::IsWord() const { ...@@ -92,6 +92,27 @@ bool BreakIterator::IsWord() const {
return (break_type_ == BREAK_WORD && status != UBRK_WORD_NONE); return (break_type_ == BREAK_WORD && status != UBRK_WORD_NONE);
} }
bool BreakIterator::IsEndOfWord(size_t position) const {
if (break_type_ != BREAK_WORD)
return false;
UBreakIterator* iter = static_cast<UBreakIterator*>(iter_);
UBool boundary = ubrk_isBoundary(iter, static_cast<int32_t>(position));
int32_t status = ubrk_getRuleStatus(iter);
return (!!boundary && status != UBRK_WORD_NONE);
}
bool BreakIterator::IsStartOfWord(size_t position) const {
if (break_type_ != BREAK_WORD)
return false;
UBreakIterator* iter = static_cast<UBreakIterator*>(iter_);
UBool boundary = ubrk_isBoundary(iter, static_cast<int32_t>(position));
ubrk_next(iter);
int32_t next_status = ubrk_getRuleStatus(iter);
return (!!boundary && next_status != UBRK_WORD_NONE);
}
string16 BreakIterator::GetString() const { string16 BreakIterator::GetString() const {
DCHECK(prev_ != npos && pos_ != npos); DCHECK(prev_ != npos && pos_ != npos);
return string_.substr(prev_, pos_ - prev_); return string_.substr(prev_, pos_ - prev_);
......
...@@ -88,6 +88,12 @@ class BASE_I18N_EXPORT BreakIterator { ...@@ -88,6 +88,12 @@ class BASE_I18N_EXPORT BreakIterator {
// this distinction doesn't apply and it always retuns false. // this distinction doesn't apply and it always retuns false.
bool IsWord() const; bool IsWord() const;
// Under BREAK_WORD mode, returns true if |position| is at the end of word or
// at the start of word. It always retuns false under BREAK_LINE and
// BREAK_NEWLINE modes.
bool IsEndOfWord(size_t position) const;
bool IsStartOfWord(size_t position) const;
// Returns the string between prev() and pos(). // Returns the string between prev() and pos().
// Advance() must have been called successfully at least once for pos() to // Advance() must have been called successfully at least once for pos() to
// have advanced to somewhere useful. // have advanced to somewhere useful.
......
...@@ -247,6 +247,12 @@ bool RenderText::MoveCursorTo(const SelectionModel& selection_model) { ...@@ -247,6 +247,12 @@ bool RenderText::MoveCursorTo(const SelectionModel& selection_model) {
sel.set_caret_pos(end.caret_pos()); sel.set_caret_pos(end.caret_pos());
sel.set_caret_placement(end.caret_placement()); sel.set_caret_placement(end.caret_placement());
} }
if (!IsCursorablePosition(sel.selection_start()) ||
!IsCursorablePosition(sel.selection_end()) ||
!IsCursorablePosition(sel.caret_pos()))
return false;
bool changed = !sel.Equals(selection_model_); bool changed = !sel.Equals(selection_model_);
SetSelectionModel(sel); SetSelectionModel(sel);
return changed; return changed;
...@@ -281,41 +287,28 @@ void RenderText::SelectAll() { ...@@ -281,41 +287,28 @@ void RenderText::SelectAll() {
SetSelectionModel(sel); SetSelectionModel(sel);
} }
// TODO(xji): it does not work for languages do not use space as word breaker,
// such as Chinese. Should use BreakIterator.
void RenderText::SelectWord() { void RenderText::SelectWord() {
size_t selection_start = GetSelectionStart();
size_t cursor_position = GetCursorPosition(); size_t cursor_position = GetCursorPosition();
// First we setup selection_start_ and selection_end_. There are so many cases
// because we try to emulate what select-word looks like in a gtk textfield.
// See associated testcase for different cases.
if (cursor_position > 0 && cursor_position < text().length()) {
if (u_isalnum(text()[cursor_position])) {
selection_start = cursor_position;
cursor_position++;
} else
selection_start = cursor_position - 1;
} else if (cursor_position == 0) {
selection_start = cursor_position;
if (text().length() > 0)
cursor_position++;
} else {
selection_start = cursor_position - 1;
}
// Now we move selection_start_ to beginning of selection. Selection boundary base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD);
// is defined as the position where we have alpha-num character on one side bool success = iter.Init();
// and non-alpha-num char on the other side. DCHECK(success);
for (; selection_start > 0; selection_start--) { if (!success)
if (IsPositionAtWordSelectionBoundary(selection_start)) return;
size_t selection_start = cursor_position;
for (; selection_start != 0; --selection_start) {
if (iter.IsStartOfWord(selection_start) ||
iter.IsEndOfWord(selection_start))
break; break;
} }
// Now we move selection_end_ to end of selection. Selection boundary if (selection_start == cursor_position)
// is defined as the position where we have alpha-num character on one side ++cursor_position;
// and non-alpha-num char on the other side.
for (; cursor_position < text().length(); cursor_position++) { for (; cursor_position < text().length(); ++cursor_position) {
if (IsPositionAtWordSelectionBoundary(cursor_position)) if (iter.IsEndOfWord(cursor_position) ||
iter.IsStartOfWord(cursor_position))
break; break;
} }
...@@ -462,6 +455,10 @@ const Rect& RenderText::GetUpdatedCursorBounds() { ...@@ -462,6 +455,10 @@ const Rect& RenderText::GetUpdatedCursorBounds() {
return cursor_bounds_; return cursor_bounds_;
} }
size_t RenderText::GetIndexOfNextGrapheme(size_t position) {
return IndexOfAdjacentGrapheme(position, true);
}
RenderText::RenderText() RenderText::RenderText()
: text_(), : text_(),
selection_model_(), selection_model_(),
...@@ -557,8 +554,7 @@ SelectionModel RenderText::RightEndSelectionModel() { ...@@ -557,8 +554,7 @@ SelectionModel RenderText::RightEndSelectionModel() {
} }
size_t RenderText::GetIndexOfPreviousGrapheme(size_t position) { size_t RenderText::GetIndexOfPreviousGrapheme(size_t position) {
// TODO(msw): Handle complex script. return IndexOfAdjacentGrapheme(position, false);
return std::max(static_cast<long>(position - 1), static_cast<long>(0));
} }
std::vector<Rect> RenderText::GetSubstringBounds(size_t from, size_t to) { std::vector<Rect> RenderText::GetSubstringBounds(size_t from, size_t to) {
...@@ -632,13 +628,10 @@ void RenderText::MoveCursorTo(size_t position, bool select) { ...@@ -632,13 +628,10 @@ void RenderText::MoveCursorTo(size_t position, bool select) {
SelectionModel::CaretPlacement placement = (caret_pos == cursor) ? SelectionModel::CaretPlacement placement = (caret_pos == cursor) ?
SelectionModel::LEADING : SelectionModel::TRAILING; SelectionModel::LEADING : SelectionModel::TRAILING;
size_t selection_start = select ? GetSelectionStart() : cursor; size_t selection_start = select ? GetSelectionStart() : cursor;
SelectionModel sel(selection_start, cursor, caret_pos, placement); if (IsCursorablePosition(cursor)) {
SetSelectionModel(sel); SelectionModel sel(selection_start, cursor, caret_pos, placement);
} SetSelectionModel(sel);
}
bool RenderText::IsPositionAtWordSelectionBoundary(size_t pos) {
return pos == 0 || (u_isalnum(text()[pos - 1]) && !u_isalnum(text()[pos])) ||
(!u_isalnum(text()[pos - 1]) && u_isalnum(text()[pos]));
} }
void RenderText::UpdateCachedBoundsAndOffset() { void RenderText::UpdateCachedBoundsAndOffset() {
......
...@@ -193,7 +193,9 @@ class UI_EXPORT RenderText { ...@@ -193,7 +193,9 @@ class UI_EXPORT RenderText {
// Set the selection_model_ to the value of |selection|. // Set the selection_model_ to the value of |selection|.
// The selection model components are modified if invalid. // The selection model components are modified if invalid.
// Returns true if the cursor position or selection range changed. // Returns true if the cursor position or selection range changed.
// TODO(xji): need to check the cursor is set at grapheme boundary. // If |selectin_start_| or |selection_end_| or |caret_pos_| in
// |selection_model| is not a cursorable position (not on grapheme boundary),
// it is a NO-OP and returns false.
bool MoveCursorTo(const SelectionModel& selection_model); bool MoveCursorTo(const SelectionModel& selection_model);
// Move the cursor to the position associated with the clicked point. // Move the cursor to the position associated with the clicked point.
...@@ -254,6 +256,9 @@ class UI_EXPORT RenderText { ...@@ -254,6 +256,9 @@ class UI_EXPORT RenderText {
// Subsequent text, cursor, or bounds changes may invalidate returned values. // Subsequent text, cursor, or bounds changes may invalidate returned values.
const Rect& GetUpdatedCursorBounds(); const Rect& GetUpdatedCursorBounds();
// Get the logical index of the grapheme following the argument |position|.
virtual size_t GetIndexOfNextGrapheme(size_t position);
protected: protected:
RenderText(); RenderText();
...@@ -288,6 +293,10 @@ class UI_EXPORT RenderText { ...@@ -288,6 +293,10 @@ class UI_EXPORT RenderText {
// TODO(msw) Re-evaluate this function's necessity and signature. // TODO(msw) Re-evaluate this function's necessity and signature.
virtual std::vector<Rect> GetSubstringBounds(size_t from, size_t to); virtual std::vector<Rect> GetSubstringBounds(size_t from, size_t to);
// Return true if cursor can appear in front of the character at |position|,
// which means it is a grapheme boundary or the first character in the text.
virtual bool IsCursorablePosition(size_t position) = 0;
// Apply composition style (underline) to composition range and selection // Apply composition style (underline) to composition range and selection
// style (foreground) to selection range. // style (foreground) to selection range.
void ApplyCompositionAndSelectionStyles(StyleRanges* style_ranges) const; void ApplyCompositionAndSelectionStyles(StyleRanges* style_ranges) const;
...@@ -305,16 +314,20 @@ class UI_EXPORT RenderText { ...@@ -305,16 +314,20 @@ class UI_EXPORT RenderText {
FRIEND_TEST_ALL_PREFIXES(RenderTextTest, ApplyStyleRange); FRIEND_TEST_ALL_PREFIXES(RenderTextTest, ApplyStyleRange);
FRIEND_TEST_ALL_PREFIXES(RenderTextTest, StyleRangesAdjust); FRIEND_TEST_ALL_PREFIXES(RenderTextTest, StyleRangesAdjust);
// Return an index belonging to the |next| or previous logical grapheme.
// The return value is bounded by 0 and the text length, inclusive.
virtual size_t IndexOfAdjacentGrapheme(size_t index, bool next) = 0;
// Sets the selection model, the argument is assumed to be valid. // Sets the selection model, the argument is assumed to be valid.
void SetSelectionModel(const SelectionModel& selection_model); void SetSelectionModel(const SelectionModel& selection_model);
// Set the cursor to |position|, with the caret trailing the previous // Set the cursor to |position|, with the caret trailing the previous
// grapheme, or if there is no previous grapheme, leading the cursor position. // grapheme, or if there is no previous grapheme, leading the cursor position.
// If |select| is false, the selection start is moved to the same position. // If |select| is false, the selection start is moved to the same position.
// If the |position| is not a cursorable position (not on grapheme boundary),
// it is a NO-OP.
void MoveCursorTo(size_t position, bool select); void MoveCursorTo(size_t position, bool select);
bool IsPositionAtWordSelectionBoundary(size_t pos);
// Update the cached bounds and display offset to ensure that the current // Update the cached bounds and display offset to ensure that the current
// cursor is within the visible display area. // cursor is within the visible display area.
void UpdateCachedBoundsAndOffset(); void UpdateCachedBoundsAndOffset();
......
...@@ -123,13 +123,19 @@ void RenderTextLinux::Draw(Canvas* canvas) { ...@@ -123,13 +123,19 @@ void RenderTextLinux::Draw(Canvas* canvas) {
} }
SelectionModel RenderTextLinux::FindCursorPosition(const Point& point) { SelectionModel RenderTextLinux::FindCursorPosition(const Point& point) {
// TODO(xji): when points outside of text, return HOME/END position.
PangoLayout* layout = EnsureLayout(); PangoLayout* layout = EnsureLayout();
if (text().empty()) if (text().empty())
return SelectionModel(0, 0, SelectionModel::LEADING); return SelectionModel(0, 0, SelectionModel::LEADING);
Point p(ToTextPoint(point)); Point p(ToTextPoint(point));
// When the point is outside of text, return HOME/END position.
if (p.x() < 0)
return LeftEndSelectionModel();
else if (p.x() > GetStringWidth())
return RightEndSelectionModel();
int caret_pos, trailing; int caret_pos, trailing;
pango_layout_xy_to_index(layout, p.x() * PANGO_SCALE, p.y() * PANGO_SCALE, pango_layout_xy_to_index(layout, p.x() * PANGO_SCALE, p.y() * PANGO_SCALE,
&caret_pos, &trailing); &caret_pos, &trailing);
...@@ -212,7 +218,7 @@ SelectionModel RenderTextLinux::LeftEndSelectionModel() { ...@@ -212,7 +218,7 @@ SelectionModel RenderTextLinux::LeftEndSelectionModel() {
return SelectionModel(text().length(), caret, SelectionModel::LEADING); return SelectionModel(text().length(), caret, SelectionModel::LEADING);
} else { // RTL. } else { // RTL.
size_t caret = Utf16IndexOfAdjacentGrapheme(item->offset + item->length, size_t caret = Utf16IndexOfAdjacentGrapheme(item->offset + item->length,
PREVIOUS); false);
return SelectionModel(text().length(), caret, SelectionModel::TRAILING); return SelectionModel(text().length(), caret, SelectionModel::TRAILING);
} }
} }
...@@ -227,7 +233,7 @@ SelectionModel RenderTextLinux::RightEndSelectionModel() { ...@@ -227,7 +233,7 @@ SelectionModel RenderTextLinux::RightEndSelectionModel() {
PangoItem* item = last_visual_run->item; PangoItem* item = last_visual_run->item;
if (item->analysis.level % 2 == 0) { // LTR. if (item->analysis.level % 2 == 0) { // LTR.
size_t caret = Utf16IndexOfAdjacentGrapheme(item->offset + item->length, size_t caret = Utf16IndexOfAdjacentGrapheme(item->offset + item->length,
PREVIOUS); false);
return SelectionModel(text().length(), caret, SelectionModel::TRAILING); return SelectionModel(text().length(), caret, SelectionModel::TRAILING);
} else { // RTL. } else { // RTL.
size_t caret = Utf8IndexToUtf16Index(item->offset); size_t caret = Utf8IndexToUtf16Index(item->offset);
...@@ -238,16 +244,18 @@ SelectionModel RenderTextLinux::RightEndSelectionModel() { ...@@ -238,16 +244,18 @@ SelectionModel RenderTextLinux::RightEndSelectionModel() {
return SelectionModel(0, 0, SelectionModel::LEADING); return SelectionModel(0, 0, SelectionModel::LEADING);
} }
size_t RenderTextLinux::GetIndexOfPreviousGrapheme(size_t position) { bool RenderTextLinux::IsCursorablePosition(size_t position) {
if (position == 0 && text().empty())
return true;
EnsureLayout(); EnsureLayout();
size_t index = Utf16IndexToUtf8Index(position); return (position < static_cast<size_t>(num_log_attrs_) &&
return Utf16IndexOfAdjacentGrapheme(index, PREVIOUS); log_attrs_[position].is_cursor_position);
} }
size_t RenderTextLinux::GetIndexOfNextGrapheme(size_t position) { size_t RenderTextLinux::IndexOfAdjacentGrapheme(size_t index, bool next) {
EnsureLayout(); EnsureLayout();
size_t index = Utf16IndexToUtf8Index(position); return Utf16IndexOfAdjacentGrapheme(Utf16IndexToUtf8Index(index), next);
return Utf16IndexOfAdjacentGrapheme(index, NEXT);
} }
GSList* RenderTextLinux::GetRunContainingPosition(size_t position) const { GSList* RenderTextLinux::GetRunContainingPosition(size_t position) const {
...@@ -266,12 +274,12 @@ GSList* RenderTextLinux::GetRunContainingPosition(size_t position) const { ...@@ -266,12 +274,12 @@ GSList* RenderTextLinux::GetRunContainingPosition(size_t position) const {
size_t RenderTextLinux::Utf8IndexOfAdjacentGrapheme( size_t RenderTextLinux::Utf8IndexOfAdjacentGrapheme(
size_t utf8_index_of_current_grapheme, size_t utf8_index_of_current_grapheme,
RelativeLogicalPosition pos) const { bool next) const {
const char* ch = layout_text_ + utf8_index_of_current_grapheme; const char* ch = layout_text_ + utf8_index_of_current_grapheme;
int char_offset = static_cast<int>(g_utf8_pointer_to_offset(layout_text_, int char_offset = static_cast<int>(g_utf8_pointer_to_offset(layout_text_,
ch)); ch));
int start_char_offset = char_offset; int start_char_offset = char_offset;
if (pos == PREVIOUS) { if (!next) {
if (char_offset > 0) { if (char_offset > 0) {
do { do {
--char_offset; --char_offset;
...@@ -292,23 +300,23 @@ size_t RenderTextLinux::Utf8IndexOfAdjacentGrapheme( ...@@ -292,23 +300,23 @@ size_t RenderTextLinux::Utf8IndexOfAdjacentGrapheme(
size_t RenderTextLinux::Utf16IndexOfAdjacentGrapheme( size_t RenderTextLinux::Utf16IndexOfAdjacentGrapheme(
size_t utf8_index_of_current_grapheme, size_t utf8_index_of_current_grapheme,
RelativeLogicalPosition pos) const { bool next) const {
size_t utf8_index = Utf8IndexOfAdjacentGrapheme( size_t utf8_index = Utf8IndexOfAdjacentGrapheme(
utf8_index_of_current_grapheme, pos); utf8_index_of_current_grapheme, next);
return Utf8IndexToUtf16Index(utf8_index); return Utf8IndexToUtf16Index(utf8_index);
} }
SelectionModel RenderTextLinux::FirstSelectionModelInsideRun( SelectionModel RenderTextLinux::FirstSelectionModelInsideRun(
const PangoItem* item) const { const PangoItem* item) const {
size_t caret = Utf8IndexToUtf16Index(item->offset); size_t caret = Utf8IndexToUtf16Index(item->offset);
size_t cursor = Utf16IndexOfAdjacentGrapheme(item->offset, NEXT); size_t cursor = Utf16IndexOfAdjacentGrapheme(item->offset, true);
return SelectionModel(cursor, caret, SelectionModel::TRAILING); return SelectionModel(cursor, caret, SelectionModel::TRAILING);
} }
SelectionModel RenderTextLinux::LastSelectionModelInsideRun( SelectionModel RenderTextLinux::LastSelectionModelInsideRun(
const PangoItem* item) const { const PangoItem* item) const {
size_t caret = Utf16IndexOfAdjacentGrapheme(item->offset + item->length, size_t caret = Utf16IndexOfAdjacentGrapheme(item->offset + item->length,
PREVIOUS); false);
return SelectionModel(caret, caret, SelectionModel::LEADING); return SelectionModel(caret, caret, SelectionModel::LEADING);
} }
......
...@@ -39,28 +39,22 @@ class RenderTextLinux : public RenderText { ...@@ -39,28 +39,22 @@ class RenderTextLinux : public RenderText {
BreakType break_type) OVERRIDE; BreakType break_type) OVERRIDE;
virtual SelectionModel LeftEndSelectionModel() OVERRIDE; virtual SelectionModel LeftEndSelectionModel() OVERRIDE;
virtual SelectionModel RightEndSelectionModel() OVERRIDE; virtual SelectionModel RightEndSelectionModel() OVERRIDE;
virtual size_t GetIndexOfPreviousGrapheme(size_t position) OVERRIDE; virtual bool IsCursorablePosition(size_t position) OVERRIDE;
private: private:
enum RelativeLogicalPosition { virtual size_t IndexOfAdjacentGrapheme(size_t index, bool next) OVERRIDE;
PREVIOUS,
NEXT
};
// Get the logical start index of the next grapheme after |position|.
size_t GetIndexOfNextGrapheme(size_t position);
// Returns the run that contains |position|. Return NULL if not found. // Returns the run that contains |position|. Return NULL if not found.
GSList* GetRunContainingPosition(size_t position) const; GSList* GetRunContainingPosition(size_t position) const;
// Given |utf8_index_of_current_grapheme|, returns the UTF8 or UTF16 index of // Given |utf8_index_of_current_grapheme|, returns the UTF8 or UTF16 index of
// next grapheme in the text if |pos| is NEXT, otherwise, returns the index of // next grapheme in the text if |next| is true, otherwise, returns the index
// previous grapheme. Returns 0 if there is no previous grapheme, and returns // of previous grapheme. Returns 0 if there is no previous grapheme, and
// the |text_| length if there is no next grapheme. // returns the |text_| length if there is no next grapheme.
size_t Utf8IndexOfAdjacentGrapheme(size_t utf8_index_of_current_grapheme, size_t Utf8IndexOfAdjacentGrapheme(size_t utf8_index_of_current_grapheme,
RelativeLogicalPosition pos) const; bool next) const;
size_t Utf16IndexOfAdjacentGrapheme(size_t utf8_index_of_current_grapheme, size_t Utf16IndexOfAdjacentGrapheme(size_t utf8_index_of_current_grapheme,
RelativeLogicalPosition pos) const; bool next) const;
// Given a |run|, returns the SelectionModel that contains the logical first // Given a |run|, returns the SelectionModel that contains the logical first
// or last caret position inside (not at a boundary of) the run. // or last caret position inside (not at a boundary of) the run.
......
This diff is collapsed.
...@@ -237,10 +237,6 @@ SelectionModel RenderTextWin::RightEndSelectionModel() { ...@@ -237,10 +237,6 @@ SelectionModel RenderTextWin::RightEndSelectionModel() {
return SelectionModel(cursor, caret, placement); return SelectionModel(cursor, caret, placement);
} }
size_t RenderTextWin::GetIndexOfPreviousGrapheme(size_t position) {
return IndexOfAdjacentGrapheme(position, false);
}
std::vector<Rect> RenderTextWin::GetSubstringBounds(size_t from, size_t to) { std::vector<Rect> RenderTextWin::GetSubstringBounds(size_t from, size_t to) {
ui::Range range(from, to); ui::Range range(from, to);
DCHECK(ui::Range(0, text().length()).Contains(range)); DCHECK(ui::Range(0, text().length()).Contains(range));
...@@ -295,6 +291,41 @@ std::vector<Rect> RenderTextWin::GetSubstringBounds(size_t from, size_t to) { ...@@ -295,6 +291,41 @@ std::vector<Rect> RenderTextWin::GetSubstringBounds(size_t from, size_t to) {
return bounds; return bounds;
} }
bool RenderTextWin::IsCursorablePosition(size_t position) {
if (position == 0 || position == text().length())
return true;
size_t run_index = GetRunContainingPosition(position);
if (run_index >= runs_.size())
return false;
internal::TextRun* run = runs_[run_index];
size_t start = run->range.start();
if (position == start)
return true;
return run->logical_clusters[position - start] !=
run->logical_clusters[position - start - 1];
}
size_t RenderTextWin::IndexOfAdjacentGrapheme(size_t index, bool next) {
size_t run_index = GetRunContainingPosition(index);
internal::TextRun* run = run_index < runs_.size() ? runs_[run_index] : NULL;
long start = run ? run->range.start() : 0;
long length = run ? run->range.length() : text().length();
long ch = index - start;
WORD cluster = run ? run->logical_clusters[ch] : 0;
if (!next) {
do {
ch--;
} while (ch >= 0 && run && run->logical_clusters[ch] == cluster);
} else {
while (ch < length && run && run->logical_clusters[ch] == cluster)
ch++;
}
return std::max(static_cast<long>(std::min(ch, length) + start), 0L);
}
void RenderTextWin::ItemizeLogicalText() { void RenderTextWin::ItemizeLogicalText() {
text_is_dirty_ = false; text_is_dirty_ = false;
STLDeleteContainerPointers(runs_.begin(), runs_.end()); STLDeleteContainerPointers(runs_.begin(), runs_.end());
...@@ -461,34 +492,16 @@ size_t RenderTextWin::GetRunContainingPoint(const Point& point) const { ...@@ -461,34 +492,16 @@ size_t RenderTextWin::GetRunContainingPoint(const Point& point) const {
return run; return run;
} }
size_t RenderTextWin::IndexOfAdjacentGrapheme(size_t index, bool next) const {
size_t run_index = GetRunContainingPosition(index);
internal::TextRun* run = run_index < runs_.size() ? runs_[run_index] : NULL;
long start = run ? run->range.start() : 0;
long length = run ? run->range.length() : text().length();
long ch = index - start;
WORD cluster = run ? run->logical_clusters[ch] : 0;
if (!next) {
do {
ch--;
} while (ch >= 0 && run && run->logical_clusters[ch] == cluster);
} else {
while (ch < length && run && run->logical_clusters[ch] == cluster)
ch++;
}
return std::max(static_cast<long>(std::min(ch, length) + start), 0L);
}
SelectionModel RenderTextWin::FirstSelectionModelInsideRun( SelectionModel RenderTextWin::FirstSelectionModelInsideRun(
internal::TextRun* run) const { internal::TextRun* run) {
size_t caret = run->range.start(); size_t caret = run->range.start();
size_t cursor = IndexOfAdjacentGrapheme(caret, true); size_t cursor = IndexOfAdjacentGrapheme(caret, true);
return SelectionModel(cursor, caret, SelectionModel::TRAILING); return SelectionModel(cursor, caret, SelectionModel::TRAILING);
} }
SelectionModel RenderTextWin::LastSelectionModelInsideRun( SelectionModel RenderTextWin::LastSelectionModelInsideRun(
internal::TextRun* run) const { internal::TextRun* run) {
size_t caret = IndexOfAdjacentGrapheme(run->range.end(), false); size_t caret = IndexOfAdjacentGrapheme(run->range.end(), false);
return SelectionModel(caret, caret, SelectionModel::LEADING); return SelectionModel(caret, caret, SelectionModel::LEADING);
} }
......
...@@ -72,10 +72,12 @@ class RenderTextWin : public RenderText { ...@@ -72,10 +72,12 @@ class RenderTextWin : public RenderText {
BreakType break_type) OVERRIDE; BreakType break_type) OVERRIDE;
virtual SelectionModel LeftEndSelectionModel() OVERRIDE; virtual SelectionModel LeftEndSelectionModel() OVERRIDE;
virtual SelectionModel RightEndSelectionModel() OVERRIDE; virtual SelectionModel RightEndSelectionModel() OVERRIDE;
virtual size_t GetIndexOfPreviousGrapheme(size_t position) OVERRIDE;
virtual std::vector<Rect> GetSubstringBounds(size_t from, size_t to) OVERRIDE; virtual std::vector<Rect> GetSubstringBounds(size_t from, size_t to) OVERRIDE;
virtual bool IsCursorablePosition(size_t position) OVERRIDE;
private: private:
virtual size_t IndexOfAdjacentGrapheme(size_t index, bool next) OVERRIDE;
void ItemizeLogicalText(); void ItemizeLogicalText();
void LayoutVisualText(HDC hdc); void LayoutVisualText(HDC hdc);
...@@ -84,15 +86,11 @@ class RenderTextWin : public RenderText { ...@@ -84,15 +86,11 @@ class RenderTextWin : public RenderText {
size_t GetRunContainingPosition(size_t position) const; size_t GetRunContainingPosition(size_t position) const;
size_t GetRunContainingPoint(const Point& point) const; size_t GetRunContainingPoint(const Point& point) const;
// Return an index belonging to the |next| or previous logical grapheme.
// The return value is bounded by 0 and the text length, inclusive.
size_t IndexOfAdjacentGrapheme(size_t index, bool next) const;
// Given a |run|, returns the SelectionModel that contains the logical first // Given a |run|, returns the SelectionModel that contains the logical first
// or last caret position inside (not at a boundary of) the run. // or last caret position inside (not at a boundary of) the run.
// The returned value represents a cursor/caret position without a selection. // The returned value represents a cursor/caret position without a selection.
SelectionModel FirstSelectionModelInsideRun(internal::TextRun*) const; SelectionModel FirstSelectionModelInsideRun(internal::TextRun*);
SelectionModel LastSelectionModelInsideRun(internal::TextRun*) const; SelectionModel LastSelectionModelInsideRun(internal::TextRun*);
// Get the selection model visually left/right of |selection| by one grapheme. // Get the selection model visually left/right of |selection| by one grapheme.
// The returned value represents a cursor/caret position without a selection. // The returned value represents a cursor/caret position without a selection.
......
...@@ -320,7 +320,10 @@ void TextfieldViewsModel::Append(const string16& text) { ...@@ -320,7 +320,10 @@ void TextfieldViewsModel::Append(const string16& text) {
if (HasCompositionText()) if (HasCompositionText())
ConfirmCompositionText(); ConfirmCompositionText();
size_t save = GetCursorPosition(); size_t save = GetCursorPosition();
MoveCursorRight(gfx::LINE_BREAK, false); if (render_text_->GetTextDirection() == base::i18n::LEFT_TO_RIGHT)
MoveCursorRight(gfx::LINE_BREAK, false);
else
MoveCursorLeft(gfx::LINE_BREAK, false);
InsertText(text); InsertText(text);
render_text_->SetCursorPosition(save); render_text_->SetCursorPosition(save);
ClearSelection(); ClearSelection();
...@@ -338,7 +341,9 @@ bool TextfieldViewsModel::Delete() { ...@@ -338,7 +341,9 @@ bool TextfieldViewsModel::Delete() {
} }
if (GetText().length() > GetCursorPosition()) { if (GetText().length() > GetCursorPosition()) {
size_t cursor_position = GetCursorPosition(); size_t cursor_position = GetCursorPosition();
ExecuteAndRecordDelete(cursor_position, cursor_position + 1, true); size_t next_grapheme_index =
render_text_->GetIndexOfNextGrapheme(cursor_position);
ExecuteAndRecordDelete(cursor_position, next_grapheme_index, true);
return true; return true;
} }
return false; return false;
...@@ -656,7 +661,7 @@ void TextfieldViewsModel::ReplaceTextInternal(const string16& text, ...@@ -656,7 +661,7 @@ void TextfieldViewsModel::ReplaceTextInternal(const string16& text,
} else if (!HasSelection()) { } else if (!HasSelection()) {
size_t cursor = GetCursorPosition(); size_t cursor = GetCursorPosition();
gfx::SelectionModel sel(render_text_->selection_model()); gfx::SelectionModel sel(render_text_->selection_model());
sel.set_selection_start(cursor + text.length()); sel.set_selection_start(render_text_->GetIndexOfNextGrapheme(cursor));
render_text_->MoveCursorTo(sel); render_text_->MoveCursorTo(sel);
} }
// Edit history is recorded in InsertText. // Edit history is recorded in InsertText.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment