summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorxji@google.com <xji@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2011-09-20 20:30:06 +0000
committerxji@google.com <xji@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2011-09-20 20:30:06 +0000
commit4eb0f75ebb5ed7d029700b6647a64a9917f1e7b4 (patch)
tree6d39c50a8e5def9399fa86955615e675204b58fc
parent9cabbaf78c3a3bbcb52c0ae6fbb06afd98224bf1 (diff)
downloadchromium_src-4eb0f75ebb5ed7d029700b6647a64a9917f1e7b4.zip
chromium_src-4eb0f75ebb5ed7d029700b6647a64a9917f1e7b4.tar.gz
chromium_src-4eb0f75ebb5ed7d029700b6647a64a9917f1e7b4.tar.bz2
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@102006 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--base/i18n/break_iterator.cc21
-rw-r--r--base/i18n/break_iterator.h6
-rw-r--r--ui/gfx/render_text.cc69
-rw-r--r--ui/gfx/render_text.h19
-rw-r--r--ui/gfx/render_text_linux.cc38
-rw-r--r--ui/gfx/render_text_linux.h20
-rw-r--r--ui/gfx/render_text_unittest.cc337
-rw-r--r--ui/gfx/render_text_win.cc61
-rw-r--r--ui/gfx/render_text_win.h12
-rw-r--r--views/controls/textfield/native_textfield_views_unittest.cc394
-rw-r--r--views/controls/textfield/textfield_views_model.cc11
-rw-r--r--views/controls/textfield/textfield_views_model_unittest.cc294
12 files changed, 1156 insertions, 126 deletions
diff --git a/base/i18n/break_iterator.cc b/base/i18n/break_iterator.cc
index afa1fd8..eae531c 100644
--- a/base/i18n/break_iterator.cc
+++ b/base/i18n/break_iterator.cc
@@ -92,6 +92,27 @@ bool BreakIterator::IsWord() const {
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 {
DCHECK(prev_ != npos && pos_ != npos);
return string_.substr(prev_, pos_ - prev_);
diff --git a/base/i18n/break_iterator.h b/base/i18n/break_iterator.h
index f375962..3207256 100644
--- a/base/i18n/break_iterator.h
+++ b/base/i18n/break_iterator.h
@@ -88,6 +88,12 @@ class BASE_I18N_EXPORT BreakIterator {
// this distinction doesn't apply and it always retuns false.
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().
// Advance() must have been called successfully at least once for pos() to
// have advanced to somewhere useful.
diff --git a/ui/gfx/render_text.cc b/ui/gfx/render_text.cc
index 1111842..6d15e5c 100644
--- a/ui/gfx/render_text.cc
+++ b/ui/gfx/render_text.cc
@@ -247,6 +247,12 @@ bool RenderText::MoveCursorTo(const SelectionModel& selection_model) {
sel.set_caret_pos(end.caret_pos());
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_);
SetSelectionModel(sel);
return changed;
@@ -281,41 +287,28 @@ void RenderText::SelectAll() {
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() {
- size_t selection_start = GetSelectionStart();
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
- // is defined as the position where we have alpha-num character on one side
- // and non-alpha-num char on the other side.
- for (; selection_start > 0; selection_start--) {
- if (IsPositionAtWordSelectionBoundary(selection_start))
+ base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD);
+ bool success = iter.Init();
+ DCHECK(success);
+ if (!success)
+ return;
+
+ size_t selection_start = cursor_position;
+ for (; selection_start != 0; --selection_start) {
+ if (iter.IsStartOfWord(selection_start) ||
+ iter.IsEndOfWord(selection_start))
break;
}
- // Now we move selection_end_ to end of selection. Selection boundary
- // is defined as the position where we have alpha-num character on one side
- // and non-alpha-num char on the other side.
- for (; cursor_position < text().length(); cursor_position++) {
- if (IsPositionAtWordSelectionBoundary(cursor_position))
+ if (selection_start == cursor_position)
+ ++cursor_position;
+
+ for (; cursor_position < text().length(); ++cursor_position) {
+ if (iter.IsEndOfWord(cursor_position) ||
+ iter.IsStartOfWord(cursor_position))
break;
}
@@ -462,6 +455,10 @@ const Rect& RenderText::GetUpdatedCursorBounds() {
return cursor_bounds_;
}
+size_t RenderText::GetIndexOfNextGrapheme(size_t position) {
+ return IndexOfAdjacentGrapheme(position, true);
+}
+
RenderText::RenderText()
: text_(),
selection_model_(),
@@ -557,8 +554,7 @@ SelectionModel RenderText::RightEndSelectionModel() {
}
size_t RenderText::GetIndexOfPreviousGrapheme(size_t position) {
- // TODO(msw): Handle complex script.
- return std::max(static_cast<long>(position - 1), static_cast<long>(0));
+ return IndexOfAdjacentGrapheme(position, false);
}
std::vector<Rect> RenderText::GetSubstringBounds(size_t from, size_t to) {
@@ -632,13 +628,10 @@ void RenderText::MoveCursorTo(size_t position, bool select) {
SelectionModel::CaretPlacement placement = (caret_pos == cursor) ?
SelectionModel::LEADING : SelectionModel::TRAILING;
size_t selection_start = select ? GetSelectionStart() : cursor;
- 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]));
+ if (IsCursorablePosition(cursor)) {
+ SelectionModel sel(selection_start, cursor, caret_pos, placement);
+ SetSelectionModel(sel);
+ }
}
void RenderText::UpdateCachedBoundsAndOffset() {
diff --git a/ui/gfx/render_text.h b/ui/gfx/render_text.h
index 82eab91..4a1f9a9 100644
--- a/ui/gfx/render_text.h
+++ b/ui/gfx/render_text.h
@@ -193,7 +193,9 @@ class UI_EXPORT RenderText {
// Set the selection_model_ to the value of |selection|.
// The selection model components are modified if invalid.
// 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);
// Move the cursor to the position associated with the clicked point.
@@ -254,6 +256,9 @@ class UI_EXPORT RenderText {
// Subsequent text, cursor, or bounds changes may invalidate returned values.
const Rect& GetUpdatedCursorBounds();
+ // Get the logical index of the grapheme following the argument |position|.
+ virtual size_t GetIndexOfNextGrapheme(size_t position);
+
protected:
RenderText();
@@ -288,6 +293,10 @@ class UI_EXPORT RenderText {
// TODO(msw) Re-evaluate this function's necessity and signature.
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
// style (foreground) to selection range.
void ApplyCompositionAndSelectionStyles(StyleRanges* style_ranges) const;
@@ -305,16 +314,20 @@ class UI_EXPORT RenderText {
FRIEND_TEST_ALL_PREFIXES(RenderTextTest, ApplyStyleRange);
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.
void SetSelectionModel(const SelectionModel& selection_model);
// Set the cursor to |position|, with the caret trailing the previous
// 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 the |position| is not a cursorable position (not on grapheme boundary),
+ // it is a NO-OP.
void MoveCursorTo(size_t position, bool select);
- bool IsPositionAtWordSelectionBoundary(size_t pos);
-
// Update the cached bounds and display offset to ensure that the current
// cursor is within the visible display area.
void UpdateCachedBoundsAndOffset();
diff --git a/ui/gfx/render_text_linux.cc b/ui/gfx/render_text_linux.cc
index 1e5e865..d5ac9c1 100644
--- a/ui/gfx/render_text_linux.cc
+++ b/ui/gfx/render_text_linux.cc
@@ -123,13 +123,19 @@ void RenderTextLinux::Draw(Canvas* canvas) {
}
SelectionModel RenderTextLinux::FindCursorPosition(const Point& point) {
- // TODO(xji): when points outside of text, return HOME/END position.
PangoLayout* layout = EnsureLayout();
if (text().empty())
return SelectionModel(0, 0, SelectionModel::LEADING);
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;
pango_layout_xy_to_index(layout, p.x() * PANGO_SCALE, p.y() * PANGO_SCALE,
&caret_pos, &trailing);
@@ -212,7 +218,7 @@ SelectionModel RenderTextLinux::LeftEndSelectionModel() {
return SelectionModel(text().length(), caret, SelectionModel::LEADING);
} else { // RTL.
size_t caret = Utf16IndexOfAdjacentGrapheme(item->offset + item->length,
- PREVIOUS);
+ false);
return SelectionModel(text().length(), caret, SelectionModel::TRAILING);
}
}
@@ -227,7 +233,7 @@ SelectionModel RenderTextLinux::RightEndSelectionModel() {
PangoItem* item = last_visual_run->item;
if (item->analysis.level % 2 == 0) { // LTR.
size_t caret = Utf16IndexOfAdjacentGrapheme(item->offset + item->length,
- PREVIOUS);
+ false);
return SelectionModel(text().length(), caret, SelectionModel::TRAILING);
} else { // RTL.
size_t caret = Utf8IndexToUtf16Index(item->offset);
@@ -238,16 +244,18 @@ SelectionModel RenderTextLinux::RightEndSelectionModel() {
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();
- size_t index = Utf16IndexToUtf8Index(position);
- return Utf16IndexOfAdjacentGrapheme(index, PREVIOUS);
+ return (position >= 0 && position < static_cast<size_t>(num_log_attrs_) &&
+ log_attrs_[position].is_cursor_position);
}
-size_t RenderTextLinux::GetIndexOfNextGrapheme(size_t position) {
+size_t RenderTextLinux::IndexOfAdjacentGrapheme(size_t index, bool next) {
EnsureLayout();
- size_t index = Utf16IndexToUtf8Index(position);
- return Utf16IndexOfAdjacentGrapheme(index, NEXT);
+ return Utf16IndexOfAdjacentGrapheme(Utf16IndexToUtf8Index(index), next);
}
GSList* RenderTextLinux::GetRunContainingPosition(size_t position) const {
@@ -266,12 +274,12 @@ GSList* RenderTextLinux::GetRunContainingPosition(size_t position) const {
size_t RenderTextLinux::Utf8IndexOfAdjacentGrapheme(
size_t utf8_index_of_current_grapheme,
- RelativeLogicalPosition pos) const {
+ bool next) const {
const char* ch = layout_text_ + utf8_index_of_current_grapheme;
int char_offset = static_cast<int>(g_utf8_pointer_to_offset(layout_text_,
ch));
int start_char_offset = char_offset;
- if (pos == PREVIOUS) {
+ if (!next) {
if (char_offset > 0) {
do {
--char_offset;
@@ -292,23 +300,23 @@ size_t RenderTextLinux::Utf8IndexOfAdjacentGrapheme(
size_t RenderTextLinux::Utf16IndexOfAdjacentGrapheme(
size_t utf8_index_of_current_grapheme,
- RelativeLogicalPosition pos) const {
+ bool next) const {
size_t utf8_index = Utf8IndexOfAdjacentGrapheme(
- utf8_index_of_current_grapheme, pos);
+ utf8_index_of_current_grapheme, next);
return Utf8IndexToUtf16Index(utf8_index);
}
SelectionModel RenderTextLinux::FirstSelectionModelInsideRun(
const PangoItem* item) const {
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);
}
SelectionModel RenderTextLinux::LastSelectionModelInsideRun(
const PangoItem* item) const {
size_t caret = Utf16IndexOfAdjacentGrapheme(item->offset + item->length,
- PREVIOUS);
+ false);
return SelectionModel(caret, caret, SelectionModel::LEADING);
}
diff --git a/ui/gfx/render_text_linux.h b/ui/gfx/render_text_linux.h
index 5d67b41..641e261 100644
--- a/ui/gfx/render_text_linux.h
+++ b/ui/gfx/render_text_linux.h
@@ -39,28 +39,22 @@ class RenderTextLinux : public RenderText {
BreakType break_type) OVERRIDE;
virtual SelectionModel LeftEndSelectionModel() OVERRIDE;
virtual SelectionModel RightEndSelectionModel() OVERRIDE;
- virtual size_t GetIndexOfPreviousGrapheme(size_t position) OVERRIDE;
+ virtual bool IsCursorablePosition(size_t position) OVERRIDE;
private:
- enum RelativeLogicalPosition {
- PREVIOUS,
- NEXT
- };
-
- // Get the logical start index of the next grapheme after |position|.
- size_t GetIndexOfNextGrapheme(size_t position);
+ virtual size_t IndexOfAdjacentGrapheme(size_t index, bool next) OVERRIDE;
// Returns the run that contains |position|. Return NULL if not found.
GSList* GetRunContainingPosition(size_t position) const;
// 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
- // previous grapheme. Returns 0 if there is no previous grapheme, and returns
- // the |text_| length if there is no next grapheme.
+ // next grapheme in the text if |next| is true, otherwise, returns the index
+ // of previous grapheme. Returns 0 if there is no previous grapheme, and
+ // returns the |text_| length if there is no next 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,
- RelativeLogicalPosition pos) const;
+ bool next) const;
// Given a |run|, returns the SelectionModel that contains the logical first
// or last caret position inside (not at a boundary of) the run.
diff --git a/ui/gfx/render_text_unittest.cc b/ui/gfx/render_text_unittest.cc
index 26389df..6898537 100644
--- a/ui/gfx/render_text_unittest.cc
+++ b/ui/gfx/render_text_unittest.cc
@@ -15,14 +15,14 @@ class RenderTextTest : public testing::Test {
TEST_F(RenderTextTest, DefaultStyle) {
// Defaults to empty text with no styles.
- scoped_ptr<gfx::RenderText> render_text(gfx::RenderText::CreateRenderText());
+ scoped_ptr<RenderText> render_text(RenderText::CreateRenderText());
EXPECT_TRUE(render_text->text().empty());
EXPECT_TRUE(render_text->style_ranges().empty());
// Test that the built-in default style is applied for new text.
render_text->SetText(ASCIIToUTF16("abc"));
EXPECT_EQ(1U, render_text->style_ranges().size());
- gfx::StyleRange style;
+ StyleRange style;
EXPECT_EQ(style.font.GetFontName(),
render_text->style_ranges()[0].font.GetFontName());
EXPECT_EQ(style.foreground, render_text->style_ranges()[0].foreground);
@@ -38,8 +38,8 @@ TEST_F(RenderTextTest, DefaultStyle) {
TEST_F(RenderTextTest, CustomDefaultStyle) {
// Test a custom default style.
- scoped_ptr<gfx::RenderText> render_text(gfx::RenderText::CreateRenderText());
- gfx::StyleRange color;
+ scoped_ptr<RenderText> render_text(RenderText::CreateRenderText());
+ StyleRange color;
color.foreground = SK_ColorRED;
render_text->set_default_style(color);
render_text->SetText(ASCIIToUTF16("abc"));
@@ -54,7 +54,7 @@ TEST_F(RenderTextTest, CustomDefaultStyle) {
EXPECT_EQ(color.foreground, render_text->style_ranges()[0].foreground);
// Test ApplyDefaultStyle after setting a new default.
- gfx::StyleRange strike;
+ StyleRange strike;
strike.strike = true;
render_text->set_default_style(strike);
render_text->ApplyDefaultStyle();
@@ -64,24 +64,24 @@ TEST_F(RenderTextTest, CustomDefaultStyle) {
}
TEST_F(RenderTextTest, ApplyStyleRange) {
- scoped_ptr<gfx::RenderText> render_text(gfx::RenderText::CreateRenderText());
+ scoped_ptr<RenderText> render_text(RenderText::CreateRenderText());
render_text->SetText(ASCIIToUTF16("01234"));
EXPECT_EQ(1U, render_text->style_ranges().size());
// Test ApplyStyleRange (no-op on empty range).
- gfx::StyleRange empty;
+ StyleRange empty;
empty.range = ui::Range(1, 1);
render_text->ApplyStyleRange(empty);
EXPECT_EQ(1U, render_text->style_ranges().size());
// Test ApplyStyleRange (no-op on invalid range).
- gfx::StyleRange invalid;
+ StyleRange invalid;
invalid.range = ui::Range::InvalidRange();
render_text->ApplyStyleRange(invalid);
EXPECT_EQ(1U, render_text->style_ranges().size());
// Apply a style with a range contained by an existing range.
- gfx::StyleRange underline;
+ StyleRange underline;
underline.underline = true;
underline.range = ui::Range(2, 3);
render_text->ApplyStyleRange(underline);
@@ -94,7 +94,7 @@ TEST_F(RenderTextTest, ApplyStyleRange) {
EXPECT_FALSE(render_text->style_ranges()[2].underline);
// Apply a style with a range equal to another range.
- gfx::StyleRange color;
+ StyleRange color;
color.foreground = SK_ColorWHITE;
color.range = ui::Range(2, 3);
render_text->ApplyStyleRange(color);
@@ -111,7 +111,7 @@ TEST_F(RenderTextTest, ApplyStyleRange) {
// Apply a style with a range containing an existing range.
// This new style also overlaps portions of neighboring ranges.
- gfx::StyleRange strike;
+ StyleRange strike;
strike.strike = true;
strike.range = ui::Range(1, 4);
render_text->ApplyStyleRange(strike);
@@ -124,7 +124,7 @@ TEST_F(RenderTextTest, ApplyStyleRange) {
EXPECT_FALSE(render_text->style_ranges()[2].strike);
// Apply a style overlapping all ranges.
- gfx::StyleRange strike_underline;
+ StyleRange strike_underline;
strike_underline.strike = true;
strike_underline.underline = true;
strike_underline.range = ui::Range(0, render_text->text().length());
@@ -144,7 +144,7 @@ TEST_F(RenderTextTest, ApplyStyleRange) {
TEST_F(RenderTextTest, StyleRangesAdjust) {
// Test that style ranges adjust to the text size.
- scoped_ptr<gfx::RenderText> render_text(gfx::RenderText::CreateRenderText());
+ scoped_ptr<RenderText> render_text(RenderText::CreateRenderText());
render_text->SetText(ASCIIToUTF16("abcdef"));
EXPECT_EQ(1U, render_text->style_ranges().size());
EXPECT_EQ(ui::Range(0, 6), render_text->style_ranges()[0].range);
@@ -155,7 +155,7 @@ TEST_F(RenderTextTest, StyleRangesAdjust) {
EXPECT_EQ(ui::Range(0, 3), render_text->style_ranges()[0].range);
// Test that the last range extends to the length of longer text.
- gfx::StyleRange strike;
+ StyleRange strike;
strike.strike = true;
strike.range = ui::Range(2, 3);
render_text->ApplyStyleRange(strike);
@@ -178,4 +178,313 @@ TEST_F(RenderTextTest, StyleRangesAdjust) {
EXPECT_FALSE(render_text->style_ranges()[0].strike);
}
+void RunMoveCursorLeftRightTest(RenderText* render_text,
+ const std::vector<SelectionModel>& expected,
+ bool move_right) {
+ for (int i = 0; i < static_cast<int>(expected.size()); ++i) {
+ SelectionModel sel = expected[i];
+ EXPECT_TRUE(render_text->selection_model().Equals(sel));
+ if (move_right)
+ render_text->MoveCursorRight(CHARACTER_BREAK, false);
+ else
+ render_text->MoveCursorLeft(CHARACTER_BREAK, false);
+ }
+
+ SelectionModel sel = expected[expected.size() - 1];
+ if (move_right)
+ render_text->MoveCursorRight(LINE_BREAK, false);
+ else
+ render_text->MoveCursorLeft(LINE_BREAK, false);
+ EXPECT_TRUE(render_text->selection_model().Equals(sel));
+}
+
+TEST_F(RenderTextTest, MoveCursorLeftRightInLtr) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateRenderText());
+
+ // Pure LTR.
+ render_text->SetText(ASCIIToUTF16("abc"));
+ // |expected| saves the expected SelectionModel when moving cursor from left
+ // to right.
+ std::vector<SelectionModel> expected;
+ expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(1, 0, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(2, 1, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(3, 2, SelectionModel::TRAILING));
+ // The last element is to test the clamped line ends.
+ expected.push_back(SelectionModel(3, 2, SelectionModel::TRAILING));
+ RunMoveCursorLeftRightTest(render_text.get(), expected, true);
+
+ expected.clear();
+ expected.push_back(SelectionModel(3, 2, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(2, 2, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(1, 1, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING));
+ RunMoveCursorLeftRightTest(render_text.get(), expected, false);
+}
+
+TEST_F(RenderTextTest, MoveCursorLeftRightInLtrRtl) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateRenderText());
+ // LTR-RTL
+ render_text->SetText(WideToUTF16(L"abc\x05d0\x05d1\x05d2"));
+ // The last one is the expected END position.
+ std::vector<SelectionModel> expected;
+ expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(1, 0, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(2, 1, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(3, 2, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(5, 5, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(4, 4, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(3, 3, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(6, 3, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(6, 3, SelectionModel::LEADING));
+ RunMoveCursorLeftRightTest(render_text.get(), expected, true);
+
+ expected.clear();
+ expected.push_back(SelectionModel(6, 3, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(4, 3, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(5, 4, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(6, 5, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(2, 2, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(1, 1, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING));
+ RunMoveCursorLeftRightTest(render_text.get(), expected, false);
+}
+
+TEST_F(RenderTextTest, MoveCursorLeftRightInLtrRtlLtr) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateRenderText());
+ // LTR-RTL-LTR.
+ render_text->SetText(WideToUTF16(L"a"L"\x05d1"L"b"));
+ std::vector<SelectionModel> expected;
+ expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(1, 0, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(1, 1, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(3, 2, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(3, 2, SelectionModel::TRAILING));
+ RunMoveCursorLeftRightTest(render_text.get(), expected, true);
+
+ expected.clear();
+ expected.push_back(SelectionModel(3, 2, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(2, 2, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(2, 1, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING));
+ RunMoveCursorLeftRightTest(render_text.get(), expected, false);
+}
+
+TEST_F(RenderTextTest, MoveCursorLeftRightInRtl) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateRenderText());
+ // Pure RTL.
+ render_text->SetText(WideToUTF16(L"\x05d0\x05d1\x05d2"));
+ render_text->MoveCursorRight(LINE_BREAK, false);
+ std::vector<SelectionModel> expected;
+
+#if defined(OS_LINUX)
+ expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING));
+#else
+ expected.push_back(SelectionModel(3, 0, SelectionModel::LEADING));
+#endif
+ expected.push_back(SelectionModel(1, 0, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(2, 1, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(3, 2, SelectionModel::TRAILING));
+#if defined(OS_LINUX)
+ expected.push_back(SelectionModel(3, 2, SelectionModel::TRAILING));
+#else
+ expected.push_back(SelectionModel(0, 2, SelectionModel::TRAILING));
+ // TODO(xji): expected (0, 2, TRAILING), actual (3, 0, LEADING).
+ // cursor moves from leftmost to rightmost.
+ // expected.push_back(SelectionModel(0, 2, SelectionModel::TRAILING));
+#endif
+ RunMoveCursorLeftRightTest(render_text.get(), expected, false);
+
+ expected.clear();
+
+#if defined(OS_LINUX)
+ expected.push_back(SelectionModel(3, 2, SelectionModel::TRAILING));
+#else
+ expected.push_back(SelectionModel(0, 2, SelectionModel::TRAILING));
+#endif
+ expected.push_back(SelectionModel(2, 2, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(1, 1, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING));
+#if defined(OS_LINUX)
+ expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING));
+#else
+ expected.push_back(SelectionModel(3, 0, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(3, 0, SelectionModel::LEADING));
+#endif
+ RunMoveCursorLeftRightTest(render_text.get(), expected, true);
+}
+
+TEST_F(RenderTextTest, MoveCursorLeftRightInRtlLtr) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateRenderText());
+ // RTL-LTR
+ render_text->SetText(WideToUTF16(L"\x05d0\x05d1\x05d2"L"abc"));
+ render_text->MoveCursorRight(LINE_BREAK, false);
+ std::vector<SelectionModel> expected;
+#if defined(OS_LINUX)
+ expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(1, 0, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(2, 1, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(3, 2, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(5, 5, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(4, 4, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(3, 3, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(6, 3, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(6, 3, SelectionModel::LEADING));
+#else
+ expected.push_back(SelectionModel(6, 5, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(5, 5, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(4, 4, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(3, 3, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(1, 0, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(2, 1, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(3, 2, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(0, 2, SelectionModel::TRAILING));
+ // TODO(xji): expected (0, 2, TRAILING), actual (3, 0, LEADING).
+ // cursor moves from leftmost to middle.
+ // expected.push_back(SelectionModel(0, 2, SelectionModel::TRAILING));
+#endif
+
+ RunMoveCursorLeftRightTest(render_text.get(), expected, false);
+
+ expected.clear();
+#if defined(OS_LINUX)
+ expected.push_back(SelectionModel(6, 3, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(4, 3, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(5, 4, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(6, 5, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(2, 2, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(1, 1, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING));
+#else
+ expected.push_back(SelectionModel(0, 2, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(2, 2, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(1, 1, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(4, 3, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(5, 4, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(6, 5, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(6, 5, SelectionModel::TRAILING));
+#endif
+ RunMoveCursorLeftRightTest(render_text.get(), expected, true);
+}
+
+TEST_F(RenderTextTest, MoveCursorLeftRightInRtlLtrRtl) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateRenderText());
+ // RTL-LTR-RTL.
+ render_text->SetText(WideToUTF16(L"\x05d0"L"a"L"\x05d1"));
+ render_text->MoveCursorRight(LINE_BREAK, false);
+ std::vector<SelectionModel> expected;
+#if defined(OS_LINUX)
+ expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(1, 0, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(1, 1, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(3, 2, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(3, 2, SelectionModel::TRAILING));
+#else
+ expected.push_back(SelectionModel(3, 2, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(3, 2, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(1, 1, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(1, 0, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(0, 0, SelectionModel::TRAILING));
+ // TODO(xji): expected (0, 0, TRAILING), actual (2, 1, LEADING).
+ // cursor moves from leftmost to middle.
+ // expected.push_back(SelectionModel(0, 0, SelectionModel::TRAILING));
+#endif
+ RunMoveCursorLeftRightTest(render_text.get(), expected, false);
+
+ expected.clear();
+#if defined(OS_LINUX)
+ expected.push_back(SelectionModel(3, 2, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(2, 2, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(2, 1, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING));
+#else
+ expected.push_back(SelectionModel(0, 0, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(2, 1, SelectionModel::TRAILING));
+ expected.push_back(SelectionModel(2, 2, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(3, 2, SelectionModel::LEADING));
+ expected.push_back(SelectionModel(3, 2, SelectionModel::LEADING));
+#endif
+ RunMoveCursorLeftRightTest(render_text.get(), expected, true);
+}
+
+// TODO(xji): temporarily disable in platform Win since the complex script
+// characters turned into empty square due to font regression. So, not able
+// to test 2 characters belong to the same grapheme.
+#if defined(OS_LINUX)
+TEST_F(RenderTextTest, MoveCursorLeftRight_ComplexScript) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateRenderText());
+
+ render_text->SetText(WideToUTF16(L"\x0915\x093f\x0915\x094d\x0915"));
+ EXPECT_EQ(0U, render_text->GetCursorPosition());
+ render_text->MoveCursorRight(CHARACTER_BREAK, false);
+ EXPECT_EQ(2U, render_text->GetCursorPosition());
+ render_text->MoveCursorRight(CHARACTER_BREAK, false);
+ EXPECT_EQ(4U, render_text->GetCursorPosition());
+ render_text->MoveCursorRight(CHARACTER_BREAK, false);
+ EXPECT_EQ(5U, render_text->GetCursorPosition());
+ render_text->MoveCursorRight(CHARACTER_BREAK, false);
+ EXPECT_EQ(5U, render_text->GetCursorPosition());
+
+ render_text->MoveCursorLeft(CHARACTER_BREAK, false);
+ EXPECT_EQ(4U, render_text->GetCursorPosition());
+ render_text->MoveCursorLeft(CHARACTER_BREAK, false);
+ EXPECT_EQ(2U, render_text->GetCursorPosition());
+ render_text->MoveCursorLeft(CHARACTER_BREAK, false);
+ EXPECT_EQ(0U, render_text->GetCursorPosition());
+ render_text->MoveCursorLeft(CHARACTER_BREAK, false);
+ EXPECT_EQ(0U, render_text->GetCursorPosition());
+}
+#endif
+
+TEST_F(RenderTextTest, MoveCursorLeftRightWithSelection) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateRenderText());
+ render_text->SetText(WideToUTF16(L"abc\x05d0\x05d1\x05d2"));
+ // Left arrow on select ranging (6, 4).
+ render_text->MoveCursorRight(LINE_BREAK, false);
+ EXPECT_EQ(6U, render_text->GetCursorPosition());
+ render_text->MoveCursorLeft(CHARACTER_BREAK, false);
+ EXPECT_EQ(4U, render_text->GetCursorPosition());
+ render_text->MoveCursorLeft(CHARACTER_BREAK, false);
+ EXPECT_EQ(5U, render_text->GetCursorPosition());
+ render_text->MoveCursorLeft(CHARACTER_BREAK, false);
+ EXPECT_EQ(6U, render_text->GetCursorPosition());
+ render_text->MoveCursorRight(CHARACTER_BREAK, true);
+ EXPECT_EQ(6U, render_text->GetSelectionStart());
+ EXPECT_EQ(5U, render_text->GetCursorPosition());
+ render_text->MoveCursorRight(CHARACTER_BREAK, true);
+ EXPECT_EQ(6U, render_text->GetSelectionStart());
+ EXPECT_EQ(4U, render_text->GetCursorPosition());
+ render_text->MoveCursorLeft(CHARACTER_BREAK, false);
+ EXPECT_EQ(6U, render_text->GetCursorPosition());
+
+ // Right arrow on select ranging (4, 6).
+ render_text->MoveCursorLeft(LINE_BREAK, false);
+ EXPECT_EQ(0U, render_text->GetCursorPosition());
+ render_text->MoveCursorRight(CHARACTER_BREAK, false);
+ EXPECT_EQ(1U, render_text->GetCursorPosition());
+ render_text->MoveCursorRight(CHARACTER_BREAK, false);
+ EXPECT_EQ(2U, render_text->GetCursorPosition());
+ render_text->MoveCursorRight(CHARACTER_BREAK, false);
+ EXPECT_EQ(3U, render_text->GetCursorPosition());
+ render_text->MoveCursorRight(CHARACTER_BREAK, false);
+ EXPECT_EQ(5U, render_text->GetCursorPosition());
+ render_text->MoveCursorRight(CHARACTER_BREAK, false);
+ EXPECT_EQ(4U, render_text->GetCursorPosition());
+ render_text->MoveCursorLeft(CHARACTER_BREAK, true);
+ EXPECT_EQ(4U, render_text->GetSelectionStart());
+ EXPECT_EQ(5U, render_text->GetCursorPosition());
+ render_text->MoveCursorLeft(CHARACTER_BREAK, true);
+ EXPECT_EQ(4U, render_text->GetSelectionStart());
+ EXPECT_EQ(6U, render_text->GetCursorPosition());
+ render_text->MoveCursorRight(CHARACTER_BREAK, false);
+ EXPECT_EQ(4U, render_text->GetCursorPosition());
+}
+
} // namespace gfx
diff --git a/ui/gfx/render_text_win.cc b/ui/gfx/render_text_win.cc
index c2c9acf..f6b983d 100644
--- a/ui/gfx/render_text_win.cc
+++ b/ui/gfx/render_text_win.cc
@@ -237,10 +237,6 @@ SelectionModel RenderTextWin::RightEndSelectionModel() {
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) {
ui::Range range(from, to);
DCHECK(ui::Range(0, text().length()).Contains(range));
@@ -295,6 +291,41 @@ std::vector<Rect> RenderTextWin::GetSubstringBounds(size_t from, size_t to) {
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() {
text_is_dirty_ = false;
STLDeleteContainerPointers(runs_.begin(), runs_.end());
@@ -461,34 +492,16 @@ size_t RenderTextWin::GetRunContainingPoint(const Point& point) const {
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(
- internal::TextRun* run) const {
+ internal::TextRun* run) {
size_t caret = run->range.start();
size_t cursor = IndexOfAdjacentGrapheme(caret, true);
return SelectionModel(cursor, caret, SelectionModel::TRAILING);
}
SelectionModel RenderTextWin::LastSelectionModelInsideRun(
- internal::TextRun* run) const {
+ internal::TextRun* run) {
size_t caret = IndexOfAdjacentGrapheme(run->range.end(), false);
return SelectionModel(caret, caret, SelectionModel::LEADING);
}
diff --git a/ui/gfx/render_text_win.h b/ui/gfx/render_text_win.h
index d812f50..53eea69 100644
--- a/ui/gfx/render_text_win.h
+++ b/ui/gfx/render_text_win.h
@@ -72,10 +72,12 @@ class RenderTextWin : public RenderText {
BreakType break_type) OVERRIDE;
virtual SelectionModel LeftEndSelectionModel() 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 bool IsCursorablePosition(size_t position) OVERRIDE;
private:
+ virtual size_t IndexOfAdjacentGrapheme(size_t index, bool next) OVERRIDE;
+
void ItemizeLogicalText();
void LayoutVisualText(HDC hdc);
@@ -84,15 +86,11 @@ class RenderTextWin : public RenderText {
size_t GetRunContainingPosition(size_t position) 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
// or last caret position inside (not at a boundary of) the run.
// The returned value represents a cursor/caret position without a selection.
- SelectionModel FirstSelectionModelInsideRun(internal::TextRun*) const;
- SelectionModel LastSelectionModelInsideRun(internal::TextRun*) const;
+ SelectionModel FirstSelectionModelInsideRun(internal::TextRun*);
+ SelectionModel LastSelectionModelInsideRun(internal::TextRun*);
// Get the selection model visually left/right of |selection| by one grapheme.
// The returned value represents a cursor/caret position without a selection.
diff --git a/views/controls/textfield/native_textfield_views_unittest.cc b/views/controls/textfield/native_textfield_views_unittest.cc
index ea8e023..f75c2b7 100644
--- a/views/controls/textfield/native_textfield_views_unittest.cc
+++ b/views/controls/textfield/native_textfield_views_unittest.cc
@@ -2,6 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include <string>
+#include <vector>
+
#include "base/auto_reset.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
@@ -15,6 +18,7 @@
#include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "ui/base/dragdrop/drag_drop_types.h"
#include "ui/base/keycodes/keyboard_codes.h"
+#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/render_text.h"
#include "views/controls/textfield/native_textfield_views.h"
#include "views/controls/textfield/textfield.h"
@@ -88,6 +92,8 @@ class GetTextHelper {
DISALLOW_COPY_AND_ASSIGN(GetTextHelper);
};
+const char16 kHebrewLetterSamekh = 0x05E1;
+
} // namespace
namespace views {
@@ -215,6 +221,19 @@ class NativeTextfieldViewsTest : public ViewsTestBase,
SendKeyEvent(key_code, false, false);
}
+ void SendKeyEvent(char16 ch) {
+ if (ch < 0x80) {
+ ui::KeyboardCode code =
+ ch == ' ' ? ui::VKEY_SPACE :
+ static_cast<ui::KeyboardCode>(ui::VKEY_A + ch - 'a');
+ SendKeyEvent(code);
+ } else {
+ KeyEvent event(ui::ET_KEY_PRESSED, ui::VKEY_UNKNOWN, 0);
+ event.set_character(ch);
+ input_method_->DispatchKeyEvent(event);
+ }
+ }
+
View* GetFocusedView() {
return widget_->GetFocusManager()->GetFocusedView();
}
@@ -225,6 +244,45 @@ class NativeTextfieldViewsTest : public ViewsTestBase,
gfx::SelectionModel(cursor_pos), false).x();
}
+ // Get the current cursor bounds.
+ gfx::Rect GetCursorBounds() {
+ gfx::RenderText* render_text = textfield_view_->GetRenderText();
+ gfx::Rect bounds = render_text->GetUpdatedCursorBounds();
+ return bounds;
+ }
+
+ // Get the cursor bounds of |sel|.
+ gfx::Rect GetCursorBounds(const gfx::SelectionModel& sel) {
+ gfx::RenderText* render_text = textfield_view_->GetRenderText();
+ gfx::Rect bounds = render_text->GetCursorBounds(sel, true);
+ return bounds;
+ }
+
+ gfx::Rect GetDisplayRect() {
+ return textfield_view_->GetRenderText()->display_rect();
+ }
+
+ // Mouse click on the point whose x-axis is |bound|'s x plus |x_offset| and
+ // y-axis is in the middle of |bound|'s vertical range.
+ void MouseClick(const gfx::Rect bound, int x_offset) {
+ int x = bound.x() + x_offset;
+ int y = bound.y() + bound.height() / 2;
+ MouseEvent click(ui::ET_MOUSE_PRESSED, x, y, ui::EF_LEFT_BUTTON_DOWN);
+ textfield_view_->OnMousePressed(click);
+ MouseEvent release(ui::ET_MOUSE_RELEASED, x, y, ui::EF_LEFT_BUTTON_DOWN);
+ textfield_view_->OnMouseReleased(release);
+ }
+
+ // This is to avoid double/triple click.
+ void NonClientMouseClick() {
+ MouseEvent click(ui::ET_MOUSE_PRESSED, 0, 0,
+ ui::EF_LEFT_BUTTON_DOWN | ui::EF_IS_NON_CLIENT);
+ textfield_view_->OnMousePressed(click);
+ MouseEvent release(ui::ET_MOUSE_RELEASED, 0, 0,
+ ui::EF_LEFT_BUTTON_DOWN | ui::EF_IS_NON_CLIENT);
+ textfield_view_->OnMouseReleased(release);
+ }
+
// Wrap for visibility in test classes.
ui::TextInputType GetTextInputType() {
return textfield_view_->GetTextInputType();
@@ -1141,4 +1199,340 @@ TEST_F(NativeTextfieldViewsTest, UndoRedoTest) {
EXPECT_STR_EQ("ab3", textfield_->text());
}
+TEST_F(NativeTextfieldViewsTest, TextCursorDisplayTest) {
+ InitTextfield(Textfield::STYLE_DEFAULT);
+ // LTR-RTL string in LTR context.
+ SendKeyEvent('a');
+ EXPECT_STR_EQ("a", textfield_->text());
+ int x = GetCursorBounds().x();
+ int prev_x = x;
+
+ SendKeyEvent('b');
+ EXPECT_STR_EQ("ab", textfield_->text());
+ x = GetCursorBounds().x();
+ EXPECT_LT(prev_x, x);
+ prev_x = x;
+
+ SendKeyEvent(0x05E1);
+ EXPECT_EQ(WideToUTF16(L"ab\x05E1"), textfield_->text());
+ x = GetCursorBounds().x();
+ EXPECT_EQ(prev_x, x);
+
+ SendKeyEvent(0x05E2);
+ EXPECT_EQ(WideToUTF16(L"ab\x05E1\x5E2"), textfield_->text());
+ x = GetCursorBounds().x();
+ EXPECT_EQ(prev_x, x);
+
+ // Clear text.
+ SendKeyEvent(ui::VKEY_A, false, true);
+ SendKeyEvent('\n');
+
+ // RTL-LTR string in LTR context.
+ SendKeyEvent(0x05E1);
+ EXPECT_EQ(WideToUTF16(L"\x05E1"), textfield_->text());
+ x = GetCursorBounds().x();
+ EXPECT_EQ(GetDisplayRect().x(), x);
+ prev_x = x;
+
+ SendKeyEvent(0x05E2);
+ EXPECT_EQ(WideToUTF16(L"\x05E1\x05E2"), textfield_->text());
+ x = GetCursorBounds().x();
+ EXPECT_EQ(prev_x, x);
+
+ SendKeyEvent('a');
+ EXPECT_EQ(WideToUTF16(L"\x05E1\x5E2"L"a"), textfield_->text());
+ x = GetCursorBounds().x();
+ EXPECT_LT(prev_x, x);
+ prev_x = x;
+
+ SendKeyEvent('b');
+ EXPECT_EQ(WideToUTF16(L"\x05E1\x5E2"L"ab"), textfield_->text());
+ x = GetCursorBounds().x();
+ EXPECT_LT(prev_x, x);
+}
+
+TEST_F(NativeTextfieldViewsTest, TextCursorDisplayInRTLTest) {
+ std::string locale = l10n_util::GetApplicationLocale("");
+ base::i18n::SetICUDefaultLocale("he");
+
+ InitTextfield(Textfield::STYLE_DEFAULT);
+ // LTR-RTL string in RTL context.
+ SendKeyEvent('a');
+ EXPECT_STR_EQ("a", textfield_->text());
+ int x = GetCursorBounds().x();
+ EXPECT_EQ(GetDisplayRect().right() - 1, x);
+ int prev_x = x;
+
+ SendKeyEvent('b');
+ EXPECT_STR_EQ("ab", textfield_->text());
+ x = GetCursorBounds().x();
+ EXPECT_EQ(prev_x, x);
+
+ SendKeyEvent(0x05E1);
+ EXPECT_EQ(WideToUTF16(L"ab\x05E1"), textfield_->text());
+ x = GetCursorBounds().x();
+ EXPECT_GT(prev_x, x);
+ prev_x = x;
+
+ SendKeyEvent(0x05E2);
+ EXPECT_EQ(WideToUTF16(L"ab\x05E1\x5E2"), textfield_->text());
+ x = GetCursorBounds().x();
+ EXPECT_GT(prev_x, x);
+
+ SendKeyEvent(ui::VKEY_A, false, true);
+ SendKeyEvent('\n');
+
+ // RTL-LTR string in RTL context.
+ SendKeyEvent(0x05E1);
+ EXPECT_EQ(WideToUTF16(L"\x05E1"), textfield_->text());
+ x = GetCursorBounds().x();
+ prev_x = x;
+
+ SendKeyEvent(0x05E2);
+ EXPECT_EQ(WideToUTF16(L"\x05E1\x05E2"), textfield_->text());
+ x = GetCursorBounds().x();
+ EXPECT_GT(prev_x, x);
+ prev_x = x;
+
+ SendKeyEvent('a');
+ EXPECT_EQ(WideToUTF16(L"\x05E1\x5E2"L"a"), textfield_->text());
+ x = GetCursorBounds().x();
+#if defined(OS_WIN)
+ // In Windows, the text is always in LTR directionality even in RTL UI.
+ // TODO(xji): it should change if we fix the directionality in Window's
+ // NativeTextfieldViews
+ EXPECT_LT(prev_x, x);
+#else
+ EXPECT_EQ(prev_x, x);
+#endif
+ prev_x = x;
+
+ SendKeyEvent('b');
+ EXPECT_EQ(WideToUTF16(L"\x05E1\x5E2"L"ab"), textfield_->text());
+ x = GetCursorBounds().x();
+ EXPECT_EQ(prev_x, x);
+
+ // Reset locale.
+ base::i18n::SetICUDefaultLocale(locale);
+}
+
+TEST_F(NativeTextfieldViewsTest, HitInsideTextAreaTest) {
+ InitTextfield(Textfield::STYLE_DEFAULT);
+ textfield_->SetText(WideToUTF16(L"ab\x05E1\x5E2"));
+ std::vector<gfx::Rect> cursor_bounds;
+
+ // Save each cursor bound.
+ gfx::SelectionModel sel(0, 0, gfx::SelectionModel::LEADING);
+ cursor_bounds.push_back(GetCursorBounds(sel));
+
+ sel = gfx::SelectionModel(1, 0, gfx::SelectionModel::TRAILING);
+ gfx::Rect bound = GetCursorBounds(sel);
+ sel = gfx::SelectionModel(1, 1, gfx::SelectionModel::LEADING);
+ EXPECT_EQ(bound, GetCursorBounds(sel));
+ cursor_bounds.push_back(bound);
+
+ sel = gfx::SelectionModel(2, 1, gfx::SelectionModel::TRAILING);
+ bound = GetCursorBounds(sel);
+ sel = gfx::SelectionModel(4, 3, gfx::SelectionModel::TRAILING);
+ EXPECT_EQ(bound, GetCursorBounds(sel));
+ cursor_bounds.push_back(bound);
+
+ sel = gfx::SelectionModel(3, 2, gfx::SelectionModel::TRAILING);
+ bound = GetCursorBounds(sel);
+ sel = gfx::SelectionModel(3, 3, gfx::SelectionModel::LEADING);
+ EXPECT_EQ(bound, GetCursorBounds(sel));
+ cursor_bounds.push_back(bound);
+
+ sel = gfx::SelectionModel(2, 2, gfx::SelectionModel::LEADING);
+ bound = GetCursorBounds(sel);
+ sel = gfx::SelectionModel(4, 2, gfx::SelectionModel::LEADING);
+ EXPECT_EQ(bound, GetCursorBounds(sel));
+ cursor_bounds.push_back(bound);
+
+ // Expected cursor position when clicking left and right of each character.
+ size_t cursor_pos_expected[] = {0, 1, 1, 2, 4, 3, 3, 2};
+
+ int index = 0;
+ for (int i = 0; i < static_cast<int>(cursor_bounds.size() - 1); ++i) {
+ int half_width = (cursor_bounds[i + 1].x() - cursor_bounds[i].x()) / 2;
+ MouseClick(cursor_bounds[i], half_width / 2);
+ EXPECT_EQ(cursor_pos_expected[index++], textfield_->GetCursorPosition());
+
+ // To avoid trigger double click. Not using sleep() since it takes longer
+ // for the test to run if using sleep().
+ NonClientMouseClick();
+
+ MouseClick(cursor_bounds[i + 1], - (half_width / 2));
+ EXPECT_EQ(cursor_pos_expected[index++], textfield_->GetCursorPosition());
+
+ NonClientMouseClick();
+ }
+}
+
+TEST_F(NativeTextfieldViewsTest, HitOutsideTextAreaTest) {
+ InitTextfield(Textfield::STYLE_DEFAULT);
+
+ // LTR-RTL string in LTR context.
+ textfield_->SetText(WideToUTF16(L"ab\x05E1\x5E2"));
+
+ SendKeyEvent(ui::VKEY_HOME);
+ gfx::Rect bound = GetCursorBounds();
+ MouseClick(bound, -10);
+ EXPECT_EQ(bound, GetCursorBounds());
+
+ SendKeyEvent(ui::VKEY_END);
+ bound = GetCursorBounds();
+ MouseClick(bound, 10);
+ EXPECT_EQ(bound, GetCursorBounds());
+
+ NonClientMouseClick();
+
+ // RTL-LTR string in LTR context.
+ textfield_->SetText(WideToUTF16(L"\x05E1\x5E2"L"ab"));
+
+ SendKeyEvent(ui::VKEY_HOME);
+ bound = GetCursorBounds();
+#if defined(OS_WIN)
+ MouseClick(bound, -10);
+#else
+ MouseClick(bound, 10);
+#endif
+ EXPECT_EQ(bound, GetCursorBounds());
+
+ SendKeyEvent(ui::VKEY_END);
+ bound = GetCursorBounds();
+#if defined(OS_WIN)
+ MouseClick(bound, 10);
+#else
+ MouseClick(bound, -10);
+#endif
+ EXPECT_EQ(bound, GetCursorBounds());
+}
+
+TEST_F(NativeTextfieldViewsTest, HitOutsideTextAreaInRTLTest) {
+ std::string locale = l10n_util::GetApplicationLocale("");
+ base::i18n::SetICUDefaultLocale("he");
+
+ InitTextfield(Textfield::STYLE_DEFAULT);
+
+ // RTL-LTR string in RTL context.
+ textfield_->SetText(WideToUTF16(L"\x05E1\x5E2"L"ab"));
+ SendKeyEvent(ui::VKEY_HOME);
+ gfx::Rect bound = GetCursorBounds();
+ MouseClick(bound, 10);
+ EXPECT_EQ(bound, GetCursorBounds());
+
+ SendKeyEvent(ui::VKEY_END);
+ bound = GetCursorBounds();
+ MouseClick(bound, -10);
+ EXPECT_EQ(bound, GetCursorBounds());
+
+ NonClientMouseClick();
+
+ // LTR-RTL string in RTL context.
+ textfield_->SetText(WideToUTF16(L"ab\x05E1\x5E2"));
+ SendKeyEvent(ui::VKEY_HOME);
+ bound = GetCursorBounds();
+#if defined(OS_WIN)
+ MouseClick(bound, 10);
+#else
+ MouseClick(bound, -10);
+#endif
+ EXPECT_EQ(bound, GetCursorBounds());
+
+ SendKeyEvent(ui::VKEY_END);
+ bound = GetCursorBounds();
+#if defined(OS_WIN)
+ MouseClick(bound, -10);
+#else
+ MouseClick(bound, 10);
+#endif
+ EXPECT_EQ(bound, GetCursorBounds());
+
+ // Reset locale.
+ base::i18n::SetICUDefaultLocale(locale);
+}
+
+// This verifies that |bound| is contained by |display|. |bound|'s right edge
+// must be less than |diaplay|'s right edge.
+void OverflowCursorBoundTestVerifier(const gfx::Rect& display,
+ const gfx::Rect& bound) {
+ EXPECT_LE(display.x(), bound.x());
+ EXPECT_GT(display.right(), bound.right());
+ EXPECT_LE(display.y(), bound.y());
+ EXPECT_GE(display.bottom(), bound.bottom());
+}
+
+TEST_F(NativeTextfieldViewsTest, OverflowTest) {
+ InitTextfield(Textfield::STYLE_DEFAULT);
+
+ string16 str;
+ for (int i = 0; i < 500; ++i)
+ SendKeyEvent('a');
+ SendKeyEvent(kHebrewLetterSamekh);
+ gfx::Rect bound = GetCursorBounds();
+ gfx::Rect display = GetDisplayRect();
+ OverflowCursorBoundTestVerifier(display, bound);
+
+ // Test mouse pointing.
+ MouseClick(bound, -1);
+ EXPECT_EQ(500U, textfield_->GetCursorPosition());
+
+ // Clear text.
+ SendKeyEvent(ui::VKEY_A, false, true);
+ SendKeyEvent('\n');
+
+ for (int i = 0; i < 500; ++i)
+ SendKeyEvent(kHebrewLetterSamekh);
+ SendKeyEvent('a');
+ bound = GetCursorBounds();
+ display = GetDisplayRect();
+ OverflowCursorBoundTestVerifier(display, bound);
+
+ MouseClick(bound, -1);
+ EXPECT_EQ(501U, textfield_->GetCursorPosition());
+}
+
+TEST_F(NativeTextfieldViewsTest, OverflowInRTLTest) {
+ std::string locale = l10n_util::GetApplicationLocale("");
+ base::i18n::SetICUDefaultLocale("he");
+
+ InitTextfield(Textfield::STYLE_DEFAULT);
+
+ string16 str;
+ for (int i = 0; i < 500; ++i)
+ SendKeyEvent('a');
+ SendKeyEvent(kHebrewLetterSamekh);
+ gfx::Rect bound = GetCursorBounds();
+ gfx::Rect display = GetDisplayRect();
+ OverflowCursorBoundTestVerifier(display, bound);
+
+ MouseClick(bound, 1);
+ EXPECT_EQ(501U, textfield_->GetCursorPosition());
+
+ // Clear text.
+ SendKeyEvent(ui::VKEY_A, false, true);
+ SendKeyEvent('\n');
+
+ for (int i = 0; i < 500; ++i)
+ SendKeyEvent(kHebrewLetterSamekh);
+ SendKeyEvent('a');
+ bound = GetCursorBounds();
+ display = GetDisplayRect();
+ OverflowCursorBoundTestVerifier(display, bound);
+
+ MouseClick(bound, 1);
+#if defined(OS_WIN)
+ // In Windows, the text is always in LTR directionality even in RTL UI.
+ // TODO(xji): it should change if we fix the directionality in Window's
+ // NativeTextfieldViews
+ EXPECT_EQ(0U, textfield_->GetCursorPosition());
+#else
+ EXPECT_EQ(500U, textfield_->GetCursorPosition());
+#endif
+
+ // Reset locale.
+ base::i18n::SetICUDefaultLocale(locale);
+}
+
} // namespace views
diff --git a/views/controls/textfield/textfield_views_model.cc b/views/controls/textfield/textfield_views_model.cc
index ea1825d..dec5b2e 100644
--- a/views/controls/textfield/textfield_views_model.cc
+++ b/views/controls/textfield/textfield_views_model.cc
@@ -320,7 +320,10 @@ void TextfieldViewsModel::Append(const string16& text) {
if (HasCompositionText())
ConfirmCompositionText();
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);
render_text_->SetCursorPosition(save);
ClearSelection();
@@ -338,7 +341,9 @@ bool TextfieldViewsModel::Delete() {
}
if (GetText().length() > 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 false;
@@ -656,7 +661,7 @@ void TextfieldViewsModel::ReplaceTextInternal(const string16& text,
} else if (!HasSelection()) {
size_t cursor = GetCursorPosition();
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);
}
// Edit history is recorded in InsertText.
diff --git a/views/controls/textfield/textfield_views_model_unittest.cc b/views/controls/textfield/textfield_views_model_unittest.cc
index 5169231..e9b6218 100644
--- a/views/controls/textfield/textfield_views_model_unittest.cc
+++ b/views/controls/textfield/textfield_views_model_unittest.cc
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include <vector>
+
#include "base/auto_reset.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop.h"
@@ -17,6 +19,20 @@
#include "views/test/views_test_base.h"
#include "views/views_delegate.h"
+namespace {
+
+struct WordAndCursor {
+ WordAndCursor(const wchar_t* w, size_t c)
+ : word(w),
+ cursor(c) {
+ }
+
+ const wchar_t* word;
+ size_t cursor;
+};
+
+} // namespace
+
namespace views {
class TextfieldViewsModelTest : public ViewsTestBase,
@@ -83,6 +99,144 @@ TEST_F(TextfieldViewsModelTest, EditString) {
// but backspace should work.
EXPECT_TRUE(model.Backspace());
EXPECT_STR_EQ("HELLORL", model.GetText());
+
+ model.MoveCursorTo(gfx::SelectionModel(5));
+ model.ReplaceText(ASCIIToUTF16(" WOR"));
+ EXPECT_STR_EQ("HELLO WORL", model.GetText());
+}
+
+TEST_F(TextfieldViewsModelTest, EditString_SimpleRTL) {
+ TextfieldViewsModel model(NULL);
+ // Append two strings.
+ model.Append(WideToUTF16(L"\x05d0\x05d1\x05d2"));
+ EXPECT_EQ(WideToUTF16(L"\x05d0\x05d1\x05d2"), model.GetText());
+ model.Append(WideToUTF16(L"\x05e0\x05e1\x05e2"));
+ EXPECT_EQ(WideToUTF16(L"\x05d0\x05d1\x05d2\x05e0\x05e1\x05e2"),
+ model.GetText());
+
+ // Insert 0x05f0.
+ model.MoveCursorTo(gfx::SelectionModel(1U));
+ model.InsertChar(0x05f0);
+ EXPECT_EQ(WideToUTF16(L"\x05d0\x05f0\x05d1\x05d2\x05e0\x05e1\x05e2"),
+ model.GetText());
+
+ // Replace "\x05d1" with "\x05f1".
+ model.ReplaceChar(0x05f1);
+ EXPECT_EQ(WideToUTF16(L"\x05d0\x05f0\x5f1\x05d2\x05e0\x05e1\x05e2"),
+ model.GetText());
+
+ // Delete and backspace.
+ EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Delete());
+ EXPECT_EQ(WideToUTF16(L"\x05d0\x05f0\x5f1\x05e0\x05e1\x05e2"),
+ model.GetText());
+ EXPECT_TRUE(model.Backspace());
+ EXPECT_EQ(2U, model.GetCursorPosition());
+ EXPECT_EQ(WideToUTF16(L"\x05d0\x05f0\x05e0\x05e1\x05e2"), model.GetText());
+}
+
+TEST_F(TextfieldViewsModelTest, EditString_ComplexScript) {
+ TextfieldViewsModel model(NULL);
+ // Append two Hindi strings.
+ model.Append(WideToUTF16(L"\x0915\x093f\x0915\x094d\x0915"));
+ EXPECT_EQ(WideToUTF16(L"\x0915\x093f\x0915\x094d\x0915"),
+ model.GetText());
+ model.Append(WideToUTF16(L"\x0915\x094d\x092e\x094d"));
+ EXPECT_EQ(WideToUTF16(
+ L"\x0915\x093f\x0915\x094d\x0915\x0915\x094d\x092e\x094d"),
+ model.GetText());
+
+ // Check it is not able to place cursor in middle of a grapheme.
+ // TODO(xji): temporarily disable in platform Win since the complex script
+ // characters turned into empty square due to font regression. So, not able
+ // to test 2 characters belong to the same grapheme.
+#if defined(OS_LINUX)
+ model.MoveCursorTo(gfx::SelectionModel(1U));
+ EXPECT_EQ(0U, model.GetCursorPosition());
+#endif
+
+ model.MoveCursorTo(gfx::SelectionModel(2U));
+ EXPECT_EQ(2U, model.GetCursorPosition());
+ model.InsertChar('a');
+ EXPECT_EQ(WideToUTF16(
+ L"\x0915\x093f\x0061\x0915\x094d\x0915\x0915\x094d\x092e\x094d"),
+ model.GetText());
+
+ // ReplaceChar will replace the whole grapheme.
+ model.ReplaceChar('b');
+ // TODO(xji): temporarily disable in platform Win since the complex script
+ // characters turned into empty square due to font regression. So, not able
+ // to test 2 characters belong to the same grapheme.
+#if defined(OS_LINUX)
+ EXPECT_EQ(WideToUTF16(
+ L"\x0915\x093f\x0061\x0062\x0915\x0915\x094d\x092e\x094d"),
+ model.GetText());
+#endif
+ EXPECT_EQ(4U, model.GetCursorPosition());
+
+ // Delete should delete the whole grapheme.
+ model.MoveCursorTo(gfx::SelectionModel(0U));
+ // TODO(xji): temporarily disable in platform Win since the complex script
+ // characters turned into empty square due to font regression. So, not able
+ // to test 2 characters belong to the same grapheme.
+#if defined(OS_LINUX)
+ EXPECT_TRUE(model.Delete());
+ EXPECT_EQ(WideToUTF16(L"\x0061\x0062\x0915\x0915\x094d\x092e\x094d"),
+ model.GetText());
+ model.MoveCursorTo(gfx::SelectionModel(model.GetText().length()));
+ EXPECT_TRUE(model.Backspace());
+ EXPECT_EQ(WideToUTF16(L"\x0061\x0062\x0915\x0915\x094d\x092e"),
+ model.GetText());
+#endif
+
+ // Test cursor position and deletion for Hindi Virama.
+ model.SetText(WideToUTF16(L"\x0D38\x0D4D\x0D15\x0D16\x0D2E"));
+ model.MoveCursorTo(gfx::SelectionModel(0));
+ EXPECT_EQ(0U, model.GetCursorPosition());
+
+ // TODO(xji): temporarily disable in platform Win since the complex script
+ // characters turned into empty square due to font regression. So, not able
+ // to test 2 characters belong to the same grapheme.
+#if defined(OS_LINUX)
+ model.MoveCursorTo(gfx::SelectionModel(1));
+ EXPECT_EQ(0U, model.GetCursorPosition());
+#endif
+
+ model.MoveCursorTo(gfx::SelectionModel(2));
+ EXPECT_EQ(2U, model.GetCursorPosition());
+
+ model.MoveCursorTo(gfx::SelectionModel(3));
+ EXPECT_EQ(3U, model.GetCursorPosition());
+
+ model.MoveCursorTo(gfx::SelectionModel(2));
+
+ EXPECT_TRUE(model.Backspace());
+ EXPECT_EQ(WideToUTF16(L"\x0D38\x0D15\x0D16\x0D2E"), model.GetText());
+
+ // Test Delete/Backspace on Hebrew with non-spacing marks.
+ // TODO(xji): temporarily disable in platform Win since the complex script
+ // characters turned into empty square due to font regression. So, not able
+ // to test 2 characters belong to the same grapheme.
+#if defined(OS_LINUX)
+ model.SetText(WideToUTF16(L"\x05d5\x05b7\x05D9\x05B0\x05D4\x05B4\x05D9"));
+ model.MoveCursorTo(gfx::SelectionModel(0));
+ EXPECT_TRUE(model.Delete());
+ EXPECT_TRUE(model.Delete());
+ EXPECT_TRUE(model.Delete());
+ EXPECT_TRUE(model.Delete());
+ EXPECT_EQ(WideToUTF16(L""), model.GetText());
+#endif
+
+ // The first 2 characters are not strong directionality characters.
+ model.SetText(WideToUTF16(L"\x002C\x0020\x05D1\x05BC\x05B7\x05E9\x05BC"));
+#if defined(OS_WIN)
+ model.MoveCursorRight(gfx::LINE_BREAK, false);
+#else
+ model.MoveCursorLeft(gfx::LINE_BREAK, false);
+#endif
+ EXPECT_TRUE(model.Backspace());
+ EXPECT_EQ(WideToUTF16(L"\x002C\x0020\x05D1\x05BC\x05B7\x05E9"),
+ model.GetText());
}
TEST_F(TextfieldViewsModelTest, EmptyString) {
@@ -143,6 +297,93 @@ TEST_F(TextfieldViewsModelTest, Selection) {
EXPECT_EQ(5U, model.GetCursorPosition());
}
+TEST_F(TextfieldViewsModelTest, Selection_BidiWithNonSpacingMarks) {
+ // Selection is a logical operation. And it should work with the arrow
+ // keys doing visual movements, while the selection is logical between
+ // the (logical) start and end points. Selection is simply defined as
+ // the portion of text between the logical positions of the start and end
+ // caret positions.
+ TextfieldViewsModel model(NULL);
+ // TODO(xji): temporarily disable in platform Win since the complex script
+ // characters turned into empty square due to font regression. So, not able
+ // to test 2 characters belong to the same grapheme.
+#if defined(OS_LINUX)
+ model.Append(WideToUTF16(
+ L"abc\x05E9\x05BC\x05C1\x05B8\x05E0\x05B8"L"def"));
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, false);
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, false);
+
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, true);
+ EXPECT_EQ(2U, model.render_text()->GetSelectionStart());
+ EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_EQ(WideToUTF16(L"c"), model.GetSelectedText());
+
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, true);
+ EXPECT_EQ(2U, model.render_text()->GetSelectionStart());
+ EXPECT_EQ(7U, model.GetCursorPosition());
+ EXPECT_EQ(WideToUTF16(L"c\x05E9\x05BC\x05C1\x05B8"),
+ model.GetSelectedText());
+
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, true);
+ EXPECT_EQ(2U, model.render_text()->GetSelectionStart());
+ EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_EQ(WideToUTF16(L"c"), model.GetSelectedText());
+
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, true);
+ EXPECT_EQ(2U, model.render_text()->GetSelectionStart());
+ EXPECT_EQ(10U, model.GetCursorPosition());
+ EXPECT_EQ(WideToUTF16(L"c\x05E9\x05BC\x05C1\x05B8\x05E0\x05B8"L"d"),
+ model.GetSelectedText());
+
+ model.ClearSelection();
+ EXPECT_EQ(string16(), model.GetSelectedText());
+ model.SelectAll();
+ EXPECT_EQ(WideToUTF16(L"abc\x05E9\x05BC\x05C1\x05B8\x05E0\x05B8"L"def"),
+ model.GetSelectedText());
+#endif
+
+ // In case of "aBc", this test shows how to select "aB" or "Bc", assume 'B' is
+ // an RTL character.
+ model.SetText(WideToUTF16(L"a\x05E9"L"b"));
+ model.MoveCursorTo(gfx::SelectionModel(0));
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, true);
+ EXPECT_EQ(WideToUTF16(L"a"), model.GetSelectedText());
+
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, true);
+ EXPECT_EQ(WideToUTF16(L"a"), model.GetSelectedText());
+
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, true);
+ EXPECT_EQ(WideToUTF16(L"a\x05E9"L"b"), model.GetSelectedText());
+
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, false);
+ EXPECT_EQ(3U, model.GetCursorPosition());
+ model.MoveCursorLeft(gfx::CHARACTER_BREAK, true);
+ EXPECT_EQ(WideToUTF16(L"b"), model.GetSelectedText());
+
+ model.MoveCursorLeft(gfx::CHARACTER_BREAK, true);
+ EXPECT_EQ(WideToUTF16(L"b"), model.GetSelectedText());
+
+ model.MoveCursorLeft(gfx::CHARACTER_BREAK, true);
+ EXPECT_EQ(WideToUTF16(L"a\x05E9"L"b"), model.GetSelectedText());
+
+ model.MoveCursorLeft(gfx::LINE_BREAK, false);
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, true);
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, true);
+ model.MoveCursorLeft(gfx::CHARACTER_BREAK, true);
+ EXPECT_EQ(WideToUTF16(L"a\x05E9"), model.GetSelectedText());
+
+ model.MoveCursorRight(gfx::LINE_BREAK, false);
+ model.MoveCursorLeft(gfx::CHARACTER_BREAK, true);
+ model.MoveCursorLeft(gfx::CHARACTER_BREAK, true);
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, true);
+ EXPECT_EQ(WideToUTF16(L"\x05E9"L"b"), model.GetSelectedText());
+
+ model.ClearSelection();
+ EXPECT_EQ(string16(), model.GetSelectedText());
+ model.SelectAll();
+ EXPECT_EQ(WideToUTF16(L"a\x05E9"L"b"), model.GetSelectedText());
+}
+
TEST_F(TextfieldViewsModelTest, SelectionAndEdit) {
TextfieldViewsModel model(NULL);
model.Append(ASCIIToUTF16("HELLO"));
@@ -314,8 +555,8 @@ TEST_F(TextfieldViewsModelTest, Clipboard) {
}
void SelectWordTestVerifier(TextfieldViewsModel &model,
- const std::string &expected_selected_string, size_t expected_cursor_pos) {
- EXPECT_STR_EQ(expected_selected_string, model.GetSelectedText());
+ const string16 &expected_selected_string, size_t expected_cursor_pos) {
+ EXPECT_EQ(expected_selected_string, model.GetSelectedText());
EXPECT_EQ(expected_cursor_pos, model.GetCursorPosition());
}
@@ -326,39 +567,74 @@ TEST_F(TextfieldViewsModelTest, SelectWordTest) {
// Test when cursor is at the beginning.
model.MoveCursorLeft(gfx::LINE_BREAK, false);
model.SelectWord();
- SelectWordTestVerifier(model, " ", 2U);
+ SelectWordTestVerifier(model, ASCIIToUTF16(" "), 2U);
// Test when cursor is at the beginning of a word.
gfx::SelectionModel selection(2U);
model.MoveCursorTo(selection);
model.SelectWord();
- SelectWordTestVerifier(model, "HELLO", 7U);
+ SelectWordTestVerifier(model, ASCIIToUTF16("HELLO"), 7U);
// Test when cursor is at the end of a word.
selection = gfx::SelectionModel(15U);
model.MoveCursorTo(selection);
model.SelectWord();
- SelectWordTestVerifier(model, "WO", 15U);
+ SelectWordTestVerifier(model, ASCIIToUTF16(" "), 20U);
- // Test when cursor is somewhere in a non-alph-numeric fragment.
+ // Test when cursor is somewhere in a non-alpha-numeric fragment.
for (size_t cursor_pos = 8; cursor_pos < 13U; cursor_pos++) {
selection = gfx::SelectionModel(cursor_pos);
model.MoveCursorTo(selection);
model.SelectWord();
- SelectWordTestVerifier(model, " !! ", 13U);
+ SelectWordTestVerifier(model, ASCIIToUTF16(" !! "), 13U);
}
// Test when cursor is somewhere in a whitespace fragment.
selection = gfx::SelectionModel(17U);
model.MoveCursorTo(selection);
model.SelectWord();
- SelectWordTestVerifier(model, " ", 20U);
+ SelectWordTestVerifier(model, ASCIIToUTF16(" "), 20U);
// Test when cursor is at the end.
model.MoveCursorRight(gfx::LINE_BREAK, false);
model.SelectWord();
- SelectWordTestVerifier(model, " ", 24U);
+ SelectWordTestVerifier(model, ASCIIToUTF16(" "), 24U);
+}
+
+// TODO(xji): temporarily disable in platform Win since the complex script
+// characters and Chinese characters are turned into empty square due to font
+// regression.
+#if defined(OS_LINUX)
+TEST_F(TextfieldViewsModelTest, SelectWordTest_MixScripts) {
+ TextfieldViewsModel model(NULL);
+ std::vector<WordAndCursor> word_and_cursor;
+ word_and_cursor.push_back(WordAndCursor(L"a\x05d0", 2));
+ word_and_cursor.push_back(WordAndCursor(L"a\x05d0", 2));
+ word_and_cursor.push_back(WordAndCursor(L"\x05d1\x05d2", 5));
+ word_and_cursor.push_back(WordAndCursor(L"\x05d1\x05d2", 5));
+ word_and_cursor.push_back(WordAndCursor(L" ", 3));
+ word_and_cursor.push_back(WordAndCursor(L"a\x05d0", 2));
+ word_and_cursor.push_back(WordAndCursor(L"\x0915\x094d\x0915", 9));
+ word_and_cursor.push_back(WordAndCursor(L"\x0915\x094d\x0915", 9));
+ word_and_cursor.push_back(WordAndCursor(L" ", 10));
+ word_and_cursor.push_back(WordAndCursor(L"\x4E2D\x56FD", 12));
+ word_and_cursor.push_back(WordAndCursor(L"\x4E2D\x56FD", 12));
+ word_and_cursor.push_back(WordAndCursor(L"\x82B1", 13));
+ word_and_cursor.push_back(WordAndCursor(L"\x5929", 14));
+
+ // The text consists of Ascii, Hebrew, Hindi with Virama sign, and Chinese.
+ model.SetText(WideToUTF16(L"a\x05d0 \x05d1\x05d2 \x0915\x094d\x0915 "
+ L"\x4E2D\x56FD\x82B1\x5929"));
+ for (size_t i = 0; i < word_and_cursor.size(); ++i) {
+ model.MoveCursorLeft(gfx::LINE_BREAK, false);
+ for (size_t j = 0; j < i; ++j)
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, false);
+ model.SelectWord();
+ SelectWordTestVerifier(model, WideToUTF16(word_and_cursor[i].word),
+ word_and_cursor[i].cursor);
+ }
}
+#endif
TEST_F(TextfieldViewsModelTest, RangeTest) {
TextfieldViewsModel model(NULL);