Index: views/controls/textfield/textfield_views_model.cc |
diff --git a/views/controls/textfield/textfield_views_model.cc b/views/controls/textfield/textfield_views_model.cc |
index 89253a626c92dbd576861fa53aac976677f6d241..b1773ee9cd5ac44898b8d85227a52fe5d508aacb 100644 |
--- a/views/controls/textfield/textfield_views_model.cc |
+++ b/views/controls/textfield/textfield_views_model.cc |
@@ -41,28 +41,31 @@ class Edit { |
// Revert the change made by this edit in |model|. |
void Undo(TextfieldViewsModel* model) { |
- size_t old_cursor = delete_backward_ ? old_text_end() : old_text_start_; |
model->ModifyText(new_text_start_, new_text_end(), |
old_text_, old_text_start_, |
- old_cursor); |
+ old_cursor_pos_); |
} |
// Apply the change of this edit to the |model|. |
void Redo(TextfieldViewsModel* model) { |
model->ModifyText(old_text_start_, old_text_end(), |
new_text_, new_text_start_, |
- new_text_end()); |
+ new_cursor_pos_); |
} |
- // Try to merge the edit into this edit. Returns true if merge was |
+ // Try to merge the |edit| into this edit. Returns true if merge was |
// successful, or false otherwise. Merged edit will be deleted after |
// redo and should not be reused. |
- bool Merge(Edit* edit) { |
- return mergeable_ && edit->mergeable() && DoMerge(edit); |
+ bool Merge(const Edit* edit) { |
+ if (edit->merge_with_previous()) { |
+ MergeSet(edit); |
+ return true; |
+ } |
+ return mergeable() && edit->mergeable() && DoMerge(edit); |
} |
// Commits the edit and marks as un-mergeable. |
- void Commit() { mergeable_ = false; } |
+ void Commit() { merge_type_ = DO_NOT_MERGE; } |
private: |
friend class InsertEdit; |
@@ -70,46 +73,79 @@ class Edit { |
friend class DeleteEdit; |
Edit(Type type, |
- bool mergeable, |
+ MergeType merge_type, |
+ size_t old_cursor_pos, |
string16 old_text, |
size_t old_text_start, |
bool delete_backward, |
+ size_t new_cursor_pos, |
string16 new_text, |
size_t new_text_start) |
: type_(type), |
- mergeable_(mergeable), |
+ merge_type_(merge_type), |
+ old_cursor_pos_(old_cursor_pos), |
old_text_(old_text), |
old_text_start_(old_text_start), |
delete_backward_(delete_backward), |
+ new_cursor_pos_(new_cursor_pos), |
new_text_(new_text), |
new_text_start_(new_text_start) { |
} |
// A template method pattern that provides specific merge |
// implementation for each type of edit. |
- virtual bool DoMerge(Edit* edit) = 0; |
+ virtual bool DoMerge(const Edit* edit) = 0; |
- Type type() { return type_; } |
+ Type type() const { return type_; } |
// Can this edit be merged? |
- bool mergeable() { return mergeable_; } |
+ bool mergeable() const { return merge_type_ == MERGEABLE; } |
+ |
+ // Does this edit should be merged with previous edit? |
msw
2011/06/02 21:02:57
okay, the function is fine, but the comment needs
oshima
2011/06/03 00:39:24
Done.
|
+ bool merge_with_previous() const { |
+ return merge_type_ == MERGE_WITH_PREVIOUS; |
+ } |
// Returns the end index of the |old_text_|. |
- size_t old_text_end() { return old_text_start_ + old_text_.length(); } |
+ size_t old_text_end() const { return old_text_start_ + old_text_.length(); } |
// Returns the end index of the |new_text_|. |
- size_t new_text_end() { return new_text_start_ + new_text_.length(); } |
+ size_t new_text_end() const { return new_text_start_ + new_text_.length(); } |
+ |
+ // Merge the Set edit into the current edit. This is a special case to |
+ // handle an omnibox setting autocomplete string after new character is |
+ // typed in. |
+ void MergeSet(const Edit* edit) { |
+ CHECK_EQ(REPLACE_EDIT, edit->type_); |
+ CHECK_EQ(0U, edit->old_text_start_); |
+ CHECK_EQ(0U, edit->new_text_start_); |
+ string16 old_text = edit->old_text_; |
+ old_text.erase(new_text_start_, new_text_.length()); |
+ old_text.insert(old_text_start_, old_text_); |
+ // Set edit replaces entire text, so remember that. |
msw
2011/06/02 21:02:57
I ran through an example just now and it makes sen
oshima
2011/06/03 00:39:24
Done.
|
+ old_text_ = old_text; |
+ old_text_start_ = edit->old_text_start_; |
+ delete_backward_ = false; |
+ |
+ new_text_ = edit->new_text_; |
+ new_text_start_ = edit->new_text_start_; |
+ merge_type_ = DO_NOT_MERGE; |
+ } |
Type type_; |
// True if the edit can be marged. |
- bool mergeable_; |
+ MergeType merge_type_; |
+ // Old cursor position. |
+ size_t old_cursor_pos_; |
// Deleted text by this edit. |
string16 old_text_; |
// The index of |old_text_|. |
size_t old_text_start_; |
// True if the deletion is made backward. |
bool delete_backward_; |
+ // New cursor position. |
+ size_t new_cursor_pos_; |
// Added text. |
string16 new_text_; |
// The index of |new_text_| |
@@ -121,45 +157,59 @@ class Edit { |
class InsertEdit : public Edit { |
public: |
InsertEdit(bool mergeable, const string16& new_text, size_t at) |
- : Edit(INSERT_EDIT, mergeable, string16(), at, false, new_text, at) { |
+ : Edit(INSERT_EDIT, |
+ mergeable ? MERGEABLE : DO_NOT_MERGE, |
+ at /* old cursor */, |
+ string16(), |
+ at, |
+ false /* N/A */, |
+ at + new_text.length() /* new cursor */, |
+ new_text, |
+ at) { |
} |
// Edit implementation. |
- virtual bool DoMerge(Edit* edit) OVERRIDE { |
+ virtual bool DoMerge(const Edit* edit) OVERRIDE { |
if (edit->type() != INSERT_EDIT || new_text_end() != edit->new_text_start_) |
return false; |
// If continuous edit, merge it. |
// TODO(oshima): gtk splits edits between whitespace. Find out what |
// we want to here and implement if necessary. |
new_text_ += edit->new_text_; |
+ new_cursor_pos_ = edit->new_cursor_pos_; |
return true; |
} |
}; |
class ReplaceEdit : public Edit { |
public: |
- ReplaceEdit(bool mergeable, |
+ ReplaceEdit(MergeType merge_type, |
const string16& old_text, |
+ size_t old_cursor_pos, |
size_t old_text_start, |
bool backward, |
+ size_t new_cursor_pos, |
const string16& new_text, |
size_t new_text_start) |
- : Edit(REPLACE_EDIT, mergeable, |
+ : Edit(REPLACE_EDIT, merge_type, |
+ old_cursor_pos, |
old_text, |
old_text_start, |
backward, |
+ new_cursor_pos, |
new_text, |
new_text_start) { |
} |
// Edit implementation. |
- virtual bool DoMerge(Edit* edit) OVERRIDE { |
+ virtual bool DoMerge(const Edit* edit) OVERRIDE { |
if (edit->type() == DELETE_EDIT || |
new_text_end() != edit->old_text_start_ || |
edit->old_text_start_ != edit->new_text_start_) |
return false; |
old_text_ += edit->old_text_; |
new_text_ += edit->new_text_; |
+ new_cursor_pos_ = edit->new_cursor_pos_; |
return true; |
} |
}; |
@@ -170,16 +220,19 @@ class DeleteEdit : public Edit { |
const string16& text, |
size_t text_start, |
bool backward) |
- : Edit(DELETE_EDIT, mergeable, |
+ : Edit(DELETE_EDIT, |
+ mergeable ? MERGEABLE : DO_NOT_MERGE, |
+ (backward ? text_start + text.length() : text_start), |
text, |
text_start, |
backward, |
+ text_start, |
string16(), |
text_start) { |
} |
// Edit implementation. |
- virtual bool DoMerge(Edit* edit) OVERRIDE { |
+ virtual bool DoMerge(const Edit* edit) OVERRIDE { |
if (edit->type() != DELETE_EDIT) |
return false; |
@@ -190,6 +243,7 @@ class DeleteEdit : public Edit { |
return false; |
old_text_start_ = edit->old_text_start_; |
old_text_ = edit->old_text_ + old_text_; |
+ new_cursor_pos_ = edit->new_cursor_pos_; |
} else { |
// delete can be merged only with delete at the same |
// position. |
@@ -313,6 +367,10 @@ using internal::Edit; |
using internal::DeleteEdit; |
using internal::InsertEdit; |
using internal::ReplaceEdit; |
+using internal::MergeType; |
+using internal::DO_NOT_MERGE; |
+using internal::MERGE_WITH_PREVIOUS; |
+using internal::MERGEABLE; |
///////////////////////////////////////////////////////////////// |
// TextfieldViewsModel: public |
@@ -395,11 +453,20 @@ bool TextfieldViewsModel::SetText(const string16& text) { |
changed = true; |
} |
if (text_ != text) { |
- if (changed) // no need to remember composition. |
+ if (changed) // No need to remember composition. |
Undo(); |
+ size_t old_cursor = cursor_pos_; |
+ size_t new_cursor = old_cursor > text.length() ? text.length() : old_cursor; |
SelectAll(); |
- InsertTextInternal(text, false); |
- cursor_pos_ = 0; |
+ // If there is a composition text, don't merge with previous edit. |
+ // Otherwise, force merge the edits. |
+ ExecuteAndRecordReplace( |
+ changed ? DO_NOT_MERGE : MERGE_WITH_PREVIOUS, |
+ old_cursor, |
+ new_cursor, |
+ text, |
+ 0U); |
+ cursor_pos_ = new_cursor; |
} |
ClearSelection(); |
return changed; |
@@ -667,6 +734,7 @@ bool TextfieldViewsModel::Undo() { |
CancelCompositionText(); |
string16 old = text_; |
+ size_t old_cursor = cursor_pos_; |
(*current_edit_)->Commit(); |
(*current_edit_)->Undo(this); |
@@ -674,7 +742,7 @@ bool TextfieldViewsModel::Undo() { |
current_edit_ = edit_history_.end(); |
else |
current_edit_--; |
- return old != text_; |
+ return old != text_ || old_cursor != cursor_pos_; |
} |
bool TextfieldViewsModel::Redo() { |
@@ -689,14 +757,21 @@ bool TextfieldViewsModel::Redo() { |
else |
current_edit_ ++; |
string16 old = text_; |
+ size_t old_cursor = cursor_pos_; |
(*current_edit_)->Redo(this); |
- return old != text_; |
+ return old != text_ || old_cursor != cursor_pos_; |
} |
bool TextfieldViewsModel::Cut() { |
if (!HasCompositionText() && HasSelection()) { |
ui::ScopedClipboardWriter(views::ViewsDelegate::views_delegate |
->GetClipboard()).WriteText(GetSelectedText()); |
+ // A trick to let undo/redo handle cursor correctly. |
+ // Undoing CUT moves the cursor to the end of the change rather |
+ // than beginning, unlike Delete/Backspace. |
+ // TODO(oshima): Change Delete/Backspace to use DeleteSelection, |
+ // update DeleteEdit and remove this trick. |
+ std::swap(cursor_pos_, selection_start_); |
DeleteSelection(); |
return true; |
} |
@@ -735,7 +810,11 @@ void TextfieldViewsModel::DeleteSelectionAndInsertTextAt( |
const string16& text, size_t position) { |
if (HasCompositionText()) |
CancelCompositionText(); |
- ExecuteAndRecordReplaceAt(text, position, false); |
+ ExecuteAndRecordReplace(DO_NOT_MERGE, |
+ cursor_pos_, |
+ position + text.length(), |
+ text, |
+ position); |
} |
string16 TextfieldViewsModel::GetTextFromRange(const ui::Range& range) const { |
@@ -879,7 +958,8 @@ void TextfieldViewsModel::InsertTextInternal(const string16& text, |
CancelCompositionText(); |
ExecuteAndRecordInsert(text, mergeable); |
} else if (HasSelection()) { |
- ExecuteAndRecordReplace(text, mergeable); |
+ ExecuteAndRecordReplaceSelection(mergeable ? MERGEABLE : DO_NOT_MERGE, |
+ text); |
} else { |
ExecuteAndRecordInsert(text, mergeable); |
} |
@@ -911,8 +991,7 @@ void TextfieldViewsModel::ClearRedoHistory() { |
} |
EditHistory::iterator delete_start = current_edit_; |
delete_start++; |
- STLDeleteContainerPointers(delete_start, |
- edit_history_.end()); |
+ STLDeleteContainerPointers(delete_start, edit_history_.end()); |
edit_history_.erase(delete_start, edit_history_.end()); |
} |
@@ -922,32 +1001,40 @@ void TextfieldViewsModel::ExecuteAndRecordDelete(size_t from, |
size_t old_text_start = std::min(from, to); |
const string16 text = text_.substr(old_text_start, |
std::abs(static_cast<long>(from - to))); |
- Edit* edit = new DeleteEdit(mergeable, |
- text, |
- old_text_start, |
- (from > to)); |
+ bool backward = from > to; |
+ Edit* edit = new DeleteEdit(mergeable, text, old_text_start, backward); |
bool delete_edit = AddOrMergeEditHistory(edit); |
edit->Redo(this); |
if (delete_edit) |
delete edit; |
} |
-void TextfieldViewsModel::ExecuteAndRecordReplace(const string16& text, |
- bool mergeable) { |
- size_t at = std::min(cursor_pos_, selection_start_); |
- ExecuteAndRecordReplaceAt(text, at, mergeable); |
+void TextfieldViewsModel::ExecuteAndRecordReplaceSelection( |
+ MergeType merge_type, const string16& new_text) { |
+ size_t new_text_start = std::min(cursor_pos_, selection_start_); |
+ size_t new_cursor_pos = new_text_start + new_text.length(); |
+ ExecuteAndRecordReplace(merge_type, |
+ cursor_pos_, |
+ new_cursor_pos, |
+ new_text, |
+ new_text_start); |
} |
-void TextfieldViewsModel::ExecuteAndRecordReplaceAt(const string16& text, |
- size_t at, |
- bool mergeable) { |
- size_t text_start = std::min(cursor_pos_, selection_start_); |
- Edit* edit = new ReplaceEdit(mergeable, |
+void TextfieldViewsModel::ExecuteAndRecordReplace(MergeType merge_type, |
+ size_t old_cursor_pos, |
+ size_t new_cursor_pos, |
+ const string16& new_text, |
+ size_t new_text_start) { |
+ size_t old_text_start = std::min(cursor_pos_, selection_start_); |
+ bool backward = selection_start_ > cursor_pos_; |
+ Edit* edit = new ReplaceEdit(merge_type, |
GetSelectedText(), |
- text_start, |
- selection_start_ > cursor_pos_, |
- text, |
- at); |
+ old_cursor_pos, |
+ old_text_start, |
+ backward, |
+ new_cursor_pos, |
+ new_text, |
+ new_text_start); |
bool delete_edit = AddOrMergeEditHistory(edit); |
edit->Redo(this); |
if (delete_edit) |