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