diff options
author | xji@google.com <xji@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-08-04 21:47:08 +0000 |
---|---|---|
committer | xji@google.com <xji@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-08-04 21:47:08 +0000 |
commit | 8e42ba22706ce8ca0fd374351f4221a26ebd5e0a (patch) | |
tree | f7b3e0d40d74fcee0ae6a7b88b4dc17689759d8c /views/controls | |
parent | 8316bdb602078971974041c5d62bf3afdf6cf2e5 (diff) | |
download | chromium_src-8e42ba22706ce8ca0fd374351f4221a26ebd5e0a.zip chromium_src-8e42ba22706ce8ca0fd374351f4221a26ebd5e0a.tar.gz chromium_src-8e42ba22706ce8ca0fd374351f4221a26ebd5e0a.tar.bz2 |
extend RenderText for inheritance. It
1. Moves temporary color definition to gfx, declares some function as virtual for override,
removes const from some functions so that the override function is able to modify local data.
2. Cache cursor bounds and compute it (along with display_offset_) when necessary.
3. Introduce SelectionModel (not derivable) for visual cursor positioning.
BUG=90426
TEST=--use-pure-views text editing
Review URL: http://codereview.chromium.org/7461102
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@95508 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'views/controls')
5 files changed, 142 insertions, 66 deletions
diff --git a/views/controls/textfield/native_textfield_views.cc b/views/controls/textfield/native_textfield_views.cc index 9fe2c7f..1fb35d3 100644 --- a/views/controls/textfield/native_textfield_views.cc +++ b/views/controls/textfield/native_textfield_views.cc @@ -202,7 +202,7 @@ int NativeTextfieldViews::OnPerformDrop(const DropTargetEvent& event) { skip_input_method_cancel_composition_ = true; // TODO(msw): Remove final reference to FindCursorPosition. - size_t drop_destination = + gfx::SelectionModel drop_destination = GetRenderText()->FindCursorPosition(event.location()); string16 text; event.data().GetString(&text); @@ -214,13 +214,16 @@ int NativeTextfieldViews::OnPerformDrop(const DropTargetEvent& event) { ui::Range selected_range; model_->GetSelectedRange(&selected_range); // Adjust the drop destination if it is on or after the current selection. - if (selected_range.GetMax() <= drop_destination) - drop_destination -= selected_range.length(); - else if (selected_range.GetMin() <= drop_destination) - drop_destination = selected_range.GetMin(); - model_->DeleteSelectionAndInsertTextAt(text, drop_destination); + if (selected_range.GetMax() <= drop_destination.selection_end()) + drop_destination.set_selection_end( + drop_destination.selection_end() - selected_range.length()); + else if (selected_range.GetMin() <= drop_destination.selection_end()) + drop_destination.set_selection_end(selected_range.GetMin()); + model_->DeleteSelectionAndInsertTextAt(text, + drop_destination.selection_end()); } else { - model_->MoveCursorTo(drop_destination, false); + drop_destination.set_selection_start(drop_destination.selection_end()); + model_->MoveCursorTo(drop_destination); // Drop always inserts text even if the textfield is not in insert mode. model_->InsertText(text); } @@ -253,9 +256,23 @@ void NativeTextfieldViews::OnBlur() { void NativeTextfieldViews::SelectRect(const gfx::Point& start, const gfx::Point& end) { - size_t start_pos = GetRenderText()->FindCursorPosition(start); - size_t end_pos = GetRenderText()->FindCursorPosition(end); - SetSelectionRange(ui::Range(start_pos, end_pos)); + if (GetTextInputType() != ui::TEXT_INPUT_TYPE_NONE) + return; + + gfx::SelectionModel start_pos = GetRenderText()->FindCursorPosition(start); + gfx::SelectionModel end_pos = GetRenderText()->FindCursorPosition(end); + + OnBeforeUserAction(); + // Merge selection models of "start_pos" and "end_pos" so that + // selection start is the value from "start_pos", while selection end, + // caret position, and caret placement are values from "end_pos". + gfx::SelectionModel sel(end_pos); + sel.set_selection_start(start_pos.selection_start()); + model_->SelectSelectionModel(sel); + + OnCaretBoundsChanged(); + SchedulePaint(); + OnAfterUserAction(); } gfx::NativeCursor NativeTextfieldViews::GetCursor(const MouseEvent& event) { @@ -676,9 +693,7 @@ ui::TextInputType NativeTextfieldViews::GetTextInputType() { } gfx::Rect NativeTextfieldViews::GetCaretBounds() { - gfx::RenderText* render_text = GetRenderText(); - return render_text->GetCursorBounds(render_text->GetCursorPosition(), - render_text->insert_mode()); + return GetRenderText()->CursorBounds(); } bool NativeTextfieldViews::HasCompositionText() { @@ -970,9 +985,11 @@ void NativeTextfieldViews::OnCaretBoundsChanged() { if (!touch_selection_controller_.get()) return; gfx::RenderText* render_text = GetRenderText(); - ui::Range range = render_text->GetSelection(); - gfx::Rect start_cursor = render_text->GetCursorBounds(range.start(), false); - gfx::Rect end_cursor = render_text->GetCursorBounds(range.end(), false); + const gfx::SelectionModel& sel = render_text->selection_model(); + gfx::SelectionModel start_sel(sel.selection_start(), sel.selection_start(), + sel.selection_start(), gfx::SelectionModel::LEADING); + gfx::Rect start_cursor = render_text->GetCursorBounds(start_sel, false); + gfx::Rect end_cursor = render_text->GetCursorBounds(sel, false); gfx::Rect display_rect = render_text->display_rect(); int total_offset_x = display_rect.x() + render_text->display_offset().x(); int total_offset_y = display_rect.y() + render_text->display_offset().y() + diff --git a/views/controls/textfield/native_textfield_views_unittest.cc b/views/controls/textfield/native_textfield_views_unittest.cc index 55e19cc..f6c4e59 100644 --- a/views/controls/textfield/native_textfield_views_unittest.cc +++ b/views/controls/textfield/native_textfield_views_unittest.cc @@ -220,7 +220,8 @@ class NativeTextfieldViewsTest : public ViewsTestBase, int GetCursorPositionX(int cursor_pos) { gfx::RenderText* render_text = textfield_view_->GetRenderText(); - return render_text->GetCursorBounds(cursor_pos, false).x(); + return render_text->GetCursorBounds( + gfx::SelectionModel(cursor_pos), false).x(); } // We need widget to populate wrapper class. diff --git a/views/controls/textfield/textfield_views_model.cc b/views/controls/textfield/textfield_views_model.cc index 548e014..eecb9dc 100644 --- a/views/controls/textfield/textfield_views_model.cc +++ b/views/controls/textfield/textfield_views_model.cc @@ -380,10 +380,16 @@ void TextfieldViewsModel::MoveCursorRight(gfx::BreakType break_type, render_text_->MoveCursorRight(break_type, select); } -bool TextfieldViewsModel::MoveCursorTo(size_t pos, bool select) { - if (HasCompositionText()) +bool TextfieldViewsModel::MoveCursorTo(const gfx::SelectionModel& selection) { + if (HasCompositionText()) { ConfirmCompositionText(); - return render_text_->MoveCursorTo(pos, select); + // ConfirmCompositionText() updates cursor position. Need to reflect it in + // the SelectionModel parameter of MoveCursorTo(). + gfx::SelectionModel sel(selection); + sel.set_selection_start(render_text_->GetSelectionStart()); + return render_text_->MoveCursorTo(sel); + } + return render_text_->MoveCursorTo(selection); } bool TextfieldViewsModel::MoveCursorTo(const gfx::Point& point, bool select) { @@ -393,22 +399,30 @@ bool TextfieldViewsModel::MoveCursorTo(const gfx::Point& point, bool select) { } std::vector<gfx::Rect> TextfieldViewsModel::GetSelectionBounds() const { - return render_text_->GetSubstringBounds(render_text_->GetSelection()); + return render_text_->GetSubstringBounds(render_text_->GetSelectionStart(), + render_text_->GetCursorPosition()); } string16 TextfieldViewsModel::GetSelectedText() const { - ui::Range selection = render_text_->GetSelection(); - return GetText().substr(selection.GetMin(), selection.length()); + return GetText().substr(render_text_->MinOfSelection(), + (render_text_->MaxOfSelection() - render_text_->MinOfSelection())); } void TextfieldViewsModel::GetSelectedRange(ui::Range* range) const { - *range = render_text_->GetSelection(); + range->set_start(render_text_->GetSelectionStart()); + range->set_end(render_text_->GetCursorPosition()); } void TextfieldViewsModel::SelectRange(const ui::Range& range) { + gfx::SelectionModel selection(range.start(), range.end(), + range.end(), gfx::SelectionModel::PREVIOUS_GRAPHEME_TRAILING); + SelectSelectionModel(selection); +} + +void TextfieldViewsModel::SelectSelectionModel(const gfx::SelectionModel& sel) { if (HasCompositionText()) ConfirmCompositionText(); - render_text_->SetSelection(range); + render_text_->SetSelectionModel(sel); } void TextfieldViewsModel::SelectAll() { @@ -492,8 +506,11 @@ bool TextfieldViewsModel::Cut() { // than beginning, unlike Delete/Backspace. // TODO(oshima): Change Delete/Backspace to use DeleteSelection, // update DeleteEdit and remove this trick. - ui::Range selection = render_text_->GetSelection(); - render_text_->SetSelection(ui::Range(selection.end(), selection.start())); + gfx::SelectionModel sel(render_text_->GetCursorPosition(), + render_text_->GetSelectionStart(), + render_text_->GetSelectionStart(), + gfx::SelectionModel::LEADING); + render_text_->SetSelectionModel(sel); DeleteSelection(); return true; } @@ -519,14 +536,14 @@ bool TextfieldViewsModel::Paste() { } bool TextfieldViewsModel::HasSelection() const { - return !render_text_->GetSelection().is_empty(); + return !render_text_->EmptySelection(); } void TextfieldViewsModel::DeleteSelection() { DCHECK(!HasCompositionText()); DCHECK(HasSelection()); - ui::Range selection = render_text_->GetSelection(); - ExecuteAndRecordDelete(selection.start(), selection.end(), false); + ExecuteAndRecordDelete(render_text_->GetSelectionStart(), + render_text_->GetCursorPosition(), false); } void TextfieldViewsModel::DeleteSelectionAndInsertTextAt( @@ -567,12 +584,17 @@ void TextfieldViewsModel::SetCompositionText( render_text_->SetCompositionRange(range); // TODO(msw): Support multiple composition underline ranges. - if (composition.selection.IsValid()) - render_text_->SetSelection(ui::Range( - std::min(range.start() + composition.selection.start(), range.end()), - std::min(range.start() + composition.selection.end(), range.end()))); - else + if (composition.selection.IsValid()) { + size_t start = + std::min(range.start() + composition.selection.start(), range.end()); + size_t end = + std::min(range.start() + composition.selection.end(), range.end()); + gfx::SelectionModel sel(start, end, end, + gfx::SelectionModel::PREVIOUS_GRAPHEME_TRAILING); + render_text_->SetSelectionModel(sel); + } else { render_text_->SetCursorPosition(range.end()); + } } void TextfieldViewsModel::ConfirmCompositionText() { @@ -640,7 +662,9 @@ void TextfieldViewsModel::ReplaceTextInternal(const string16& text, CancelCompositionText(); } else if (!HasSelection()) { size_t cursor = GetCursorPosition(); - render_text_->SetSelection(ui::Range(cursor + text.length(), cursor)); + gfx::SelectionModel sel(render_text_->selection_model()); + sel.set_selection_start(cursor + text.length()); + render_text_->SetSelectionModel(sel); } // Edit history is recorded in InsertText. InsertTextInternal(text, mergeable); @@ -671,7 +695,7 @@ void TextfieldViewsModel::ExecuteAndRecordDelete(size_t from, bool mergeable) { size_t old_text_start = std::min(from, to); const string16 text = GetText().substr(old_text_start, - std::abs(static_cast<long>(from - to))); + std::abs(static_cast<long>(from - to))); bool backward = from > to; Edit* edit = new DeleteEdit(mergeable, text, old_text_start, backward); bool delete_edit = AddOrMergeEditHistory(edit); @@ -682,7 +706,7 @@ void TextfieldViewsModel::ExecuteAndRecordDelete(size_t from, void TextfieldViewsModel::ExecuteAndRecordReplaceSelection( MergeType merge_type, const string16& new_text) { - size_t new_text_start = render_text_->GetSelection().GetMin(); + size_t new_text_start = render_text_->MinOfSelection(); size_t new_cursor_pos = new_text_start + new_text.length(); ExecuteAndRecordReplace(merge_type, GetCursorPosition(), @@ -696,8 +720,9 @@ void TextfieldViewsModel::ExecuteAndRecordReplace(MergeType merge_type, size_t new_cursor_pos, const string16& new_text, size_t new_text_start) { - size_t old_text_start = render_text_->GetSelection().GetMin(); - bool backward = render_text_->GetSelection().is_reversed(); + size_t old_text_start = render_text_->MinOfSelection(); + bool backward = + render_text_->GetSelectionStart() > render_text_->GetCursorPosition(); Edit* edit = new ReplaceEdit(merge_type, GetSelectedText(), old_cursor_pos, diff --git a/views/controls/textfield/textfield_views_model.h b/views/controls/textfield/textfield_views_model.h index 0c49267..0fadf12 100644 --- a/views/controls/textfield/textfield_views_model.h +++ b/views/controls/textfield/textfield_views_model.h @@ -134,10 +134,11 @@ class VIEWS_API TextfieldViewsModel { void MoveCursorLeft(gfx::BreakType break_type, bool select); void MoveCursorRight(gfx::BreakType break_type, bool select); - // Moves the cursor to the specified |position|. - // If |select| is true, it updates the selection accordingly. - // The current composition text will be confirmed. - bool MoveCursorTo(size_t position, bool select); + // Moves the selection to the specified selection in |selection|. + // If there is composition text, it will be confirmed, which will update the + // selection range, and it overrides the selection_start to which the + // selection will move to. + bool MoveCursorTo(const gfx::SelectionModel& selection); // Helper function to call MoveCursorTo on the TextfieldViewsModel. bool MoveCursorTo(const gfx::Point& point, bool select); @@ -158,6 +159,10 @@ class VIEWS_API TextfieldViewsModel { // the cursor position becomes the end position. void SelectRange(const ui::Range& range); + // The current composition text will be confirmed. + // render_text_'s selection model is set to |sel|. + void SelectSelectionModel(const gfx::SelectionModel& sel); + // Selects all text. // The current composition text will be confirmed. void SelectAll(); diff --git a/views/controls/textfield/textfield_views_model_unittest.cc b/views/controls/textfield/textfield_views_model_unittest.cc index 907e353..e5ce138 100644 --- a/views/controls/textfield/textfield_views_model_unittest.cc +++ b/views/controls/textfield/textfield_views_model_unittest.cc @@ -126,13 +126,18 @@ TEST_F(TextfieldViewsModelTest, Selection) { EXPECT_EQ(5U, range.end()); // Select and move cursor - model.MoveCursorTo(1U, false); - model.MoveCursorTo(3U, true); + gfx::SelectionModel selection(1U); + model.MoveCursorTo(selection); + selection.set_selection_end(3U); + model.MoveCursorTo(selection); EXPECT_STR_EQ("EL", model.GetSelectedText()); model.MoveCursorLeft(gfx::CHARACTER_BREAK, false); EXPECT_EQ(1U, model.GetCursorPosition()); - model.MoveCursorTo(1U, false); - model.MoveCursorTo(3U, true); + selection.set_selection_end(1U); + selection.set_selection_start(selection.selection_end()); + model.MoveCursorTo(selection); + selection.set_selection_end(3U); + model.MoveCursorTo(selection); model.MoveCursorRight(gfx::CHARACTER_BREAK, false); EXPECT_EQ(3U, model.GetCursorPosition()); @@ -331,24 +336,31 @@ TEST_F(TextfieldViewsModelTest, SelectWordTest) { SelectWordTestVerifier(model, " ", 2U); // Test when cursor is at the beginning of a word. - model.MoveCursorTo(2U, false); + gfx::SelectionModel selection(2U); + model.MoveCursorTo(selection); model.SelectWord(); SelectWordTestVerifier(model, "HELLO", 7U); // Test when cursor is at the end of a word. - model.MoveCursorTo(15U, false); + selection.set_selection_end(15U); + selection.set_selection_start(selection.selection_end()); + model.MoveCursorTo(selection); model.SelectWord(); SelectWordTestVerifier(model, "WO", 15U); // Test when cursor is somewhere in a non-alph-numeric fragment. for (size_t cursor_pos = 8; cursor_pos < 13U; cursor_pos++) { - model.MoveCursorTo(cursor_pos, false); + selection.set_selection_end(cursor_pos); + selection.set_selection_start(selection.selection_end()); + model.MoveCursorTo(selection); model.SelectWord(); SelectWordTestVerifier(model, " !! ", 13U); } // Test when cursor is somewhere in a whitespace fragment. - model.MoveCursorTo(17U, false); + selection.set_selection_end(17U); + selection.set_selection_start(selection.selection_end()); + model.MoveCursorTo(selection); model.SelectWord(); SelectWordTestVerifier(model, " ", 20U); @@ -597,7 +609,9 @@ TEST_F(TextfieldViewsModelTest, CompositionTextTest) { EXPECT_STR_EQ("678", model.GetText()); model.SetCompositionText(composition); - model.MoveCursorTo(0, true); + gfx::SelectionModel sel(0); + sel.set_selection_start(model.render_text()->GetSelectionStart()); + model.MoveCursorTo(sel); EXPECT_TRUE(composition_text_confirmed_or_cleared_); composition_text_confirmed_or_cleared_ = false; EXPECT_STR_EQ("678678", model.GetText()); @@ -700,7 +714,8 @@ TEST_F(TextfieldViewsModelTest, UndoRedo_BasicTest) { // Delete =============================== model.SetText(ASCIIToUTF16("ABCDE")); model.ClearEditHistory(); - model.MoveCursorTo(2, false); + gfx::SelectionModel sel(2); + model.MoveCursorTo(sel); EXPECT_TRUE(model.Delete()); EXPECT_STR_EQ("ABDE", model.GetText()); model.MoveCursorLeft(gfx::LINE_BREAK, false); @@ -794,8 +809,10 @@ TEST_F(TextfieldViewsModelTest, UndoRedo_CutCopyPasteTest) { model.SetText(ASCIIToUTF16("ABCDE")); EXPECT_FALSE(model.Redo()); // nothing to redo // Cut - model.MoveCursorTo(1, false); - model.MoveCursorTo(3, true); + gfx::SelectionModel sel(1); + model.MoveCursorTo(sel); + sel.set_selection_end(3); + model.MoveCursorTo(sel); model.Cut(); EXPECT_STR_EQ("ADE", model.GetText()); EXPECT_EQ(1U, model.GetCursorPosition()); @@ -881,8 +898,11 @@ TEST_F(TextfieldViewsModelTest, UndoRedo_CutCopyPasteTest) { model.SetText(ASCIIToUTF16("12345")); EXPECT_STR_EQ("12345", model.GetText()); EXPECT_EQ(0U, model.GetCursorPosition()); - model.MoveCursorTo(1, false); - model.MoveCursorTo(3, true); + sel.set_selection_end(1); + sel.set_selection_start(sel.selection_end()); + model.MoveCursorTo(sel); + sel.set_selection_end(3); + model.MoveCursorTo(sel); model.Copy(); // Copy "23" EXPECT_STR_EQ("12345", model.GetText()); EXPECT_EQ(3U, model.GetCursorPosition()); @@ -1006,32 +1026,40 @@ TEST_F(TextfieldViewsModelTest, UndoRedo_ReplaceTest) { SCOPED_TRACE("forward & insert by cursor"); TextfieldViewsModel model(NULL); model.SetText(ASCIIToUTF16("abcd")); - model.MoveCursorTo(1, false); - model.MoveCursorTo(3, true); + gfx::SelectionModel sel(1); + model.MoveCursorTo(sel); + sel.set_selection_end(3); + model.MoveCursorTo(sel); RunInsertReplaceTest(model); } { SCOPED_TRACE("backward & insert by cursor"); TextfieldViewsModel model(NULL); model.SetText(ASCIIToUTF16("abcd")); - model.MoveCursorTo(3, false); - model.MoveCursorTo(1, true); + gfx::SelectionModel sel(3); + model.MoveCursorTo(sel); + sel.set_selection_end(1); + model.MoveCursorTo(sel); RunInsertReplaceTest(model); } { SCOPED_TRACE("forward & overwrite by cursor"); TextfieldViewsModel model(NULL); model.SetText(ASCIIToUTF16("abcd")); - model.MoveCursorTo(1, false); - model.MoveCursorTo(3, true); + gfx::SelectionModel sel(1); + model.MoveCursorTo(sel); + sel.set_selection_end(3); + model.MoveCursorTo(sel); RunOverwriteReplaceTest(model); } { SCOPED_TRACE("backward & overwrite by cursor"); TextfieldViewsModel model(NULL); model.SetText(ASCIIToUTF16("abcd")); - model.MoveCursorTo(3, false); - model.MoveCursorTo(1, true); + gfx::SelectionModel sel(3); + model.MoveCursorTo(sel); + sel.set_selection_end(1); + model.MoveCursorTo(sel); RunOverwriteReplaceTest(model); } // By SelectRange API |