| Index: ui/views/controls/textfield/textfield_model.cc
|
| diff --git a/ui/views/controls/textfield/textfield_model.cc b/ui/views/controls/textfield/textfield_model.cc
|
| deleted file mode 100644
|
| index fba7e1176396ee6fba8bad67db7c4084c3380adf..0000000000000000000000000000000000000000
|
| --- a/ui/views/controls/textfield/textfield_model.cc
|
| +++ /dev/null
|
| @@ -1,769 +0,0 @@
|
| -// Copyright 2014 The Chromium Authors. All rights reserved.
|
| -// Use of this source code is governed by a BSD-style license that can be
|
| -// found in the LICENSE file.
|
| -
|
| -#include "ui/views/controls/textfield/textfield_model.h"
|
| -
|
| -#include <algorithm>
|
| -
|
| -#include "base/logging.h"
|
| -#include "base/stl_util.h"
|
| -#include "ui/base/clipboard/clipboard.h"
|
| -#include "ui/base/clipboard/scoped_clipboard_writer.h"
|
| -#include "ui/gfx/range/range.h"
|
| -#include "ui/gfx/utf16_indexing.h"
|
| -
|
| -namespace views {
|
| -
|
| -namespace internal {
|
| -
|
| -// Edit holds state information to undo/redo editing changes. Editing operations
|
| -// are merged when possible, like when characters are typed in sequence. Calling
|
| -// Commit() marks an edit as an independent operation that shouldn't be merged.
|
| -class Edit {
|
| - public:
|
| - enum Type {
|
| - INSERT_EDIT,
|
| - DELETE_EDIT,
|
| - REPLACE_EDIT,
|
| - };
|
| -
|
| - virtual ~Edit() {}
|
| -
|
| - // Revert the change made by this edit in |model|.
|
| - void Undo(TextfieldModel* model) {
|
| - model->ModifyText(new_text_start_, new_text_end(),
|
| - old_text_, old_text_start_,
|
| - old_cursor_pos_);
|
| - }
|
| -
|
| - // Apply the change of this edit to the |model|.
|
| - void Redo(TextfieldModel* model) {
|
| - model->ModifyText(old_text_start_, old_text_end(),
|
| - new_text_, new_text_start_,
|
| - new_cursor_pos_);
|
| - }
|
| -
|
| - // Try to merge the |edit| into this edit and returns true on success. The
|
| - // merged edit will be deleted after redo and should not be reused.
|
| - bool Merge(const Edit* edit) {
|
| - // Don't merge if previous edit is DELETE. This happens when a
|
| - // user deletes characters then hits return. In this case, the
|
| - // delete should be treated as separate edit that can be undone
|
| - // and should not be merged with the replace edit.
|
| - if (type_ != DELETE_EDIT && edit->force_merge()) {
|
| - MergeReplace(edit);
|
| - return true;
|
| - }
|
| - return mergeable() && edit->mergeable() && DoMerge(edit);
|
| - }
|
| -
|
| - // Commits the edit and marks as un-mergeable.
|
| - 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) {
|
| - }
|
| -
|
| - // Each type of edit provides its own specific merge implementation.
|
| - virtual bool DoMerge(const Edit* edit) = 0;
|
| -
|
| - Type type() const { return type_; }
|
| -
|
| - // Can this edit be merged?
|
| - bool mergeable() const { return merge_type_ == MERGEABLE; }
|
| -
|
| - // Should this edit be forcibly merged with the previous edit?
|
| - bool force_merge() const { return merge_type_ == FORCE_MERGE; }
|
| -
|
| - // Returns the end index of the |old_text_|.
|
| - size_t old_text_end() const { return old_text_start_ + old_text_.length(); }
|
| -
|
| - // Returns the end index of the |new_text_|.
|
| - size_t new_text_end() const { return new_text_start_ + new_text_.length(); }
|
| -
|
| - // Merge the replace edit into the current edit. This handles the special case
|
| - // where an omnibox autocomplete string is set after a new character is typed.
|
| - 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_);
|
| - // SetText() replaces entire text. Set |old_text_| to the entire
|
| - // replaced text with |this| edit undone.
|
| - 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_;
|
| -
|
| - // True if the edit can be marged.
|
| - MergeType merge_type_;
|
| - // Old cursor position.
|
| - size_t old_cursor_pos_;
|
| - // Deleted text by this edit.
|
| - base::string16 old_text_;
|
| - // The index of |old_text_|.
|
| - size_t old_text_start_;
|
| - // True if the deletion is made backward.
|
| - bool delete_backward_;
|
| - // New cursor position.
|
| - size_t new_cursor_pos_;
|
| - // Added text.
|
| - base::string16 new_text_;
|
| - // The index of |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 /* old cursor */,
|
| - base::string16(),
|
| - at,
|
| - false /* N/A */,
|
| - at + new_text.length() /* new cursor */,
|
| - new_text,
|
| - at) {
|
| - }
|
| -
|
| - // Edit implementation.
|
| - virtual bool DoMerge(const Edit* edit) override {
|
| - if (edit->type() != INSERT_EDIT || new_text_end() != edit->new_text_start_)
|
| - return false;
|
| - // If continuous edit, merge it.
|
| - // TODO(oshima): gtk splits edits between whitespace. Find out what
|
| - // we want to here and implement if necessary.
|
| - 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) {
|
| - }
|
| -
|
| - // Edit implementation.
|
| - 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) {
|
| - }
|
| -
|
| - // Edit implementation.
|
| - virtual bool DoMerge(const Edit* edit) override {
|
| - if (edit->type() != DELETE_EDIT)
|
| - return false;
|
| -
|
| - if (delete_backward_) {
|
| - // backspace can be merged only with backspace at the same position.
|
| - 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 {
|
| - // delete can be merged only with delete at the same position.
|
| - if (edit->delete_backward_ || old_text_start_ != edit->old_text_start_)
|
| - return false;
|
| - old_text_ += edit->old_text_;
|
| - }
|
| - return true;
|
| - }
|
| -};
|
| -
|
| -} // namespace internal
|
| -
|
| -namespace {
|
| -
|
| -// Returns the first segment that is visually emphasized. Usually it's used for
|
| -// representing the target clause (on Windows). Returns an invalid range if
|
| -// there is no such a range.
|
| -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();
|
| -}
|
| -
|
| -} // namespace
|
| -
|
| -using internal::Edit;
|
| -using internal::DeleteEdit;
|
| -using internal::InsertEdit;
|
| -using internal::ReplaceEdit;
|
| -using internal::MergeType;
|
| -using internal::DO_NOT_MERGE;
|
| -using internal::FORCE_MERGE;
|
| -using internal::MERGEABLE;
|
| -
|
| -/////////////////////////////////////////////////////////////////
|
| -// TextfieldModel: public
|
| -
|
| -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) // No need to remember composition.
|
| - Undo();
|
| - size_t old_cursor = GetCursorPosition();
|
| - // SetText moves the cursor to the end.
|
| - size_t new_cursor = new_text.length();
|
| - SelectAll(false);
|
| - // If there is a composition text, don't merge with previous edit.
|
| - // Otherwise, force merge the edits.
|
| - ExecuteAndRecordReplace(changed ? DO_NOT_MERGE : FORCE_MERGE,
|
| - 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()) {
|
| - // No undo/redo for composition text.
|
| - 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()) {
|
| - // No undo/redo for composition text.
|
| - CancelCompositionText();
|
| - return true;
|
| - }
|
| - if (HasSelection()) {
|
| - DeleteSelection();
|
| - return true;
|
| - }
|
| - size_t cursor_position = GetCursorPosition();
|
| - if (cursor_position > 0) {
|
| - // Delete one code point, which may be two UTF-16 words.
|
| - 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& cursor) {
|
| - if (HasCompositionText()) {
|
| - ConfirmCompositionText();
|
| - // ConfirmCompositionText() updates cursor position. Need to reflect it in
|
| - // the SelectionModel parameter of MoveCursorTo().
|
| - gfx::Range range(render_text_->selection().start(), cursor.caret_pos());
|
| - if (!range.is_empty())
|
| - return render_text_->SelectRange(range);
|
| - return render_text_->MoveCursorTo(
|
| - gfx::SelectionModel(cursor.caret_pos(), cursor.caret_affinity()));
|
| - }
|
| - return render_text_->MoveCursorTo(cursor);
|
| -}
|
| -
|
| -bool TextfieldModel::MoveCursorTo(const gfx::Point& point, bool select) {
|
| - if (HasCompositionText())
|
| - ConfirmCompositionText();
|
| - gfx::SelectionModel cursor = render_text_->FindCursorPosition(point);
|
| - if (select)
|
| - cursor.set_selection_start(render_text_->selection().start());
|
| - return render_text_->MoveCursorTo(cursor);
|
| -}
|
| -
|
| -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;
|
| - // There is no redo iff the current edit is the last element in the history.
|
| - EditHistory::iterator iter = current_edit_;
|
| - return iter == edit_history_.end() || // at the top.
|
| - ++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_TYPE_COPY_PASTE).WriteText(GetSelectedText());
|
| - // A trick to let undo/redo handle cursor correctly.
|
| - // Undoing CUT moves the cursor to the end of the change rather
|
| - // than beginning, unlike Delete/Backspace.
|
| - // TODO(oshima): Change Delete/Backspace to use DeleteSelection,
|
| - // update DeleteEdit and remove this trick.
|
| - 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_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())
|
| - return false;
|
| -
|
| - InsertTextInternal(result, false);
|
| - return true;
|
| -}
|
| -
|
| -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()) {
|
| - // This is a workaround due to the lack of support in RenderText to draw
|
| - // a thick underline. In a composition returned from an IME, the segment
|
| - // emphasized by a thick underline usually represents the target clause.
|
| - // Because the target clause is more important than the actual selection
|
| - // range (or caret position) in the composition here we use a selection-like
|
| - // marker instead to show this range.
|
| - // TODO(yukawa, msw): Support thick underlines and remove this workaround.
|
| - 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());
|
| - // TODO(oshima): current behavior on ChromeOS is a bit weird and not
|
| - // sure exactly how this should work. Find out and fix if necessary.
|
| - 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();
|
| -}
|
| -
|
| -/////////////////////////////////////////////////////////////////
|
| -// TextfieldModel: private
|
| -
|
| -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();
|
| - // When there is no selection, the default is to replace the next grapheme
|
| - // with |new_text|. So, need to find the index of next grapheme first.
|
| - 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()));
|
| - }
|
| - // Edit history is recorded in InsertText.
|
| - 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)) {
|
| - // If a current edit exists and has been merged with a new edit, don't add
|
| - // to the history, and return true to delete |edit| after redo.
|
| - return true;
|
| - }
|
| - edit_history_.push_back(edit);
|
| - if (current_edit_ == edit_history_.end()) {
|
| - // If there is no redoable edit, this is the 1st edit because RedoHistory
|
| - // has been already deleted.
|
| - 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);
|
| - // TODO(oshima): Select text that was just undone, like Mac (but not GTK).
|
| -}
|
| -
|
| -} // namespace views
|
|
|