Home | History | Annotate | Download | only in textfield
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "ui/views/controls/textfield/textfield_model.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "base/logging.h"
     10 #include "base/stl_util.h"
     11 #include "ui/base/clipboard/clipboard.h"
     12 #include "ui/base/clipboard/scoped_clipboard_writer.h"
     13 #include "ui/gfx/range/range.h"
     14 #include "ui/gfx/utf16_indexing.h"
     15 
     16 namespace views {
     17 
     18 namespace internal {
     19 
     20 // Edit holds state information to undo/redo editing changes. Editing operations
     21 // are merged when possible, like when characters are typed in sequence. Calling
     22 // Commit() marks an edit as an independent operation that shouldn't be merged.
     23 class Edit {
     24  public:
     25   enum Type {
     26     INSERT_EDIT,
     27     DELETE_EDIT,
     28     REPLACE_EDIT,
     29   };
     30 
     31   virtual ~Edit() {}
     32 
     33   // Revert the change made by this edit in |model|.
     34   void Undo(TextfieldModel* model) {
     35     model->ModifyText(new_text_start_, new_text_end(),
     36                       old_text_, old_text_start_,
     37                       old_cursor_pos_);
     38   }
     39 
     40   // Apply the change of this edit to the |model|.
     41   void Redo(TextfieldModel* model) {
     42     model->ModifyText(old_text_start_, old_text_end(),
     43                       new_text_, new_text_start_,
     44                       new_cursor_pos_);
     45   }
     46 
     47   // Try to merge the |edit| into this edit and returns true on success. The
     48   // merged edit will be deleted after redo and should not be reused.
     49   bool Merge(const Edit* edit) {
     50     // Don't merge if previous edit is DELETE. This happens when a
     51     // user deletes characters then hits return. In this case, the
     52     // delete should be treated as separate edit that can be undone
     53     // and should not be merged with the replace edit.
     54     if (type_ != DELETE_EDIT && edit->force_merge()) {
     55       MergeReplace(edit);
     56       return true;
     57     }
     58     return mergeable() && edit->mergeable() && DoMerge(edit);
     59   }
     60 
     61   // Commits the edit and marks as un-mergeable.
     62   void Commit() { merge_type_ = DO_NOT_MERGE; }
     63 
     64  private:
     65   friend class InsertEdit;
     66   friend class ReplaceEdit;
     67   friend class DeleteEdit;
     68 
     69   Edit(Type type,
     70        MergeType merge_type,
     71        size_t old_cursor_pos,
     72        const base::string16& old_text,
     73        size_t old_text_start,
     74        bool delete_backward,
     75        size_t new_cursor_pos,
     76        const base::string16& new_text,
     77        size_t new_text_start)
     78       : type_(type),
     79         merge_type_(merge_type),
     80         old_cursor_pos_(old_cursor_pos),
     81         old_text_(old_text),
     82         old_text_start_(old_text_start),
     83         delete_backward_(delete_backward),
     84         new_cursor_pos_(new_cursor_pos),
     85         new_text_(new_text),
     86         new_text_start_(new_text_start) {
     87   }
     88 
     89   // Each type of edit provides its own specific merge implementation.
     90   virtual bool DoMerge(const Edit* edit) = 0;
     91 
     92   Type type() const { return type_; }
     93 
     94   // Can this edit be merged?
     95   bool mergeable() const { return merge_type_ == MERGEABLE; }
     96 
     97   // Should this edit be forcibly merged with the previous edit?
     98   bool force_merge() const { return merge_type_ == FORCE_MERGE; }
     99 
    100   // Returns the end index of the |old_text_|.
    101   size_t old_text_end() const { return old_text_start_ + old_text_.length(); }
    102 
    103   // Returns the end index of the |new_text_|.
    104   size_t new_text_end() const { return new_text_start_ + new_text_.length(); }
    105 
    106   // Merge the replace edit into the current edit. This handles the special case
    107   // where an omnibox autocomplete string is set after a new character is typed.
    108   void MergeReplace(const Edit* edit) {
    109     CHECK_EQ(REPLACE_EDIT, edit->type_);
    110     CHECK_EQ(0U, edit->old_text_start_);
    111     CHECK_EQ(0U, edit->new_text_start_);
    112     base::string16 old_text = edit->old_text_;
    113     old_text.erase(new_text_start_, new_text_.length());
    114     old_text.insert(old_text_start_, old_text_);
    115     // SetText() replaces entire text. Set |old_text_| to the entire
    116     // replaced text with |this| edit undone.
    117     old_text_ = old_text;
    118     old_text_start_ = edit->old_text_start_;
    119     delete_backward_ = false;
    120 
    121     new_text_ = edit->new_text_;
    122     new_text_start_ = edit->new_text_start_;
    123     merge_type_ = DO_NOT_MERGE;
    124   }
    125 
    126   Type type_;
    127 
    128   // True if the edit can be marged.
    129   MergeType merge_type_;
    130   // Old cursor position.
    131   size_t old_cursor_pos_;
    132   // Deleted text by this edit.
    133   base::string16 old_text_;
    134   // The index of |old_text_|.
    135   size_t old_text_start_;
    136   // True if the deletion is made backward.
    137   bool delete_backward_;
    138   // New cursor position.
    139   size_t new_cursor_pos_;
    140   // Added text.
    141   base::string16 new_text_;
    142   // The index of |new_text_|
    143   size_t new_text_start_;
    144 
    145   DISALLOW_COPY_AND_ASSIGN(Edit);
    146 };
    147 
    148 class InsertEdit : public Edit {
    149  public:
    150   InsertEdit(bool mergeable, const base::string16& new_text, size_t at)
    151       : Edit(INSERT_EDIT,
    152              mergeable ? MERGEABLE : DO_NOT_MERGE,
    153              at  /* old cursor */,
    154              base::string16(),
    155              at,
    156              false  /* N/A */,
    157              at + new_text.length()  /* new cursor */,
    158              new_text,
    159              at) {
    160   }
    161 
    162   // Edit implementation.
    163   virtual bool DoMerge(const Edit* edit) OVERRIDE {
    164     if (edit->type() != INSERT_EDIT || new_text_end() != edit->new_text_start_)
    165       return false;
    166     // If continuous edit, merge it.
    167     // TODO(oshima): gtk splits edits between whitespace. Find out what
    168     // we want to here and implement if necessary.
    169     new_text_ += edit->new_text_;
    170     new_cursor_pos_ = edit->new_cursor_pos_;
    171     return true;
    172   }
    173 };
    174 
    175 class ReplaceEdit : public Edit {
    176  public:
    177   ReplaceEdit(MergeType merge_type,
    178               const base::string16& old_text,
    179               size_t old_cursor_pos,
    180               size_t old_text_start,
    181               bool backward,
    182               size_t new_cursor_pos,
    183               const base::string16& new_text,
    184               size_t new_text_start)
    185       : Edit(REPLACE_EDIT, merge_type,
    186              old_cursor_pos,
    187              old_text,
    188              old_text_start,
    189              backward,
    190              new_cursor_pos,
    191              new_text,
    192              new_text_start) {
    193   }
    194 
    195   // Edit implementation.
    196   virtual bool DoMerge(const Edit* edit) OVERRIDE {
    197     if (edit->type() == DELETE_EDIT ||
    198         new_text_end() != edit->old_text_start_ ||
    199         edit->old_text_start_ != edit->new_text_start_)
    200       return false;
    201     old_text_ += edit->old_text_;
    202     new_text_ += edit->new_text_;
    203     new_cursor_pos_ = edit->new_cursor_pos_;
    204     return true;
    205   }
    206 };
    207 
    208 class DeleteEdit : public Edit {
    209  public:
    210   DeleteEdit(bool mergeable,
    211              const base::string16& text,
    212              size_t text_start,
    213              bool backward)
    214       : Edit(DELETE_EDIT,
    215              mergeable ? MERGEABLE : DO_NOT_MERGE,
    216              (backward ? text_start + text.length() : text_start),
    217              text,
    218              text_start,
    219              backward,
    220              text_start,
    221              base::string16(),
    222              text_start) {
    223   }
    224 
    225   // Edit implementation.
    226   virtual bool DoMerge(const Edit* edit) OVERRIDE {
    227     if (edit->type() != DELETE_EDIT)
    228       return false;
    229 
    230     if (delete_backward_) {
    231       // backspace can be merged only with backspace at the same position.
    232       if (!edit->delete_backward_ || old_text_start_ != edit->old_text_end())
    233         return false;
    234       old_text_start_ = edit->old_text_start_;
    235       old_text_ = edit->old_text_ + old_text_;
    236       new_cursor_pos_ = edit->new_cursor_pos_;
    237     } else {
    238       // delete can be merged only with delete at the same position.
    239       if (edit->delete_backward_ || old_text_start_ != edit->old_text_start_)
    240         return false;
    241       old_text_ += edit->old_text_;
    242     }
    243     return true;
    244   }
    245 };
    246 
    247 }  // namespace internal
    248 
    249 namespace {
    250 
    251 // Returns the first segment that is visually emphasized. Usually it's used for
    252 // representing the target clause (on Windows). Returns an invalid range if
    253 // there is no such a range.
    254 gfx::Range GetFirstEmphasizedRange(const ui::CompositionText& composition) {
    255   for (size_t i = 0; i < composition.underlines.size(); ++i) {
    256     const ui::CompositionUnderline& underline = composition.underlines[i];
    257     if (underline.thick)
    258       return gfx::Range(underline.start_offset, underline.end_offset);
    259   }
    260   return gfx::Range::InvalidRange();
    261 }
    262 
    263 }  // namespace
    264 
    265 using internal::Edit;
    266 using internal::DeleteEdit;
    267 using internal::InsertEdit;
    268 using internal::ReplaceEdit;
    269 using internal::MergeType;
    270 using internal::DO_NOT_MERGE;
    271 using internal::FORCE_MERGE;
    272 using internal::MERGEABLE;
    273 
    274 /////////////////////////////////////////////////////////////////
    275 // TextfieldModel: public
    276 
    277 TextfieldModel::Delegate::~Delegate() {}
    278 
    279 TextfieldModel::TextfieldModel(Delegate* delegate)
    280     : delegate_(delegate),
    281       render_text_(gfx::RenderText::CreateInstance()),
    282       current_edit_(edit_history_.end()) {
    283 }
    284 
    285 TextfieldModel::~TextfieldModel() {
    286   ClearEditHistory();
    287   ClearComposition();
    288 }
    289 
    290 bool TextfieldModel::SetText(const base::string16& new_text) {
    291   bool changed = false;
    292   if (HasCompositionText()) {
    293     ConfirmCompositionText();
    294     changed = true;
    295   }
    296   if (text() != new_text) {
    297     if (changed)  // No need to remember composition.
    298       Undo();
    299     size_t old_cursor = GetCursorPosition();
    300     // SetText moves the cursor to the end.
    301     size_t new_cursor = new_text.length();
    302     SelectAll(false);
    303     // If there is a composition text, don't merge with previous edit.
    304     // Otherwise, force merge the edits.
    305     ExecuteAndRecordReplace(changed ? DO_NOT_MERGE : FORCE_MERGE,
    306                             old_cursor, new_cursor, new_text, 0U);
    307     render_text_->SetCursorPosition(new_cursor);
    308   }
    309   ClearSelection();
    310   return changed;
    311 }
    312 
    313 void TextfieldModel::Append(const base::string16& new_text) {
    314   if (HasCompositionText())
    315     ConfirmCompositionText();
    316   size_t save = GetCursorPosition();
    317   MoveCursor(gfx::LINE_BREAK,
    318              render_text_->GetVisualDirectionOfLogicalEnd(),
    319              false);
    320   InsertText(new_text);
    321   render_text_->SetCursorPosition(save);
    322   ClearSelection();
    323 }
    324 
    325 bool TextfieldModel::Delete() {
    326   if (HasCompositionText()) {
    327     // No undo/redo for composition text.
    328     CancelCompositionText();
    329     return true;
    330   }
    331   if (HasSelection()) {
    332     DeleteSelection();
    333     return true;
    334   }
    335   if (text().length() > GetCursorPosition()) {
    336     size_t cursor_position = GetCursorPosition();
    337     size_t next_grapheme_index = render_text_->IndexOfAdjacentGrapheme(
    338         cursor_position, gfx::CURSOR_FORWARD);
    339     ExecuteAndRecordDelete(gfx::Range(cursor_position, next_grapheme_index),
    340                            true);
    341     return true;
    342   }
    343   return false;
    344 }
    345 
    346 bool TextfieldModel::Backspace() {
    347   if (HasCompositionText()) {
    348     // No undo/redo for composition text.
    349     CancelCompositionText();
    350     return true;
    351   }
    352   if (HasSelection()) {
    353     DeleteSelection();
    354     return true;
    355   }
    356   size_t cursor_position = GetCursorPosition();
    357   if (cursor_position > 0) {
    358     // Delete one code point, which may be two UTF-16 words.
    359     size_t previous_char = gfx::UTF16OffsetToIndex(text(), cursor_position, -1);
    360     ExecuteAndRecordDelete(gfx::Range(cursor_position, previous_char), true);
    361     return true;
    362   }
    363   return false;
    364 }
    365 
    366 size_t TextfieldModel::GetCursorPosition() const {
    367   return render_text_->cursor_position();
    368 }
    369 
    370 void TextfieldModel::MoveCursor(gfx::BreakType break_type,
    371                                 gfx::VisualCursorDirection direction,
    372                                 bool select) {
    373   if (HasCompositionText())
    374     ConfirmCompositionText();
    375   render_text_->MoveCursor(break_type, direction, select);
    376 }
    377 
    378 bool TextfieldModel::MoveCursorTo(const gfx::SelectionModel& cursor) {
    379   if (HasCompositionText()) {
    380     ConfirmCompositionText();
    381     // ConfirmCompositionText() updates cursor position. Need to reflect it in
    382     // the SelectionModel parameter of MoveCursorTo().
    383     gfx::Range range(render_text_->selection().start(), cursor.caret_pos());
    384     if (!range.is_empty())
    385       return render_text_->SelectRange(range);
    386     return render_text_->MoveCursorTo(
    387         gfx::SelectionModel(cursor.caret_pos(), cursor.caret_affinity()));
    388   }
    389   return render_text_->MoveCursorTo(cursor);
    390 }
    391 
    392 bool TextfieldModel::MoveCursorTo(const gfx::Point& point, bool select) {
    393   if (HasCompositionText())
    394     ConfirmCompositionText();
    395   gfx::SelectionModel cursor = render_text_->FindCursorPosition(point);
    396   if (select)
    397     cursor.set_selection_start(render_text_->selection().start());
    398   return render_text_->MoveCursorTo(cursor);
    399 }
    400 
    401 base::string16 TextfieldModel::GetSelectedText() const {
    402   return text().substr(render_text_->selection().GetMin(),
    403                        render_text_->selection().length());
    404 }
    405 
    406 void TextfieldModel::SelectRange(const gfx::Range& range) {
    407   if (HasCompositionText())
    408     ConfirmCompositionText();
    409   render_text_->SelectRange(range);
    410 }
    411 
    412 void TextfieldModel::SelectSelectionModel(const gfx::SelectionModel& sel) {
    413   if (HasCompositionText())
    414     ConfirmCompositionText();
    415   render_text_->MoveCursorTo(sel);
    416 }
    417 
    418 void TextfieldModel::SelectAll(bool reversed) {
    419   if (HasCompositionText())
    420     ConfirmCompositionText();
    421   render_text_->SelectAll(reversed);
    422 }
    423 
    424 void TextfieldModel::SelectWord() {
    425   if (HasCompositionText())
    426     ConfirmCompositionText();
    427   render_text_->SelectWord();
    428 }
    429 
    430 void TextfieldModel::ClearSelection() {
    431   if (HasCompositionText())
    432     ConfirmCompositionText();
    433   render_text_->ClearSelection();
    434 }
    435 
    436 bool TextfieldModel::CanUndo() {
    437   return edit_history_.size() && current_edit_ != edit_history_.end();
    438 }
    439 
    440 bool TextfieldModel::CanRedo() {
    441   if (!edit_history_.size())
    442     return false;
    443   // There is no redo iff the current edit is the last element in the history.
    444   EditHistory::iterator iter = current_edit_;
    445   return iter == edit_history_.end() || // at the top.
    446       ++iter != edit_history_.end();
    447 }
    448 
    449 bool TextfieldModel::Undo() {
    450   if (!CanUndo())
    451     return false;
    452   DCHECK(!HasCompositionText());
    453   if (HasCompositionText())
    454     CancelCompositionText();
    455 
    456   base::string16 old = text();
    457   size_t old_cursor = GetCursorPosition();
    458   (*current_edit_)->Commit();
    459   (*current_edit_)->Undo(this);
    460 
    461   if (current_edit_ == edit_history_.begin())
    462     current_edit_ = edit_history_.end();
    463   else
    464     current_edit_--;
    465   return old != text() || old_cursor != GetCursorPosition();
    466 }
    467 
    468 bool TextfieldModel::Redo() {
    469   if (!CanRedo())
    470     return false;
    471   DCHECK(!HasCompositionText());
    472   if (HasCompositionText())
    473     CancelCompositionText();
    474 
    475   if (current_edit_ == edit_history_.end())
    476     current_edit_ = edit_history_.begin();
    477   else
    478     current_edit_ ++;
    479   base::string16 old = text();
    480   size_t old_cursor = GetCursorPosition();
    481   (*current_edit_)->Redo(this);
    482   return old != text() || old_cursor != GetCursorPosition();
    483 }
    484 
    485 bool TextfieldModel::Cut() {
    486   if (!HasCompositionText() && HasSelection() && !render_text_->obscured()) {
    487     ui::ScopedClipboardWriter(
    488         ui::Clipboard::GetForCurrentThread(),
    489         ui::CLIPBOARD_TYPE_COPY_PASTE).WriteText(GetSelectedText());
    490     // A trick to let undo/redo handle cursor correctly.
    491     // Undoing CUT moves the cursor to the end of the change rather
    492     // than beginning, unlike Delete/Backspace.
    493     // TODO(oshima): Change Delete/Backspace to use DeleteSelection,
    494     // update DeleteEdit and remove this trick.
    495     const gfx::Range& selection = render_text_->selection();
    496     render_text_->SelectRange(gfx::Range(selection.end(), selection.start()));
    497     DeleteSelection();
    498     return true;
    499   }
    500   return false;
    501 }
    502 
    503 bool TextfieldModel::Copy() {
    504   if (!HasCompositionText() && HasSelection() && !render_text_->obscured()) {
    505     ui::ScopedClipboardWriter(
    506         ui::Clipboard::GetForCurrentThread(),
    507         ui::CLIPBOARD_TYPE_COPY_PASTE).WriteText(GetSelectedText());
    508     return true;
    509   }
    510   return false;
    511 }
    512 
    513 bool TextfieldModel::Paste() {
    514   base::string16 result;
    515   ui::Clipboard::GetForCurrentThread()->ReadText(ui::CLIPBOARD_TYPE_COPY_PASTE,
    516                                                  &result);
    517   if (result.empty())
    518     return false;
    519 
    520   InsertTextInternal(result, false);
    521   return true;
    522 }
    523 
    524 bool TextfieldModel::HasSelection() const {
    525   return !render_text_->selection().is_empty();
    526 }
    527 
    528 void TextfieldModel::DeleteSelection() {
    529   DCHECK(!HasCompositionText());
    530   DCHECK(HasSelection());
    531   ExecuteAndRecordDelete(render_text_->selection(), false);
    532 }
    533 
    534 void TextfieldModel::DeleteSelectionAndInsertTextAt(
    535     const base::string16& new_text,
    536     size_t position) {
    537   if (HasCompositionText())
    538     CancelCompositionText();
    539   ExecuteAndRecordReplace(DO_NOT_MERGE,
    540                           GetCursorPosition(),
    541                           position + new_text.length(),
    542                           new_text,
    543                           position);
    544 }
    545 
    546 base::string16 TextfieldModel::GetTextFromRange(const gfx::Range& range) const {
    547   if (range.IsValid() && range.GetMin() < text().length())
    548     return text().substr(range.GetMin(), range.length());
    549   return base::string16();
    550 }
    551 
    552 void TextfieldModel::GetTextRange(gfx::Range* range) const {
    553   *range = gfx::Range(0, text().length());
    554 }
    555 
    556 void TextfieldModel::SetCompositionText(
    557     const ui::CompositionText& composition) {
    558   if (HasCompositionText())
    559     CancelCompositionText();
    560   else if (HasSelection())
    561     DeleteSelection();
    562 
    563   if (composition.text.empty())
    564     return;
    565 
    566   size_t cursor = GetCursorPosition();
    567   base::string16 new_text = text();
    568   render_text_->SetText(new_text.insert(cursor, composition.text));
    569   gfx::Range range(cursor, cursor + composition.text.length());
    570   render_text_->SetCompositionRange(range);
    571   gfx::Range emphasized_range = GetFirstEmphasizedRange(composition);
    572   if (emphasized_range.IsValid()) {
    573     // This is a workaround due to the lack of support in RenderText to draw
    574     // a thick underline. In a composition returned from an IME, the segment
    575     // emphasized by a thick underline usually represents the target clause.
    576     // Because the target clause is more important than the actual selection
    577     // range (or caret position) in the composition here we use a selection-like
    578     // marker instead to show this range.
    579     // TODO(yukawa, msw): Support thick underlines and remove this workaround.
    580     render_text_->SelectRange(gfx::Range(
    581         cursor + emphasized_range.GetMin(),
    582         cursor + emphasized_range.GetMax()));
    583   } else if (!composition.selection.is_empty()) {
    584     render_text_->SelectRange(gfx::Range(
    585         cursor + composition.selection.GetMin(),
    586         cursor + composition.selection.GetMax()));
    587   } else {
    588     render_text_->SetCursorPosition(cursor + composition.selection.end());
    589   }
    590 }
    591 
    592 void TextfieldModel::ConfirmCompositionText() {
    593   DCHECK(HasCompositionText());
    594   gfx::Range range = render_text_->GetCompositionRange();
    595   base::string16 composition = text().substr(range.start(), range.length());
    596   // TODO(oshima): current behavior on ChromeOS is a bit weird and not
    597   // sure exactly how this should work. Find out and fix if necessary.
    598   AddOrMergeEditHistory(new InsertEdit(false, composition, range.start()));
    599   render_text_->SetCursorPosition(range.end());
    600   ClearComposition();
    601   if (delegate_)
    602     delegate_->OnCompositionTextConfirmedOrCleared();
    603 }
    604 
    605 void TextfieldModel::CancelCompositionText() {
    606   DCHECK(HasCompositionText());
    607   gfx::Range range = render_text_->GetCompositionRange();
    608   ClearComposition();
    609   base::string16 new_text = text();
    610   render_text_->SetText(new_text.erase(range.start(), range.length()));
    611   render_text_->SetCursorPosition(range.start());
    612   if (delegate_)
    613     delegate_->OnCompositionTextConfirmedOrCleared();
    614 }
    615 
    616 void TextfieldModel::ClearComposition() {
    617   render_text_->SetCompositionRange(gfx::Range::InvalidRange());
    618 }
    619 
    620 void TextfieldModel::GetCompositionTextRange(gfx::Range* range) const {
    621   *range = gfx::Range(render_text_->GetCompositionRange());
    622 }
    623 
    624 bool TextfieldModel::HasCompositionText() const {
    625   return !render_text_->GetCompositionRange().is_empty();
    626 }
    627 
    628 void TextfieldModel::ClearEditHistory() {
    629   STLDeleteElements(&edit_history_);
    630   current_edit_ = edit_history_.end();
    631 }
    632 
    633 /////////////////////////////////////////////////////////////////
    634 // TextfieldModel: private
    635 
    636 void TextfieldModel::InsertTextInternal(const base::string16& new_text,
    637                                         bool mergeable) {
    638   if (HasCompositionText()) {
    639     CancelCompositionText();
    640     ExecuteAndRecordInsert(new_text, mergeable);
    641   } else if (HasSelection()) {
    642     ExecuteAndRecordReplaceSelection(mergeable ? MERGEABLE : DO_NOT_MERGE,
    643                                      new_text);
    644   } else {
    645     ExecuteAndRecordInsert(new_text, mergeable);
    646   }
    647 }
    648 
    649 void TextfieldModel::ReplaceTextInternal(const base::string16& new_text,
    650                                          bool mergeable) {
    651   if (HasCompositionText()) {
    652     CancelCompositionText();
    653   } else if (!HasSelection()) {
    654     size_t cursor = GetCursorPosition();
    655     const gfx::SelectionModel& model = render_text_->selection_model();
    656     // When there is no selection, the default is to replace the next grapheme
    657     // with |new_text|. So, need to find the index of next grapheme first.
    658     size_t next =
    659         render_text_->IndexOfAdjacentGrapheme(cursor, gfx::CURSOR_FORWARD);
    660     if (next == model.caret_pos())
    661       render_text_->MoveCursorTo(model);
    662     else
    663       render_text_->SelectRange(gfx::Range(next, model.caret_pos()));
    664   }
    665   // Edit history is recorded in InsertText.
    666   InsertTextInternal(new_text, mergeable);
    667 }
    668 
    669 void TextfieldModel::ClearRedoHistory() {
    670   if (edit_history_.begin() == edit_history_.end())
    671     return;
    672   if (current_edit_ == edit_history_.end()) {
    673     ClearEditHistory();
    674     return;
    675   }
    676   EditHistory::iterator delete_start = current_edit_;
    677   delete_start++;
    678   STLDeleteContainerPointers(delete_start, edit_history_.end());
    679   edit_history_.erase(delete_start, edit_history_.end());
    680 }
    681 
    682 void TextfieldModel::ExecuteAndRecordDelete(gfx::Range range, bool mergeable) {
    683   size_t old_text_start = range.GetMin();
    684   const base::string16 old_text = text().substr(old_text_start, range.length());
    685   bool backward = range.is_reversed();
    686   Edit* edit = new DeleteEdit(mergeable, old_text, old_text_start, backward);
    687   bool delete_edit = AddOrMergeEditHistory(edit);
    688   edit->Redo(this);
    689   if (delete_edit)
    690     delete edit;
    691 }
    692 
    693 void TextfieldModel::ExecuteAndRecordReplaceSelection(
    694     MergeType merge_type,
    695     const base::string16& new_text) {
    696   size_t new_text_start = render_text_->selection().GetMin();
    697   size_t new_cursor_pos = new_text_start + new_text.length();
    698   ExecuteAndRecordReplace(merge_type,
    699                           GetCursorPosition(),
    700                           new_cursor_pos,
    701                           new_text,
    702                           new_text_start);
    703 }
    704 
    705 void TextfieldModel::ExecuteAndRecordReplace(MergeType merge_type,
    706                                              size_t old_cursor_pos,
    707                                              size_t new_cursor_pos,
    708                                              const base::string16& new_text,
    709                                              size_t new_text_start) {
    710   size_t old_text_start = render_text_->selection().GetMin();
    711   bool backward = render_text_->selection().is_reversed();
    712   Edit* edit = new ReplaceEdit(merge_type,
    713                                GetSelectedText(),
    714                                old_cursor_pos,
    715                                old_text_start,
    716                                backward,
    717                                new_cursor_pos,
    718                                new_text,
    719                                new_text_start);
    720   bool delete_edit = AddOrMergeEditHistory(edit);
    721   edit->Redo(this);
    722   if (delete_edit)
    723     delete edit;
    724 }
    725 
    726 void TextfieldModel::ExecuteAndRecordInsert(const base::string16& new_text,
    727                                             bool mergeable) {
    728   Edit* edit = new InsertEdit(mergeable, new_text, GetCursorPosition());
    729   bool delete_edit = AddOrMergeEditHistory(edit);
    730   edit->Redo(this);
    731   if (delete_edit)
    732     delete edit;
    733 }
    734 
    735 bool TextfieldModel::AddOrMergeEditHistory(Edit* edit) {
    736   ClearRedoHistory();
    737 
    738   if (current_edit_ != edit_history_.end() && (*current_edit_)->Merge(edit)) {
    739     // If a current edit exists and has been merged with a new edit, don't add
    740     // to the history, and return true to delete |edit| after redo.
    741     return true;
    742   }
    743   edit_history_.push_back(edit);
    744   if (current_edit_ == edit_history_.end()) {
    745     // If there is no redoable edit, this is the 1st edit because RedoHistory
    746     // has been already deleted.
    747     DCHECK_EQ(1u, edit_history_.size());
    748     current_edit_ = edit_history_.begin();
    749   } else {
    750     current_edit_++;
    751   }
    752   return false;
    753 }
    754 
    755 void TextfieldModel::ModifyText(size_t delete_from,
    756                                 size_t delete_to,
    757                                 const base::string16& new_text,
    758                                 size_t new_text_insert_at,
    759                                 size_t new_cursor_pos) {
    760   DCHECK_LE(delete_from, delete_to);
    761   base::string16 old_text = text();
    762   ClearComposition();
    763   if (delete_from != delete_to)
    764     render_text_->SetText(old_text.erase(delete_from, delete_to - delete_from));
    765   if (!new_text.empty())
    766     render_text_->SetText(old_text.insert(new_text_insert_at, new_text));
    767   render_text_->SetCursorPosition(new_cursor_pos);
    768   // TODO(oshima): Select text that was just undone, like Mac (but not GTK).
    769 }
    770 
    771 }  // namespace views
    772