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
16namespace views {
17
18namespace 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.
23class 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
148class 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
175class 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
208class 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
249namespace {
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.
254gfx::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
265using internal::Edit;
266using internal::DeleteEdit;
267using internal::InsertEdit;
268using internal::ReplaceEdit;
269using internal::MergeType;
270using internal::DO_NOT_MERGE;
271using internal::FORCE_MERGE;
272using internal::MERGEABLE;
273
274/////////////////////////////////////////////////////////////////
275// TextfieldModel: public
276
277TextfieldModel::Delegate::~Delegate() {}
278
279TextfieldModel::TextfieldModel(Delegate* delegate)
280    : delegate_(delegate),
281      render_text_(gfx::RenderText::CreateInstance()),
282      current_edit_(edit_history_.end()) {
283}
284
285TextfieldModel::~TextfieldModel() {
286  ClearEditHistory();
287  ClearComposition();
288}
289
290bool 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
313void 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
325bool 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
346bool 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
366size_t TextfieldModel::GetCursorPosition() const {
367  return render_text_->cursor_position();
368}
369
370void 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
378bool 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
392bool 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
401base::string16 TextfieldModel::GetSelectedText() const {
402  return text().substr(render_text_->selection().GetMin(),
403                       render_text_->selection().length());
404}
405
406void TextfieldModel::SelectRange(const gfx::Range& range) {
407  if (HasCompositionText())
408    ConfirmCompositionText();
409  render_text_->SelectRange(range);
410}
411
412void TextfieldModel::SelectSelectionModel(const gfx::SelectionModel& sel) {
413  if (HasCompositionText())
414    ConfirmCompositionText();
415  render_text_->MoveCursorTo(sel);
416}
417
418void TextfieldModel::SelectAll(bool reversed) {
419  if (HasCompositionText())
420    ConfirmCompositionText();
421  render_text_->SelectAll(reversed);
422}
423
424void TextfieldModel::SelectWord() {
425  if (HasCompositionText())
426    ConfirmCompositionText();
427  render_text_->SelectWord();
428}
429
430void TextfieldModel::ClearSelection() {
431  if (HasCompositionText())
432    ConfirmCompositionText();
433  render_text_->ClearSelection();
434}
435
436bool TextfieldModel::CanUndo() {
437  return edit_history_.size() && current_edit_ != edit_history_.end();
438}
439
440bool 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
449bool 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
468bool 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
485bool TextfieldModel::Cut() {
486  if (!HasCompositionText() && HasSelection() && !render_text_->obscured()) {
487    ui::ScopedClipboardWriter(
488        ui::CLIPBOARD_TYPE_COPY_PASTE).WriteText(GetSelectedText());
489    // A trick to let undo/redo handle cursor correctly.
490    // Undoing CUT moves the cursor to the end of the change rather
491    // than beginning, unlike Delete/Backspace.
492    // TODO(oshima): Change Delete/Backspace to use DeleteSelection,
493    // update DeleteEdit and remove this trick.
494    const gfx::Range& selection = render_text_->selection();
495    render_text_->SelectRange(gfx::Range(selection.end(), selection.start()));
496    DeleteSelection();
497    return true;
498  }
499  return false;
500}
501
502bool TextfieldModel::Copy() {
503  if (!HasCompositionText() && HasSelection() && !render_text_->obscured()) {
504    ui::ScopedClipboardWriter(
505        ui::CLIPBOARD_TYPE_COPY_PASTE).WriteText(GetSelectedText());
506    return true;
507  }
508  return false;
509}
510
511bool TextfieldModel::Paste() {
512  base::string16 result;
513  ui::Clipboard::GetForCurrentThread()->ReadText(ui::CLIPBOARD_TYPE_COPY_PASTE,
514                                                 &result);
515  if (result.empty())
516    return false;
517
518  InsertTextInternal(result, false);
519  return true;
520}
521
522bool TextfieldModel::HasSelection() const {
523  return !render_text_->selection().is_empty();
524}
525
526void TextfieldModel::DeleteSelection() {
527  DCHECK(!HasCompositionText());
528  DCHECK(HasSelection());
529  ExecuteAndRecordDelete(render_text_->selection(), false);
530}
531
532void TextfieldModel::DeleteSelectionAndInsertTextAt(
533    const base::string16& new_text,
534    size_t position) {
535  if (HasCompositionText())
536    CancelCompositionText();
537  ExecuteAndRecordReplace(DO_NOT_MERGE,
538                          GetCursorPosition(),
539                          position + new_text.length(),
540                          new_text,
541                          position);
542}
543
544base::string16 TextfieldModel::GetTextFromRange(const gfx::Range& range) const {
545  if (range.IsValid() && range.GetMin() < text().length())
546    return text().substr(range.GetMin(), range.length());
547  return base::string16();
548}
549
550void TextfieldModel::GetTextRange(gfx::Range* range) const {
551  *range = gfx::Range(0, text().length());
552}
553
554void TextfieldModel::SetCompositionText(
555    const ui::CompositionText& composition) {
556  if (HasCompositionText())
557    CancelCompositionText();
558  else if (HasSelection())
559    DeleteSelection();
560
561  if (composition.text.empty())
562    return;
563
564  size_t cursor = GetCursorPosition();
565  base::string16 new_text = text();
566  render_text_->SetText(new_text.insert(cursor, composition.text));
567  gfx::Range range(cursor, cursor + composition.text.length());
568  render_text_->SetCompositionRange(range);
569  gfx::Range emphasized_range = GetFirstEmphasizedRange(composition);
570  if (emphasized_range.IsValid()) {
571    // This is a workaround due to the lack of support in RenderText to draw
572    // a thick underline. In a composition returned from an IME, the segment
573    // emphasized by a thick underline usually represents the target clause.
574    // Because the target clause is more important than the actual selection
575    // range (or caret position) in the composition here we use a selection-like
576    // marker instead to show this range.
577    // TODO(yukawa, msw): Support thick underlines and remove this workaround.
578    render_text_->SelectRange(gfx::Range(
579        cursor + emphasized_range.GetMin(),
580        cursor + emphasized_range.GetMax()));
581  } else if (!composition.selection.is_empty()) {
582    render_text_->SelectRange(gfx::Range(
583        cursor + composition.selection.GetMin(),
584        cursor + composition.selection.GetMax()));
585  } else {
586    render_text_->SetCursorPosition(cursor + composition.selection.end());
587  }
588}
589
590void TextfieldModel::ConfirmCompositionText() {
591  DCHECK(HasCompositionText());
592  gfx::Range range = render_text_->GetCompositionRange();
593  base::string16 composition = text().substr(range.start(), range.length());
594  // TODO(oshima): current behavior on ChromeOS is a bit weird and not
595  // sure exactly how this should work. Find out and fix if necessary.
596  AddOrMergeEditHistory(new InsertEdit(false, composition, range.start()));
597  render_text_->SetCursorPosition(range.end());
598  ClearComposition();
599  if (delegate_)
600    delegate_->OnCompositionTextConfirmedOrCleared();
601}
602
603void TextfieldModel::CancelCompositionText() {
604  DCHECK(HasCompositionText());
605  gfx::Range range = render_text_->GetCompositionRange();
606  ClearComposition();
607  base::string16 new_text = text();
608  render_text_->SetText(new_text.erase(range.start(), range.length()));
609  render_text_->SetCursorPosition(range.start());
610  if (delegate_)
611    delegate_->OnCompositionTextConfirmedOrCleared();
612}
613
614void TextfieldModel::ClearComposition() {
615  render_text_->SetCompositionRange(gfx::Range::InvalidRange());
616}
617
618void TextfieldModel::GetCompositionTextRange(gfx::Range* range) const {
619  *range = gfx::Range(render_text_->GetCompositionRange());
620}
621
622bool TextfieldModel::HasCompositionText() const {
623  return !render_text_->GetCompositionRange().is_empty();
624}
625
626void TextfieldModel::ClearEditHistory() {
627  STLDeleteElements(&edit_history_);
628  current_edit_ = edit_history_.end();
629}
630
631/////////////////////////////////////////////////////////////////
632// TextfieldModel: private
633
634void TextfieldModel::InsertTextInternal(const base::string16& new_text,
635                                        bool mergeable) {
636  if (HasCompositionText()) {
637    CancelCompositionText();
638    ExecuteAndRecordInsert(new_text, mergeable);
639  } else if (HasSelection()) {
640    ExecuteAndRecordReplaceSelection(mergeable ? MERGEABLE : DO_NOT_MERGE,
641                                     new_text);
642  } else {
643    ExecuteAndRecordInsert(new_text, mergeable);
644  }
645}
646
647void TextfieldModel::ReplaceTextInternal(const base::string16& new_text,
648                                         bool mergeable) {
649  if (HasCompositionText()) {
650    CancelCompositionText();
651  } else if (!HasSelection()) {
652    size_t cursor = GetCursorPosition();
653    const gfx::SelectionModel& model = render_text_->selection_model();
654    // When there is no selection, the default is to replace the next grapheme
655    // with |new_text|. So, need to find the index of next grapheme first.
656    size_t next =
657        render_text_->IndexOfAdjacentGrapheme(cursor, gfx::CURSOR_FORWARD);
658    if (next == model.caret_pos())
659      render_text_->MoveCursorTo(model);
660    else
661      render_text_->SelectRange(gfx::Range(next, model.caret_pos()));
662  }
663  // Edit history is recorded in InsertText.
664  InsertTextInternal(new_text, mergeable);
665}
666
667void TextfieldModel::ClearRedoHistory() {
668  if (edit_history_.begin() == edit_history_.end())
669    return;
670  if (current_edit_ == edit_history_.end()) {
671    ClearEditHistory();
672    return;
673  }
674  EditHistory::iterator delete_start = current_edit_;
675  delete_start++;
676  STLDeleteContainerPointers(delete_start, edit_history_.end());
677  edit_history_.erase(delete_start, edit_history_.end());
678}
679
680void TextfieldModel::ExecuteAndRecordDelete(gfx::Range range, bool mergeable) {
681  size_t old_text_start = range.GetMin();
682  const base::string16 old_text = text().substr(old_text_start, range.length());
683  bool backward = range.is_reversed();
684  Edit* edit = new DeleteEdit(mergeable, old_text, old_text_start, backward);
685  bool delete_edit = AddOrMergeEditHistory(edit);
686  edit->Redo(this);
687  if (delete_edit)
688    delete edit;
689}
690
691void TextfieldModel::ExecuteAndRecordReplaceSelection(
692    MergeType merge_type,
693    const base::string16& new_text) {
694  size_t new_text_start = render_text_->selection().GetMin();
695  size_t new_cursor_pos = new_text_start + new_text.length();
696  ExecuteAndRecordReplace(merge_type,
697                          GetCursorPosition(),
698                          new_cursor_pos,
699                          new_text,
700                          new_text_start);
701}
702
703void TextfieldModel::ExecuteAndRecordReplace(MergeType merge_type,
704                                             size_t old_cursor_pos,
705                                             size_t new_cursor_pos,
706                                             const base::string16& new_text,
707                                             size_t new_text_start) {
708  size_t old_text_start = render_text_->selection().GetMin();
709  bool backward = render_text_->selection().is_reversed();
710  Edit* edit = new ReplaceEdit(merge_type,
711                               GetSelectedText(),
712                               old_cursor_pos,
713                               old_text_start,
714                               backward,
715                               new_cursor_pos,
716                               new_text,
717                               new_text_start);
718  bool delete_edit = AddOrMergeEditHistory(edit);
719  edit->Redo(this);
720  if (delete_edit)
721    delete edit;
722}
723
724void TextfieldModel::ExecuteAndRecordInsert(const base::string16& new_text,
725                                            bool mergeable) {
726  Edit* edit = new InsertEdit(mergeable, new_text, GetCursorPosition());
727  bool delete_edit = AddOrMergeEditHistory(edit);
728  edit->Redo(this);
729  if (delete_edit)
730    delete edit;
731}
732
733bool TextfieldModel::AddOrMergeEditHistory(Edit* edit) {
734  ClearRedoHistory();
735
736  if (current_edit_ != edit_history_.end() && (*current_edit_)->Merge(edit)) {
737    // If a current edit exists and has been merged with a new edit, don't add
738    // to the history, and return true to delete |edit| after redo.
739    return true;
740  }
741  edit_history_.push_back(edit);
742  if (current_edit_ == edit_history_.end()) {
743    // If there is no redoable edit, this is the 1st edit because RedoHistory
744    // has been already deleted.
745    DCHECK_EQ(1u, edit_history_.size());
746    current_edit_ = edit_history_.begin();
747  } else {
748    current_edit_++;
749  }
750  return false;
751}
752
753void TextfieldModel::ModifyText(size_t delete_from,
754                                size_t delete_to,
755                                const base::string16& new_text,
756                                size_t new_text_insert_at,
757                                size_t new_cursor_pos) {
758  DCHECK_LE(delete_from, delete_to);
759  base::string16 old_text = text();
760  ClearComposition();
761  if (delete_from != delete_to)
762    render_text_->SetText(old_text.erase(delete_from, delete_to - delete_from));
763  if (!new_text.empty())
764    render_text_->SetText(old_text.insert(new_text_insert_at, new_text));
765  render_text_->SetCursorPosition(new_cursor_pos);
766  // TODO(oshima): Select text that was just undone, like Mac (but not GTK).
767}
768
769}  // namespace views
770