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