Demonstrate the basic functionality of the File System
[chromium-blink-merge.git] / ui / views / controls / textfield / textfield_model.cc
blobdb0dc73b1b08aa179129798d8d08fd5d1a99a49d
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 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;
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 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;
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 virtual 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::CreateInstance()),
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::GetForCurrentThread(),
489 ui::CLIPBOARD_TYPE_COPY_PASTE).WriteText(GetSelectedText());
490 // A trick to let undo/redo handle cursor correctly.
491 // Undoing CUT moves the cursor to the end of the change rather
492 // than beginning, unlike Delete/Backspace.
493 // TODO(oshima): Change Delete/Backspace to use DeleteSelection,
494 // update DeleteEdit and remove this trick.
495 const gfx::Range& selection = render_text_->selection();
496 render_text_->SelectRange(gfx::Range(selection.end(), selection.start()));
497 DeleteSelection();
498 return true;
500 return false;
503 bool TextfieldModel::Copy() {
504 if (!HasCompositionText() && HasSelection() && !render_text_->obscured()) {
505 ui::ScopedClipboardWriter(
506 ui::Clipboard::GetForCurrentThread(),
507 ui::CLIPBOARD_TYPE_COPY_PASTE).WriteText(GetSelectedText());
508 return true;
510 return false;
513 bool TextfieldModel::Paste() {
514 base::string16 result;
515 ui::Clipboard::GetForCurrentThread()->ReadText(ui::CLIPBOARD_TYPE_COPY_PASTE,
516 &result);
517 if (result.empty())
518 return false;
520 InsertTextInternal(result, false);
521 return true;
524 bool TextfieldModel::HasSelection() const {
525 return !render_text_->selection().is_empty();
528 void TextfieldModel::DeleteSelection() {
529 DCHECK(!HasCompositionText());
530 DCHECK(HasSelection());
531 ExecuteAndRecordDelete(render_text_->selection(), false);
534 void TextfieldModel::DeleteSelectionAndInsertTextAt(
535 const base::string16& new_text,
536 size_t position) {
537 if (HasCompositionText())
538 CancelCompositionText();
539 ExecuteAndRecordReplace(DO_NOT_MERGE,
540 GetCursorPosition(),
541 position + new_text.length(),
542 new_text,
543 position);
546 base::string16 TextfieldModel::GetTextFromRange(const gfx::Range& range) const {
547 if (range.IsValid() && range.GetMin() < text().length())
548 return text().substr(range.GetMin(), range.length());
549 return base::string16();
552 void TextfieldModel::GetTextRange(gfx::Range* range) const {
553 *range = gfx::Range(0, text().length());
556 void TextfieldModel::SetCompositionText(
557 const ui::CompositionText& composition) {
558 if (HasCompositionText())
559 CancelCompositionText();
560 else if (HasSelection())
561 DeleteSelection();
563 if (composition.text.empty())
564 return;
566 size_t cursor = GetCursorPosition();
567 base::string16 new_text = text();
568 render_text_->SetText(new_text.insert(cursor, composition.text));
569 gfx::Range range(cursor, cursor + composition.text.length());
570 render_text_->SetCompositionRange(range);
571 gfx::Range emphasized_range = GetFirstEmphasizedRange(composition);
572 if (emphasized_range.IsValid()) {
573 // This is a workaround due to the lack of support in RenderText to draw
574 // a thick underline. In a composition returned from an IME, the segment
575 // emphasized by a thick underline usually represents the target clause.
576 // Because the target clause is more important than the actual selection
577 // range (or caret position) in the composition here we use a selection-like
578 // marker instead to show this range.
579 // TODO(yukawa, msw): Support thick underlines and remove this workaround.
580 render_text_->SelectRange(gfx::Range(
581 cursor + emphasized_range.GetMin(),
582 cursor + emphasized_range.GetMax()));
583 } else if (!composition.selection.is_empty()) {
584 render_text_->SelectRange(gfx::Range(
585 cursor + composition.selection.GetMin(),
586 cursor + composition.selection.GetMax()));
587 } else {
588 render_text_->SetCursorPosition(cursor + composition.selection.end());
592 void TextfieldModel::ConfirmCompositionText() {
593 DCHECK(HasCompositionText());
594 gfx::Range range = render_text_->GetCompositionRange();
595 base::string16 composition = text().substr(range.start(), range.length());
596 // TODO(oshima): current behavior on ChromeOS is a bit weird and not
597 // sure exactly how this should work. Find out and fix if necessary.
598 AddOrMergeEditHistory(new InsertEdit(false, composition, range.start()));
599 render_text_->SetCursorPosition(range.end());
600 ClearComposition();
601 if (delegate_)
602 delegate_->OnCompositionTextConfirmedOrCleared();
605 void TextfieldModel::CancelCompositionText() {
606 DCHECK(HasCompositionText());
607 gfx::Range range = render_text_->GetCompositionRange();
608 ClearComposition();
609 base::string16 new_text = text();
610 render_text_->SetText(new_text.erase(range.start(), range.length()));
611 render_text_->SetCursorPosition(range.start());
612 if (delegate_)
613 delegate_->OnCompositionTextConfirmedOrCleared();
616 void TextfieldModel::ClearComposition() {
617 render_text_->SetCompositionRange(gfx::Range::InvalidRange());
620 void TextfieldModel::GetCompositionTextRange(gfx::Range* range) const {
621 *range = gfx::Range(render_text_->GetCompositionRange());
624 bool TextfieldModel::HasCompositionText() const {
625 return !render_text_->GetCompositionRange().is_empty();
628 void TextfieldModel::ClearEditHistory() {
629 STLDeleteElements(&edit_history_);
630 current_edit_ = edit_history_.end();
633 /////////////////////////////////////////////////////////////////
634 // TextfieldModel: private
636 void TextfieldModel::InsertTextInternal(const base::string16& new_text,
637 bool mergeable) {
638 if (HasCompositionText()) {
639 CancelCompositionText();
640 ExecuteAndRecordInsert(new_text, mergeable);
641 } else if (HasSelection()) {
642 ExecuteAndRecordReplaceSelection(mergeable ? MERGEABLE : DO_NOT_MERGE,
643 new_text);
644 } else {
645 ExecuteAndRecordInsert(new_text, mergeable);
649 void TextfieldModel::ReplaceTextInternal(const base::string16& new_text,
650 bool mergeable) {
651 if (HasCompositionText()) {
652 CancelCompositionText();
653 } else if (!HasSelection()) {
654 size_t cursor = GetCursorPosition();
655 const gfx::SelectionModel& model = render_text_->selection_model();
656 // When there is no selection, the default is to replace the next grapheme
657 // with |new_text|. So, need to find the index of next grapheme first.
658 size_t next =
659 render_text_->IndexOfAdjacentGrapheme(cursor, gfx::CURSOR_FORWARD);
660 if (next == model.caret_pos())
661 render_text_->MoveCursorTo(model);
662 else
663 render_text_->SelectRange(gfx::Range(next, model.caret_pos()));
665 // Edit history is recorded in InsertText.
666 InsertTextInternal(new_text, mergeable);
669 void TextfieldModel::ClearRedoHistory() {
670 if (edit_history_.begin() == edit_history_.end())
671 return;
672 if (current_edit_ == edit_history_.end()) {
673 ClearEditHistory();
674 return;
676 EditHistory::iterator delete_start = current_edit_;
677 delete_start++;
678 STLDeleteContainerPointers(delete_start, edit_history_.end());
679 edit_history_.erase(delete_start, edit_history_.end());
682 void TextfieldModel::ExecuteAndRecordDelete(gfx::Range range, bool mergeable) {
683 size_t old_text_start = range.GetMin();
684 const base::string16 old_text = text().substr(old_text_start, range.length());
685 bool backward = range.is_reversed();
686 Edit* edit = new DeleteEdit(mergeable, old_text, old_text_start, backward);
687 bool delete_edit = AddOrMergeEditHistory(edit);
688 edit->Redo(this);
689 if (delete_edit)
690 delete edit;
693 void TextfieldModel::ExecuteAndRecordReplaceSelection(
694 MergeType merge_type,
695 const base::string16& new_text) {
696 size_t new_text_start = render_text_->selection().GetMin();
697 size_t new_cursor_pos = new_text_start + new_text.length();
698 ExecuteAndRecordReplace(merge_type,
699 GetCursorPosition(),
700 new_cursor_pos,
701 new_text,
702 new_text_start);
705 void TextfieldModel::ExecuteAndRecordReplace(MergeType merge_type,
706 size_t old_cursor_pos,
707 size_t new_cursor_pos,
708 const base::string16& new_text,
709 size_t new_text_start) {
710 size_t old_text_start = render_text_->selection().GetMin();
711 bool backward = render_text_->selection().is_reversed();
712 Edit* edit = new ReplaceEdit(merge_type,
713 GetSelectedText(),
714 old_cursor_pos,
715 old_text_start,
716 backward,
717 new_cursor_pos,
718 new_text,
719 new_text_start);
720 bool delete_edit = AddOrMergeEditHistory(edit);
721 edit->Redo(this);
722 if (delete_edit)
723 delete edit;
726 void TextfieldModel::ExecuteAndRecordInsert(const base::string16& new_text,
727 bool mergeable) {
728 Edit* edit = new InsertEdit(mergeable, new_text, GetCursorPosition());
729 bool delete_edit = AddOrMergeEditHistory(edit);
730 edit->Redo(this);
731 if (delete_edit)
732 delete edit;
735 bool TextfieldModel::AddOrMergeEditHistory(Edit* edit) {
736 ClearRedoHistory();
738 if (current_edit_ != edit_history_.end() && (*current_edit_)->Merge(edit)) {
739 // If a current edit exists and has been merged with a new edit, don't add
740 // to the history, and return true to delete |edit| after redo.
741 return true;
743 edit_history_.push_back(edit);
744 if (current_edit_ == edit_history_.end()) {
745 // If there is no redoable edit, this is the 1st edit because RedoHistory
746 // has been already deleted.
747 DCHECK_EQ(1u, edit_history_.size());
748 current_edit_ = edit_history_.begin();
749 } else {
750 current_edit_++;
752 return false;
755 void TextfieldModel::ModifyText(size_t delete_from,
756 size_t delete_to,
757 const base::string16& new_text,
758 size_t new_text_insert_at,
759 size_t new_cursor_pos) {
760 DCHECK_LE(delete_from, delete_to);
761 base::string16 old_text = text();
762 ClearComposition();
763 if (delete_from != delete_to)
764 render_text_->SetText(old_text.erase(delete_from, delete_to - delete_from));
765 if (!new_text.empty())
766 render_text_->SetText(old_text.insert(new_text_insert_at, new_text));
767 render_text_->SetCursorPosition(new_cursor_pos);
768 // TODO(oshima): Select text that was just undone, like Mac (but not GTK).
771 } // namespace views