• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
~Edit()31   virtual ~Edit() {}
32 
33   // Revert the change made by this edit in |model|.
Undo(TextfieldModel * 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|.
Redo(TextfieldModel * 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.
Merge(const Edit * edit)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.
Commit()62   void Commit() { merge_type_ = DO_NOT_MERGE; }
63 
64  private:
65   friend class InsertEdit;
66   friend class ReplaceEdit;
67   friend class DeleteEdit;
68 
Edit(Type type,MergeType merge_type,size_t old_cursor_pos,const base::string16 & old_text,size_t old_text_start,bool delete_backward,size_t new_cursor_pos,const base::string16 & new_text,size_t new_text_start)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 
type() const92   Type type() const { return type_; }
93 
94   // Can this edit be merged?
mergeable() const95   bool mergeable() const { return merge_type_ == MERGEABLE; }
96 
97   // Should this edit be forcibly merged with the previous edit?
force_merge() const98   bool force_merge() const { return merge_type_ == FORCE_MERGE; }
99 
100   // Returns the end index of the |old_text_|.
old_text_end() const101   size_t old_text_end() const { return old_text_start_ + old_text_.length(); }
102 
103   // Returns the end index of the |new_text_|.
new_text_end() const104   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.
MergeReplace(const Edit * edit)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:
InsertEdit(bool mergeable,const base::string16 & new_text,size_t at)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.
DoMerge(const Edit * edit)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:
ReplaceEdit(MergeType merge_type,const base::string16 & old_text,size_t old_cursor_pos,size_t old_text_start,bool backward,size_t new_cursor_pos,const base::string16 & new_text,size_t new_text_start)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.
DoMerge(const Edit * edit)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:
DeleteEdit(bool mergeable,const base::string16 & text,size_t text_start,bool backward)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.
DoMerge(const Edit * edit)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.
GetFirstEmphasizedRange(const ui::CompositionText & composition)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 
~Delegate()277 TextfieldModel::Delegate::~Delegate() {}
278 
TextfieldModel(Delegate * delegate)279 TextfieldModel::TextfieldModel(Delegate* delegate)
280     : delegate_(delegate),
281       render_text_(gfx::RenderText::CreateInstance()),
282       current_edit_(edit_history_.end()) {
283 }
284 
~TextfieldModel()285 TextfieldModel::~TextfieldModel() {
286   ClearEditHistory();
287   ClearComposition();
288 }
289 
SetText(const base::string16 & new_text)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 
Append(const base::string16 & new_text)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 
Delete()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 
Backspace()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 
GetCursorPosition() const366 size_t TextfieldModel::GetCursorPosition() const {
367   return render_text_->cursor_position();
368 }
369 
MoveCursor(gfx::BreakType break_type,gfx::VisualCursorDirection direction,bool select)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 
MoveCursorTo(const gfx::SelectionModel & cursor)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 
MoveCursorTo(const gfx::Point & point,bool select)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 
GetSelectedText() const401 base::string16 TextfieldModel::GetSelectedText() const {
402   return text().substr(render_text_->selection().GetMin(),
403                        render_text_->selection().length());
404 }
405 
SelectRange(const gfx::Range & range)406 void TextfieldModel::SelectRange(const gfx::Range& range) {
407   if (HasCompositionText())
408     ConfirmCompositionText();
409   render_text_->SelectRange(range);
410 }
411 
SelectSelectionModel(const gfx::SelectionModel & sel)412 void TextfieldModel::SelectSelectionModel(const gfx::SelectionModel& sel) {
413   if (HasCompositionText())
414     ConfirmCompositionText();
415   render_text_->MoveCursorTo(sel);
416 }
417 
SelectAll(bool reversed)418 void TextfieldModel::SelectAll(bool reversed) {
419   if (HasCompositionText())
420     ConfirmCompositionText();
421   render_text_->SelectAll(reversed);
422 }
423 
SelectWord()424 void TextfieldModel::SelectWord() {
425   if (HasCompositionText())
426     ConfirmCompositionText();
427   render_text_->SelectWord();
428 }
429 
ClearSelection()430 void TextfieldModel::ClearSelection() {
431   if (HasCompositionText())
432     ConfirmCompositionText();
433   render_text_->ClearSelection();
434 }
435 
CanUndo()436 bool TextfieldModel::CanUndo() {
437   return edit_history_.size() && current_edit_ != edit_history_.end();
438 }
439 
CanRedo()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 
Undo()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 
Redo()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 
Cut()485 bool 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 
Copy()502 bool 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 
Paste()511 bool 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 
HasSelection() const522 bool TextfieldModel::HasSelection() const {
523   return !render_text_->selection().is_empty();
524 }
525 
DeleteSelection()526 void TextfieldModel::DeleteSelection() {
527   DCHECK(!HasCompositionText());
528   DCHECK(HasSelection());
529   ExecuteAndRecordDelete(render_text_->selection(), false);
530 }
531 
DeleteSelectionAndInsertTextAt(const base::string16 & new_text,size_t position)532 void 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 
GetTextFromRange(const gfx::Range & range) const544 base::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 
GetTextRange(gfx::Range * range) const550 void TextfieldModel::GetTextRange(gfx::Range* range) const {
551   *range = gfx::Range(0, text().length());
552 }
553 
SetCompositionText(const ui::CompositionText & composition)554 void 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 
ConfirmCompositionText()590 void 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 
CancelCompositionText()603 void 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 
ClearComposition()614 void TextfieldModel::ClearComposition() {
615   render_text_->SetCompositionRange(gfx::Range::InvalidRange());
616 }
617 
GetCompositionTextRange(gfx::Range * range) const618 void TextfieldModel::GetCompositionTextRange(gfx::Range* range) const {
619   *range = gfx::Range(render_text_->GetCompositionRange());
620 }
621 
HasCompositionText() const622 bool TextfieldModel::HasCompositionText() const {
623   return !render_text_->GetCompositionRange().is_empty();
624 }
625 
ClearEditHistory()626 void TextfieldModel::ClearEditHistory() {
627   STLDeleteElements(&edit_history_);
628   current_edit_ = edit_history_.end();
629 }
630 
631 /////////////////////////////////////////////////////////////////
632 // TextfieldModel: private
633 
InsertTextInternal(const base::string16 & new_text,bool mergeable)634 void 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 
ReplaceTextInternal(const base::string16 & new_text,bool mergeable)647 void 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 
ClearRedoHistory()667 void 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 
ExecuteAndRecordDelete(gfx::Range range,bool mergeable)680 void 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 
ExecuteAndRecordReplaceSelection(MergeType merge_type,const base::string16 & new_text)691 void 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 
ExecuteAndRecordReplace(MergeType merge_type,size_t old_cursor_pos,size_t new_cursor_pos,const base::string16 & new_text,size_t new_text_start)703 void 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 
ExecuteAndRecordInsert(const base::string16 & new_text,bool mergeable)724 void 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 
AddOrMergeEditHistory(Edit * edit)733 bool 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 
ModifyText(size_t delete_from,size_t delete_to,const base::string16 & new_text,size_t new_text_insert_at,size_t new_cursor_pos)753 void 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