- add sources.
[platform/framework/web/crosswalk.git] / src / ui / views / controls / textfield / textfield_views_model.cc
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