Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / ui / views / controls / textfield / textfield_model.cc
blobd270a20c0a01e8cbcaddd8a6949f208e33dd6e26
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.
5 #include "ui/views/controls/textfield/textfield_model.h"
7 #include <algorithm>
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"
16 namespace views {
18 namespace internal {
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,
31 virtual ~Edit() {}
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_);
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_);
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;
58 return mergeable() && edit->mergeable() && DoMerge(edit);
61 // Commits the edit and marks as un-mergeable.
62 void Commit() { merge_type_ = DO_NOT_MERGE; }
64 private:
65 friend class InsertEdit;
66 friend class ReplaceEdit;
67 friend class DeleteEdit;
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) {
89 // Each type of edit provides its own specific merge implementation.
90 virtual bool DoMerge(const Edit* edit) = 0;
92 Type type() const { return type_; }
94 // Can this edit be merged?
95 bool mergeable() const { return merge_type_ == MERGEABLE; }
97 // Should this edit be forcibly merged with the previous edit?
98 bool force_merge() const { return merge_type_ == FORCE_MERGE; }
100 // Returns the end index of the |old_text_|.
101 size_t old_text_end() const { return old_text_start_ + old_text_.length(); }
103 // Returns the end index of the |new_text_|.
104 size_t new_text_end() const { return new_text_start_ + new_text_.length(); }
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;
121 new_text_ = edit->new_text_;
122 new_text_start_ = edit->new_text_start_;
123 merge_type_ = DO_NOT_MERGE;
126 Type type_;
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_;
145 DISALLOW_COPY_AND_ASSIGN(Edit);
148 class InsertEdit : public Edit {
149 public:
150 InsertEdit(bool mergeable, const base::string16& new_text, size_t at)
151 : Edit(INSERT_EDIT,
152 mergeable ? MERGEABLE : DO_NOT_MERGE,
153 at /* old cursor */,
154 base::string16(),
156 false /* N/A */,
157 at + new_text.length() /* new cursor */,
158 new_text,
159 at) {
162 // Edit implementation.
163 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;
175 class ReplaceEdit : public Edit {
176 public:
177 ReplaceEdit(MergeType merge_type,
178 const base::string16& old_text,
179 size_t old_cursor_pos,
180 size_t old_text_start,
181 bool backward,
182 size_t new_cursor_pos,
183 const base::string16& new_text,
184 size_t new_text_start)
185 : Edit(REPLACE_EDIT, merge_type,
186 old_cursor_pos,
187 old_text,
188 old_text_start,
189 backward,
190 new_cursor_pos,
191 new_text,
192 new_text_start) {
195 // Edit implementation.
196 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;
208 class DeleteEdit : public Edit {
209 public:
210 DeleteEdit(bool mergeable,
211 const base::string16& text,
212 size_t text_start,
213 bool backward)
214 : Edit(DELETE_EDIT,
215 mergeable ? MERGEABLE : DO_NOT_MERGE,
216 (backward ? text_start + text.length() : text_start),
217 text,
218 text_start,
219 backward,
220 text_start,
221 base::string16(),
222 text_start) {
225 // Edit implementation.
226 bool DoMerge(const Edit* edit) override {
227 if (edit->type() != DELETE_EDIT)
228 return false;
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_;
243 return true;
247 } // namespace internal
249 namespace {
251 // Returns the first segment that is visually emphasized. Usually it's used for
252 // representing the target clause (on Windows). Returns an invalid range if
253 // there is no such a range.
254 gfx::Range GetFirstEmphasizedRange(const ui::CompositionText& composition) {
255 for (size_t i = 0; i < composition.underlines.size(); ++i) {
256 const ui::CompositionUnderline& underline = composition.underlines[i];
257 if (underline.thick)
258 return gfx::Range(underline.start_offset, underline.end_offset);
260 return gfx::Range::InvalidRange();
263 } // namespace
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;
274 /////////////////////////////////////////////////////////////////
275 // TextfieldModel: public
277 TextfieldModel::Delegate::~Delegate() {}
279 TextfieldModel::TextfieldModel(Delegate* delegate)
280 : delegate_(delegate),
281 render_text_(gfx::RenderText::CreateInstanceForEditing()),
282 current_edit_(edit_history_.end()) {
285 TextfieldModel::~TextfieldModel() {
286 ClearEditHistory();
287 ClearComposition();
290 bool TextfieldModel::SetText(const base::string16& new_text) {
291 bool changed = false;
292 if (HasCompositionText()) {
293 ConfirmCompositionText();
294 changed = true;
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);
309 ClearSelection();
310 return changed;
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();
325 bool TextfieldModel::Delete() {
326 if (HasCompositionText()) {
327 // No undo/redo for composition text.
328 CancelCompositionText();
329 return true;
331 if (HasSelection()) {
332 DeleteSelection();
333 return true;
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;
343 return false;
346 bool TextfieldModel::Backspace() {
347 if (HasCompositionText()) {
348 // No undo/redo for composition text.
349 CancelCompositionText();
350 return true;
352 if (HasSelection()) {
353 DeleteSelection();
354 return true;
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;
363 return false;
366 size_t TextfieldModel::GetCursorPosition() const {
367 return render_text_->cursor_position();
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);
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()));
389 return render_text_->MoveCursorTo(cursor);
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);
401 base::string16 TextfieldModel::GetSelectedText() const {
402 return text().substr(render_text_->selection().GetMin(),
403 render_text_->selection().length());
406 void TextfieldModel::SelectRange(const gfx::Range& range) {
407 if (HasCompositionText())
408 ConfirmCompositionText();
409 render_text_->SelectRange(range);
412 void TextfieldModel::SelectSelectionModel(const gfx::SelectionModel& sel) {
413 if (HasCompositionText())
414 ConfirmCompositionText();
415 render_text_->MoveCursorTo(sel);
418 void TextfieldModel::SelectAll(bool reversed) {
419 if (HasCompositionText())
420 ConfirmCompositionText();
421 render_text_->SelectAll(reversed);
424 void TextfieldModel::SelectWord() {
425 if (HasCompositionText())
426 ConfirmCompositionText();
427 render_text_->SelectWord();
430 void TextfieldModel::ClearSelection() {
431 if (HasCompositionText())
432 ConfirmCompositionText();
433 render_text_->ClearSelection();
436 bool TextfieldModel::CanUndo() {
437 return edit_history_.size() && current_edit_ != edit_history_.end();
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();
449 bool TextfieldModel::Undo() {
450 if (!CanUndo())
451 return false;
452 DCHECK(!HasCompositionText());
453 if (HasCompositionText())
454 CancelCompositionText();
456 base::string16 old = text();
457 size_t old_cursor = GetCursorPosition();
458 (*current_edit_)->Commit();
459 (*current_edit_)->Undo(this);
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();
468 bool TextfieldModel::Redo() {
469 if (!CanRedo())
470 return false;
471 DCHECK(!HasCompositionText());
472 if (HasCompositionText())
473 CancelCompositionText();
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();
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;
499 return false;
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;
508 return false;
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;
518 InsertTextInternal(result, false);
519 return true;
522 bool TextfieldModel::HasSelection() const {
523 return !render_text_->selection().is_empty();
526 void TextfieldModel::DeleteSelection() {
527 DCHECK(!HasCompositionText());
528 DCHECK(HasSelection());
529 ExecuteAndRecordDelete(render_text_->selection(), false);
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);
544 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();
550 void TextfieldModel::GetTextRange(gfx::Range* range) const {
551 *range = gfx::Range(0, text().length());
554 void TextfieldModel::SetCompositionText(
555 const ui::CompositionText& composition) {
556 if (HasCompositionText())
557 CancelCompositionText();
558 else if (HasSelection())
559 DeleteSelection();
561 if (composition.text.empty())
562 return;
564 size_t cursor = GetCursorPosition();
565 base::string16 new_text = text();
566 render_text_->SetText(new_text.insert(cursor, composition.text));
567 composition_range_ = gfx::Range(cursor, cursor + composition.text.length());
568 // Don't render transparent composition underlines.
569 if (composition.underlines.size() > 0 && composition.underlines[0].color != 0)
570 render_text_->SetCompositionRange(composition_range_);
571 else
572 render_text_->SetCompositionRange(gfx::Range::InvalidRange());
573 gfx::Range emphasized_range = GetFirstEmphasizedRange(composition);
574 if (emphasized_range.IsValid()) {
575 // This is a workaround due to the lack of support in RenderText to draw
576 // a thick underline. In a composition returned from an IME, the segment
577 // emphasized by a thick underline usually represents the target clause.
578 // Because the target clause is more important than the actual selection
579 // range (or caret position) in the composition here we use a selection-like
580 // marker instead to show this range.
581 // TODO(yukawa, msw): Support thick underlines and remove this workaround.
582 render_text_->SelectRange(gfx::Range(
583 cursor + emphasized_range.GetMin(),
584 cursor + emphasized_range.GetMax()));
585 } else if (!composition.selection.is_empty()) {
586 render_text_->SelectRange(gfx::Range(
587 cursor + composition.selection.GetMin(),
588 cursor + composition.selection.GetMax()));
589 } else {
590 render_text_->SetCursorPosition(cursor + composition.selection.end());
594 void TextfieldModel::ConfirmCompositionText() {
595 DCHECK(HasCompositionText());
596 base::string16 composition = text().substr(
597 composition_range_.start(), composition_range_.length());
598 // TODO(oshima): current behavior on ChromeOS is a bit weird and not
599 // sure exactly how this should work. Find out and fix if necessary.
600 AddOrMergeEditHistory(
601 new InsertEdit(false, composition, composition_range_.start()));
602 render_text_->SetCursorPosition(composition_range_.end());
603 ClearComposition();
604 if (delegate_)
605 delegate_->OnCompositionTextConfirmedOrCleared();
608 void TextfieldModel::CancelCompositionText() {
609 DCHECK(HasCompositionText());
610 gfx::Range range = composition_range_;
611 ClearComposition();
612 base::string16 new_text = text();
613 render_text_->SetText(new_text.erase(range.start(), range.length()));
614 render_text_->SetCursorPosition(range.start());
615 if (delegate_)
616 delegate_->OnCompositionTextConfirmedOrCleared();
619 void TextfieldModel::ClearComposition() {
620 composition_range_ = gfx::Range::InvalidRange();
621 render_text_->SetCompositionRange(composition_range_);
624 void TextfieldModel::GetCompositionTextRange(gfx::Range* range) const {
625 *range = composition_range_;
628 bool TextfieldModel::HasCompositionText() const {
629 return !composition_range_.is_empty();
632 void TextfieldModel::ClearEditHistory() {
633 STLDeleteElements(&edit_history_);
634 current_edit_ = edit_history_.end();
637 /////////////////////////////////////////////////////////////////
638 // TextfieldModel: private
640 void TextfieldModel::InsertTextInternal(const base::string16& new_text,
641 bool mergeable) {
642 if (HasCompositionText()) {
643 CancelCompositionText();
644 ExecuteAndRecordInsert(new_text, mergeable);
645 } else if (HasSelection()) {
646 ExecuteAndRecordReplaceSelection(mergeable ? MERGEABLE : DO_NOT_MERGE,
647 new_text);
648 } else {
649 ExecuteAndRecordInsert(new_text, mergeable);
653 void TextfieldModel::ReplaceTextInternal(const base::string16& new_text,
654 bool mergeable) {
655 if (HasCompositionText()) {
656 CancelCompositionText();
657 } else if (!HasSelection()) {
658 size_t cursor = GetCursorPosition();
659 const gfx::SelectionModel& model = render_text_->selection_model();
660 // When there is no selection, the default is to replace the next grapheme
661 // with |new_text|. So, need to find the index of next grapheme first.
662 size_t next =
663 render_text_->IndexOfAdjacentGrapheme(cursor, gfx::CURSOR_FORWARD);
664 if (next == model.caret_pos())
665 render_text_->MoveCursorTo(model);
666 else
667 render_text_->SelectRange(gfx::Range(next, model.caret_pos()));
669 // Edit history is recorded in InsertText.
670 InsertTextInternal(new_text, mergeable);
673 void TextfieldModel::ClearRedoHistory() {
674 if (edit_history_.begin() == edit_history_.end())
675 return;
676 if (current_edit_ == edit_history_.end()) {
677 ClearEditHistory();
678 return;
680 EditHistory::iterator delete_start = current_edit_;
681 delete_start++;
682 STLDeleteContainerPointers(delete_start, edit_history_.end());
683 edit_history_.erase(delete_start, edit_history_.end());
686 void TextfieldModel::ExecuteAndRecordDelete(gfx::Range range, bool mergeable) {
687 size_t old_text_start = range.GetMin();
688 const base::string16 old_text = text().substr(old_text_start, range.length());
689 bool backward = range.is_reversed();
690 Edit* edit = new DeleteEdit(mergeable, old_text, old_text_start, backward);
691 bool delete_edit = AddOrMergeEditHistory(edit);
692 edit->Redo(this);
693 if (delete_edit)
694 delete edit;
697 void TextfieldModel::ExecuteAndRecordReplaceSelection(
698 MergeType merge_type,
699 const base::string16& new_text) {
700 size_t new_text_start = render_text_->selection().GetMin();
701 size_t new_cursor_pos = new_text_start + new_text.length();
702 ExecuteAndRecordReplace(merge_type,
703 GetCursorPosition(),
704 new_cursor_pos,
705 new_text,
706 new_text_start);
709 void TextfieldModel::ExecuteAndRecordReplace(MergeType merge_type,
710 size_t old_cursor_pos,
711 size_t new_cursor_pos,
712 const base::string16& new_text,
713 size_t new_text_start) {
714 size_t old_text_start = render_text_->selection().GetMin();
715 bool backward = render_text_->selection().is_reversed();
716 Edit* edit = new ReplaceEdit(merge_type,
717 GetSelectedText(),
718 old_cursor_pos,
719 old_text_start,
720 backward,
721 new_cursor_pos,
722 new_text,
723 new_text_start);
724 bool delete_edit = AddOrMergeEditHistory(edit);
725 edit->Redo(this);
726 if (delete_edit)
727 delete edit;
730 void TextfieldModel::ExecuteAndRecordInsert(const base::string16& new_text,
731 bool mergeable) {
732 Edit* edit = new InsertEdit(mergeable, new_text, GetCursorPosition());
733 bool delete_edit = AddOrMergeEditHistory(edit);
734 edit->Redo(this);
735 if (delete_edit)
736 delete edit;
739 bool TextfieldModel::AddOrMergeEditHistory(Edit* edit) {
740 ClearRedoHistory();
742 if (current_edit_ != edit_history_.end() && (*current_edit_)->Merge(edit)) {
743 // If a current edit exists and has been merged with a new edit, don't add
744 // to the history, and return true to delete |edit| after redo.
745 return true;
747 edit_history_.push_back(edit);
748 if (current_edit_ == edit_history_.end()) {
749 // If there is no redoable edit, this is the 1st edit because RedoHistory
750 // has been already deleted.
751 DCHECK_EQ(1u, edit_history_.size());
752 current_edit_ = edit_history_.begin();
753 } else {
754 current_edit_++;
756 return false;
759 void TextfieldModel::ModifyText(size_t delete_from,
760 size_t delete_to,
761 const base::string16& new_text,
762 size_t new_text_insert_at,
763 size_t new_cursor_pos) {
764 DCHECK_LE(delete_from, delete_to);
765 base::string16 old_text = text();
766 ClearComposition();
767 if (delete_from != delete_to)
768 render_text_->SetText(old_text.erase(delete_from, delete_to - delete_from));
769 if (!new_text.empty())
770 render_text_->SetText(old_text.insert(new_text_insert_at, new_text));
771 render_text_->SetCursorPosition(new_cursor_pos);
772 // TODO(oshima): Select text that was just undone, like Mac (but not GTK).
775 } // namespace views