This source file includes following definitions.
- Undo
- Redo
- Merge
- Commit
- new_text_start_
- type
- mergeable
- merge_with_previous
- old_text_end
- new_text_end
- MergeReplace
- DoMerge
- DoMerge
- DoMerge
- GetFirstEmphasizedRange
- current_edit_
- SetText
- Append
- Delete
- Backspace
- GetCursorPosition
- MoveCursor
- MoveCursorTo
- MoveCursorTo
- GetSelectedText
- SelectRange
- SelectSelectionModel
- SelectAll
- SelectWord
- ClearSelection
- CanUndo
- CanRedo
- Undo
- Redo
- Cut
- Copy
- Paste
- HasSelection
- DeleteSelection
- DeleteSelectionAndInsertTextAt
- GetTextFromRange
- GetTextRange
- SetCompositionText
- ConfirmCompositionText
- CancelCompositionText
- ClearComposition
- GetCompositionTextRange
- HasCompositionText
- ClearEditHistory
- InsertTextInternal
- ReplaceTextInternal
- ClearRedoHistory
- ExecuteAndRecordDelete
- ExecuteAndRecordReplaceSelection
- ExecuteAndRecordReplace
- ExecuteAndRecordInsert
- AddOrMergeEditHistory
- ModifyText
#include "ui/views/controls/textfield/textfield_model.h"
#include <algorithm>
#include "base/i18n/break_iterator.h"
#include "base/logging.h"
#include "base/stl_util.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/base/clipboard/clipboard.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/font.h"
#include "ui/gfx/range/range.h"
#include "ui/gfx/render_text.h"
#include "ui/gfx/text_constants.h"
#include "ui/gfx/utf16_indexing.h"
#include "ui/views/controls/textfield/textfield.h"
namespace views {
namespace internal {
class Edit {
public:
enum Type {
INSERT_EDIT,
DELETE_EDIT,
REPLACE_EDIT
};
virtual ~Edit() {
}
void Undo(TextfieldModel* model) {
model->ModifyText(new_text_start_, new_text_end(),
old_text_, old_text_start_,
old_cursor_pos_);
}
void Redo(TextfieldModel* model) {
model->ModifyText(old_text_start_, old_text_end(),
new_text_, new_text_start_,
new_cursor_pos_);
}
bool Merge(const Edit* edit) {
if (type_ != DELETE_EDIT && edit->merge_with_previous()) {
MergeReplace(edit);
return true;
}
return mergeable() && edit->mergeable() && DoMerge(edit);
}
void Commit() { merge_type_ = DO_NOT_MERGE; }
private:
friend class InsertEdit;
friend class ReplaceEdit;
friend class DeleteEdit;
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)
: type_(type),
merge_type_(merge_type),
old_cursor_pos_(old_cursor_pos),
old_text_(old_text),
old_text_start_(old_text_start),
delete_backward_(delete_backward),
new_cursor_pos_(new_cursor_pos),
new_text_(new_text),
new_text_start_(new_text_start) {
}
virtual bool DoMerge(const Edit* edit) = 0;
Type type() const { return type_; }
bool mergeable() const { return merge_type_ == MERGEABLE; }
bool merge_with_previous() const {
return merge_type_ == MERGE_WITH_PREVIOUS;
}
size_t old_text_end() const { return old_text_start_ + old_text_.length(); }
size_t new_text_end() const { return new_text_start_ + new_text_.length(); }
void MergeReplace(const Edit* edit) {
CHECK_EQ(REPLACE_EDIT, edit->type_);
CHECK_EQ(0U, edit->old_text_start_);
CHECK_EQ(0U, edit->new_text_start_);
base::string16 old_text = edit->old_text_;
old_text.erase(new_text_start_, new_text_.length());
old_text.insert(old_text_start_, old_text_);
old_text_ = old_text;
old_text_start_ = edit->old_text_start_;
delete_backward_ = false;
new_text_ = edit->new_text_;
new_text_start_ = edit->new_text_start_;
merge_type_ = DO_NOT_MERGE;
}
Type type_;
MergeType merge_type_;
size_t old_cursor_pos_;
base::string16 old_text_;
size_t old_text_start_;
bool delete_backward_;
size_t new_cursor_pos_;
base::string16 new_text_;
size_t new_text_start_;
DISALLOW_COPY_AND_ASSIGN(Edit);
};
class InsertEdit : public Edit {
public:
InsertEdit(bool mergeable, const base::string16& new_text, size_t at)
: Edit(INSERT_EDIT,
mergeable ? MERGEABLE : DO_NOT_MERGE,
at ,
base::string16(),
at,
false ,
at + new_text.length() ,
new_text,
at) {
}
virtual bool DoMerge(const Edit* edit) OVERRIDE {
if (edit->type() != INSERT_EDIT || new_text_end() != edit->new_text_start_)
return false;
new_text_ += edit->new_text_;
new_cursor_pos_ = edit->new_cursor_pos_;
return true;
}
};
class ReplaceEdit : public Edit {
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)
: Edit(REPLACE_EDIT, merge_type,
old_cursor_pos,
old_text,
old_text_start,
backward,
new_cursor_pos,
new_text,
new_text_start) {
}
virtual bool DoMerge(const Edit* edit) OVERRIDE {
if (edit->type() == DELETE_EDIT ||
new_text_end() != edit->old_text_start_ ||
edit->old_text_start_ != edit->new_text_start_)
return false;
old_text_ += edit->old_text_;
new_text_ += edit->new_text_;
new_cursor_pos_ = edit->new_cursor_pos_;
return true;
}
};
class DeleteEdit : public Edit {
public:
DeleteEdit(bool mergeable,
const base::string16& text,
size_t text_start,
bool backward)
: Edit(DELETE_EDIT,
mergeable ? MERGEABLE : DO_NOT_MERGE,
(backward ? text_start + text.length() : text_start),
text,
text_start,
backward,
text_start,
base::string16(),
text_start) {
}
virtual bool DoMerge(const Edit* edit) OVERRIDE {
if (edit->type() != DELETE_EDIT)
return false;
if (delete_backward_) {
if (!edit->delete_backward_ || old_text_start_ != edit->old_text_end())
return false;
old_text_start_ = edit->old_text_start_;
old_text_ = edit->old_text_ + old_text_;
new_cursor_pos_ = edit->new_cursor_pos_;
} else {
if (edit->delete_backward_ || old_text_start_ != edit->old_text_start_)
return false;
old_text_ += edit->old_text_;
}
return true;
}
};
}
namespace {
gfx::Range GetFirstEmphasizedRange(const ui::CompositionText& composition) {
for (size_t i = 0; i < composition.underlines.size(); ++i) {
const ui::CompositionUnderline& underline = composition.underlines[i];
if (underline.thick)
return gfx::Range(underline.start_offset, underline.end_offset);
}
return gfx::Range::InvalidRange();
}
}
using internal::Edit;
using internal::DeleteEdit;
using internal::InsertEdit;
using internal::ReplaceEdit;
using internal::MergeType;
using internal::DO_NOT_MERGE;
using internal::MERGE_WITH_PREVIOUS;
using internal::MERGEABLE;
TextfieldModel::Delegate::~Delegate() {
}
TextfieldModel::TextfieldModel(Delegate* delegate)
: delegate_(delegate),
render_text_(gfx::RenderText::CreateInstance()),
current_edit_(edit_history_.end()) {
}
TextfieldModel::~TextfieldModel() {
ClearEditHistory();
ClearComposition();
}
bool TextfieldModel::SetText(const base::string16& new_text) {
bool changed = false;
if (HasCompositionText()) {
ConfirmCompositionText();
changed = true;
}
if (text() != new_text) {
if (changed)
Undo();
size_t old_cursor = GetCursorPosition();
size_t new_cursor = new_text.length();
SelectAll(false);
ExecuteAndRecordReplace(
changed ? DO_NOT_MERGE : MERGE_WITH_PREVIOUS,
old_cursor,
new_cursor,
new_text,
0U);
render_text_->SetCursorPosition(new_cursor);
}
ClearSelection();
return changed;
}
void TextfieldModel::Append(const base::string16& new_text) {
if (HasCompositionText())
ConfirmCompositionText();
size_t save = GetCursorPosition();
MoveCursor(gfx::LINE_BREAK,
render_text_->GetVisualDirectionOfLogicalEnd(),
false);
InsertText(new_text);
render_text_->SetCursorPosition(save);
ClearSelection();
}
bool TextfieldModel::Delete() {
if (HasCompositionText()) {
CancelCompositionText();
return true;
}
if (HasSelection()) {
DeleteSelection();
return true;
}
if (text().length() > GetCursorPosition()) {
size_t cursor_position = GetCursorPosition();
size_t next_grapheme_index = render_text_->IndexOfAdjacentGrapheme(
cursor_position, gfx::CURSOR_FORWARD);
ExecuteAndRecordDelete(gfx::Range(cursor_position, next_grapheme_index),
true);
return true;
}
return false;
}
bool TextfieldModel::Backspace() {
if (HasCompositionText()) {
CancelCompositionText();
return true;
}
if (HasSelection()) {
DeleteSelection();
return true;
}
size_t cursor_position = GetCursorPosition();
if (cursor_position > 0) {
size_t previous_char = gfx::UTF16OffsetToIndex(text(), cursor_position, -1);
ExecuteAndRecordDelete(gfx::Range(cursor_position, previous_char), true);
return true;
}
return false;
}
size_t TextfieldModel::GetCursorPosition() const {
return render_text_->cursor_position();
}
void TextfieldModel::MoveCursor(gfx::BreakType break_type,
gfx::VisualCursorDirection direction,
bool select) {
if (HasCompositionText())
ConfirmCompositionText();
render_text_->MoveCursor(break_type, direction, select);
}
bool TextfieldModel::MoveCursorTo(const gfx::SelectionModel& model) {
if (HasCompositionText()) {
ConfirmCompositionText();
gfx::Range range(render_text_->selection().start(), model.caret_pos());
if (!range.is_empty())
return render_text_->SelectRange(range);
return render_text_->MoveCursorTo(
gfx::SelectionModel(model.caret_pos(), model.caret_affinity()));
}
return render_text_->MoveCursorTo(model);
}
bool TextfieldModel::MoveCursorTo(const gfx::Point& point, bool select) {
if (HasCompositionText())
ConfirmCompositionText();
return render_text_->MoveCursorTo(point, select);
}
base::string16 TextfieldModel::GetSelectedText() const {
return text().substr(render_text_->selection().GetMin(),
render_text_->selection().length());
}
void TextfieldModel::SelectRange(const gfx::Range& range) {
if (HasCompositionText())
ConfirmCompositionText();
render_text_->SelectRange(range);
}
void TextfieldModel::SelectSelectionModel(const gfx::SelectionModel& sel) {
if (HasCompositionText())
ConfirmCompositionText();
render_text_->MoveCursorTo(sel);
}
void TextfieldModel::SelectAll(bool reversed) {
if (HasCompositionText())
ConfirmCompositionText();
render_text_->SelectAll(reversed);
}
void TextfieldModel::SelectWord() {
if (HasCompositionText())
ConfirmCompositionText();
render_text_->SelectWord();
}
void TextfieldModel::ClearSelection() {
if (HasCompositionText())
ConfirmCompositionText();
render_text_->ClearSelection();
}
bool TextfieldModel::CanUndo() {
return edit_history_.size() && current_edit_ != edit_history_.end();
}
bool TextfieldModel::CanRedo() {
if (!edit_history_.size())
return false;
EditHistory::iterator iter = current_edit_;
return iter == edit_history_.end() ||
++iter != edit_history_.end();
}
bool TextfieldModel::Undo() {
if (!CanUndo())
return false;
DCHECK(!HasCompositionText());
if (HasCompositionText())
CancelCompositionText();
base::string16 old = text();
size_t old_cursor = GetCursorPosition();
(*current_edit_)->Commit();
(*current_edit_)->Undo(this);
if (current_edit_ == edit_history_.begin())
current_edit_ = edit_history_.end();
else
current_edit_--;
return old != text() || old_cursor != GetCursorPosition();
}
bool TextfieldModel::Redo() {
if (!CanRedo())
return false;
DCHECK(!HasCompositionText());
if (HasCompositionText())
CancelCompositionText();
if (current_edit_ == edit_history_.end())
current_edit_ = edit_history_.begin();
else
current_edit_ ++;
base::string16 old = text();
size_t old_cursor = GetCursorPosition();
(*current_edit_)->Redo(this);
return old != text() || old_cursor != GetCursorPosition();
}
bool TextfieldModel::Cut() {
if (!HasCompositionText() && HasSelection() && !render_text_->obscured()) {
ui::ScopedClipboardWriter(
ui::Clipboard::GetForCurrentThread(),
ui::CLIPBOARD_TYPE_COPY_PASTE).WriteText(GetSelectedText());
const gfx::Range& selection = render_text_->selection();
render_text_->SelectRange(gfx::Range(selection.end(), selection.start()));
DeleteSelection();
return true;
}
return false;
}
bool TextfieldModel::Copy() {
if (!HasCompositionText() && HasSelection() && !render_text_->obscured()) {
ui::ScopedClipboardWriter(
ui::Clipboard::GetForCurrentThread(),
ui::CLIPBOARD_TYPE_COPY_PASTE).WriteText(GetSelectedText());
return true;
}
return false;
}
bool TextfieldModel::Paste() {
base::string16 result;
ui::Clipboard::GetForCurrentThread()->ReadText(ui::CLIPBOARD_TYPE_COPY_PASTE,
&result);
if (!result.empty()) {
InsertTextInternal(result, false);
return true;
}
return false;
}
bool TextfieldModel::HasSelection() const {
return !render_text_->selection().is_empty();
}
void TextfieldModel::DeleteSelection() {
DCHECK(!HasCompositionText());
DCHECK(HasSelection());
ExecuteAndRecordDelete(render_text_->selection(), false);
}
void TextfieldModel::DeleteSelectionAndInsertTextAt(
const base::string16& new_text,
size_t position) {
if (HasCompositionText())
CancelCompositionText();
ExecuteAndRecordReplace(DO_NOT_MERGE,
GetCursorPosition(),
position + new_text.length(),
new_text,
position);
}
base::string16 TextfieldModel::GetTextFromRange(const gfx::Range& range) const {
if (range.IsValid() && range.GetMin() < text().length())
return text().substr(range.GetMin(), range.length());
return base::string16();
}
void TextfieldModel::GetTextRange(gfx::Range* range) const {
*range = gfx::Range(0, text().length());
}
void TextfieldModel::SetCompositionText(
const ui::CompositionText& composition) {
if (HasCompositionText())
CancelCompositionText();
else if (HasSelection())
DeleteSelection();
if (composition.text.empty())
return;
size_t cursor = GetCursorPosition();
base::string16 new_text = text();
render_text_->SetText(new_text.insert(cursor, composition.text));
gfx::Range range(cursor, cursor + composition.text.length());
render_text_->SetCompositionRange(range);
gfx::Range emphasized_range = GetFirstEmphasizedRange(composition);
if (emphasized_range.IsValid()) {
render_text_->SelectRange(gfx::Range(
cursor + emphasized_range.GetMin(),
cursor + emphasized_range.GetMax()));
} else if (!composition.selection.is_empty()) {
render_text_->SelectRange(gfx::Range(
cursor + composition.selection.GetMin(),
cursor + composition.selection.GetMax()));
} else {
render_text_->SetCursorPosition(cursor + composition.selection.end());
}
}
void TextfieldModel::ConfirmCompositionText() {
DCHECK(HasCompositionText());
gfx::Range range = render_text_->GetCompositionRange();
base::string16 composition = text().substr(range.start(), range.length());
AddOrMergeEditHistory(new InsertEdit(false, composition, range.start()));
render_text_->SetCursorPosition(range.end());
ClearComposition();
if (delegate_)
delegate_->OnCompositionTextConfirmedOrCleared();
}
void TextfieldModel::CancelCompositionText() {
DCHECK(HasCompositionText());
gfx::Range range = render_text_->GetCompositionRange();
ClearComposition();
base::string16 new_text = text();
render_text_->SetText(new_text.erase(range.start(), range.length()));
render_text_->SetCursorPosition(range.start());
if (delegate_)
delegate_->OnCompositionTextConfirmedOrCleared();
}
void TextfieldModel::ClearComposition() {
render_text_->SetCompositionRange(gfx::Range::InvalidRange());
}
void TextfieldModel::GetCompositionTextRange(gfx::Range* range) const {
*range = gfx::Range(render_text_->GetCompositionRange());
}
bool TextfieldModel::HasCompositionText() const {
return !render_text_->GetCompositionRange().is_empty();
}
void TextfieldModel::ClearEditHistory() {
STLDeleteElements(&edit_history_);
current_edit_ = edit_history_.end();
}
void TextfieldModel::InsertTextInternal(const base::string16& new_text,
bool mergeable) {
if (HasCompositionText()) {
CancelCompositionText();
ExecuteAndRecordInsert(new_text, mergeable);
} else if (HasSelection()) {
ExecuteAndRecordReplaceSelection(mergeable ? MERGEABLE : DO_NOT_MERGE,
new_text);
} else {
ExecuteAndRecordInsert(new_text, mergeable);
}
}
void TextfieldModel::ReplaceTextInternal(const base::string16& new_text,
bool mergeable) {
if (HasCompositionText()) {
CancelCompositionText();
} else if (!HasSelection()) {
size_t cursor = GetCursorPosition();
const gfx::SelectionModel& model = render_text_->selection_model();
size_t next =
render_text_->IndexOfAdjacentGrapheme(cursor, gfx::CURSOR_FORWARD);
if (next == model.caret_pos())
render_text_->MoveCursorTo(model);
else
render_text_->SelectRange(gfx::Range(next, model.caret_pos()));
}
InsertTextInternal(new_text, mergeable);
}
void TextfieldModel::ClearRedoHistory() {
if (edit_history_.begin() == edit_history_.end())
return;
if (current_edit_ == edit_history_.end()) {
ClearEditHistory();
return;
}
EditHistory::iterator delete_start = current_edit_;
delete_start++;
STLDeleteContainerPointers(delete_start, edit_history_.end());
edit_history_.erase(delete_start, edit_history_.end());
}
void TextfieldModel::ExecuteAndRecordDelete(gfx::Range range, bool mergeable) {
size_t old_text_start = range.GetMin();
const base::string16 old_text = text().substr(old_text_start, range.length());
bool backward = range.is_reversed();
Edit* edit = new DeleteEdit(mergeable, old_text, old_text_start, backward);
bool delete_edit = AddOrMergeEditHistory(edit);
edit->Redo(this);
if (delete_edit)
delete edit;
}
void TextfieldModel::ExecuteAndRecordReplaceSelection(
MergeType merge_type,
const base::string16& new_text) {
size_t new_text_start = render_text_->selection().GetMin();
size_t new_cursor_pos = new_text_start + new_text.length();
ExecuteAndRecordReplace(merge_type,
GetCursorPosition(),
new_cursor_pos,
new_text,
new_text_start);
}
void TextfieldModel::ExecuteAndRecordReplace(MergeType merge_type,
size_t old_cursor_pos,
size_t new_cursor_pos,
const base::string16& new_text,
size_t new_text_start) {
size_t old_text_start = render_text_->selection().GetMin();
bool backward = render_text_->selection().is_reversed();
Edit* edit = new ReplaceEdit(merge_type,
GetSelectedText(),
old_cursor_pos,
old_text_start,
backward,
new_cursor_pos,
new_text,
new_text_start);
bool delete_edit = AddOrMergeEditHistory(edit);
edit->Redo(this);
if (delete_edit)
delete edit;
}
void TextfieldModel::ExecuteAndRecordInsert(const base::string16& new_text,
bool mergeable) {
Edit* edit = new InsertEdit(mergeable, new_text, GetCursorPosition());
bool delete_edit = AddOrMergeEditHistory(edit);
edit->Redo(this);
if (delete_edit)
delete edit;
}
bool TextfieldModel::AddOrMergeEditHistory(Edit* edit) {
ClearRedoHistory();
if (current_edit_ != edit_history_.end() && (*current_edit_)->Merge(edit)) {
return true;
}
edit_history_.push_back(edit);
if (current_edit_ == edit_history_.end()) {
DCHECK_EQ(1u, edit_history_.size());
current_edit_ = edit_history_.begin();
} else {
current_edit_++;
}
return false;
}
void TextfieldModel::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) {
DCHECK_LE(delete_from, delete_to);
base::string16 old_text = text();
ClearComposition();
if (delete_from != delete_to)
render_text_->SetText(old_text.erase(delete_from, delete_to - delete_from));
if (!new_text.empty())
render_text_->SetText(old_text.insert(new_text_insert_at, new_text));
render_text_->SetCursorPosition(new_cursor_pos);
}
}