| Index: ui/views/controls/textfield/textfield_views_model.cc
|
| diff --git a/ui/views/controls/textfield/textfield_views_model.cc b/ui/views/controls/textfield/textfield_views_model.cc
|
| index d0fc8e01aec4c841e6429b8b3b7241661718158a..1eda7aa9dcf10d232d685e559853e7481ded8055 100644
|
| --- a/ui/views/controls/textfield/textfield_views_model.cc
|
| +++ b/ui/views/controls/textfield/textfield_views_model.cc
|
| @@ -9,6 +9,7 @@
|
| #include "base/i18n/break_iterator.h"
|
| #include "base/logging.h"
|
| #include "base/stl_util.h"
|
| +#include "base/utf_offset_string_conversions.h"
|
| #include "base/utf_string_conversions.h"
|
| #include "ui/base/clipboard/clipboard.h"
|
| #include "ui/base/clipboard/scoped_clipboard_writer.h"
|
| @@ -23,6 +24,11 @@ namespace views {
|
|
|
| namespace internal {
|
|
|
| +// All chars are replaced by this char when the password style is set.
|
| +// TODO(benrg): GTK uses the first of U+25CF, U+2022, U+2731, U+273A, '*'
|
| +// that's available in the font (find_invisible_char() in gtkentry.c).
|
| +const char16 kPasswordReplacementChar = '*';
|
| +
|
| // An edit object holds enough information/state to undo/redo the
|
| // change. Two edits are merged when possible, for example, when
|
| // you type new characters in sequence. |Commit()| can be used to
|
| @@ -289,10 +295,6 @@ TextfieldViewsModel::~TextfieldViewsModel() {
|
| ClearComposition();
|
| }
|
|
|
| -const string16& TextfieldViewsModel::GetText() const {
|
| - return render_text_->text();
|
| -}
|
| -
|
| bool TextfieldViewsModel::SetText(const string16& text) {
|
| bool changed = false;
|
| if (HasCompositionText()) {
|
| @@ -313,16 +315,23 @@ bool TextfieldViewsModel::SetText(const string16& text) {
|
| new_cursor,
|
| text,
|
| 0U);
|
| - render_text_->SetCursorPosition(new_cursor);
|
| + render_text_->SetCursorPosition(ToDisplayIndex(new_cursor));
|
| }
|
| ClearSelection();
|
| return changed;
|
| }
|
|
|
| +void TextfieldViewsModel::SetObscured(bool obscured) {
|
| + if (obscured != obscured_) {
|
| + obscured_ = obscured;
|
| + render_text_->SetText(GetDisplayText());
|
| + }
|
| +}
|
| +
|
| void TextfieldViewsModel::Append(const string16& text) {
|
| if (HasCompositionText())
|
| ConfirmCompositionText();
|
| - size_t save = GetCursorPosition();
|
| + size_t save = render_text_->GetCursorPosition();
|
| MoveCursor(gfx::LINE_BREAK,
|
| render_text_->GetVisualDirectionOfLogicalEnd(),
|
| false);
|
| @@ -343,8 +352,9 @@ bool TextfieldViewsModel::Delete() {
|
| }
|
| if (GetText().length() > GetCursorPosition()) {
|
| size_t cursor_position = GetCursorPosition();
|
| - size_t next_grapheme_index = render_text_->IndexOfAdjacentGrapheme(
|
| - cursor_position, gfx::CURSOR_FORWARD);
|
| + size_t next_grapheme_index =
|
| + FromDisplayIndex(render_text_->IndexOfAdjacentGrapheme(
|
| + ToDisplayIndex(cursor_position), gfx::CURSOR_FORWARD));
|
| ExecuteAndRecordDelete(cursor_position, next_grapheme_index, true);
|
| return true;
|
| }
|
| @@ -361,16 +371,17 @@ bool TextfieldViewsModel::Backspace() {
|
| DeleteSelection();
|
| return true;
|
| }
|
| - if (GetCursorPosition() > 0) {
|
| - size_t cursor_position = GetCursorPosition();
|
| - ExecuteAndRecordDelete(cursor_position, cursor_position - 1, true);
|
| + size_t cursor_position = GetCursorPosition();
|
| + if (cursor_position > 0) {
|
| + size_t previous_char = Utf16OffsetToIndex(GetText(), cursor_position, -1);
|
| + ExecuteAndRecordDelete(cursor_position, previous_char, true);
|
| return true;
|
| }
|
| return false;
|
| }
|
|
|
| size_t TextfieldViewsModel::GetCursorPosition() const {
|
| - return render_text_->GetCursorPosition();
|
| + return FromDisplayIndex(render_text_->GetCursorPosition());
|
| }
|
|
|
| void TextfieldViewsModel::MoveCursor(gfx::BreakType break_type,
|
| @@ -381,7 +392,8 @@ void TextfieldViewsModel::MoveCursor(gfx::BreakType break_type,
|
| render_text_->MoveCursor(break_type, direction, select);
|
| }
|
|
|
| -bool TextfieldViewsModel::MoveCursorTo(const gfx::SelectionModel& selection) {
|
| +bool TextfieldViewsModel::MoveCursorTo(const gfx::SelectionModel& sel) {
|
| + gfx::SelectionModel selection = ToDisplaySelection(sel);
|
| if (HasCompositionText()) {
|
| ConfirmCompositionText();
|
| // ConfirmCompositionText() updates cursor position. Need to reflect it in
|
| @@ -404,29 +416,30 @@ bool TextfieldViewsModel::MoveCursorTo(const gfx::Point& point, bool select) {
|
| }
|
|
|
| string16 TextfieldViewsModel::GetSelectedText() const {
|
| - return GetText().substr(render_text_->MinOfSelection(),
|
| - (render_text_->MaxOfSelection() - render_text_->MinOfSelection()));
|
| + size_t lo = FromDisplayIndex(render_text_->MinOfSelection());
|
| + size_t hi = FromDisplayIndex(render_text_->MaxOfSelection());
|
| + return GetText().substr(lo, hi - lo);
|
| }
|
|
|
| void TextfieldViewsModel::GetSelectedRange(ui::Range* range) const {
|
| - range->set_start(render_text_->GetSelectionStart());
|
| - range->set_end(render_text_->GetCursorPosition());
|
| + *range = FromDisplayRange(ui::Range(
|
| + render_text_->GetSelectionStart(), render_text_->GetCursorPosition()));
|
| }
|
|
|
| void TextfieldViewsModel::SelectRange(const ui::Range& range) {
|
| if (HasCompositionText())
|
| ConfirmCompositionText();
|
| - render_text_->SelectRange(range);
|
| + render_text_->SelectRange(ToDisplayRange(range));
|
| }
|
|
|
| void TextfieldViewsModel::GetSelectionModel(gfx::SelectionModel* sel) const {
|
| - *sel = render_text_->selection_model();
|
| + *sel = FromDisplaySelection(render_text_->selection_model());
|
| }
|
|
|
| void TextfieldViewsModel::SelectSelectionModel(const gfx::SelectionModel& sel) {
|
| if (HasCompositionText())
|
| ConfirmCompositionText();
|
| - render_text_->MoveCursorTo(sel);
|
| + render_text_->MoveCursorTo(ToDisplaySelection(sel));
|
| }
|
|
|
| void TextfieldViewsModel::SelectAll() {
|
| @@ -498,7 +511,7 @@ bool TextfieldViewsModel::Redo() {
|
| }
|
|
|
| bool TextfieldViewsModel::Cut() {
|
| - if (!HasCompositionText() && HasSelection()) {
|
| + if (!HasCompositionText() && HasSelection() && !is_obscured()) {
|
| ui::ScopedClipboardWriter(views::ViewsDelegate::views_delegate
|
| ->GetClipboard()).WriteText(GetSelectedText());
|
| // A trick to let undo/redo handle cursor correctly.
|
| @@ -515,7 +528,7 @@ bool TextfieldViewsModel::Cut() {
|
| }
|
|
|
| bool TextfieldViewsModel::Copy() {
|
| - if (!HasCompositionText() && HasSelection()) {
|
| + if (!HasCompositionText() && HasSelection() && !is_obscured()) {
|
| ui::ScopedClipboardWriter(views::ViewsDelegate::views_delegate
|
| ->GetClipboard()).WriteText(GetSelectedText());
|
| return true;
|
| @@ -541,8 +554,9 @@ bool TextfieldViewsModel::HasSelection() const {
|
| void TextfieldViewsModel::DeleteSelection() {
|
| DCHECK(!HasCompositionText());
|
| DCHECK(HasSelection());
|
| - ExecuteAndRecordDelete(render_text_->GetSelectionStart(),
|
| - render_text_->GetCursorPosition(), false);
|
| + ExecuteAndRecordDelete(FromDisplayIndex(render_text_->GetSelectionStart()),
|
| + FromDisplayIndex(render_text_->GetCursorPosition()),
|
| + false);
|
| }
|
|
|
| void TextfieldViewsModel::DeleteSelectionAndInsertTextAt(
|
| @@ -578,9 +592,9 @@ void TextfieldViewsModel::SetCompositionText(
|
|
|
| size_t cursor = GetCursorPosition();
|
| string16 new_text = GetText();
|
| - render_text_->SetText(new_text.insert(cursor, composition.text));
|
| + SetTextInternal(new_text.insert(cursor, composition.text));
|
| ui::Range range(cursor, cursor + composition.text.length());
|
| - render_text_->SetCompositionRange(range);
|
| + render_text_->SetCompositionRange(ToDisplayRange(range));
|
| // TODO(msw): Support multiple composition underline ranges.
|
|
|
| if (composition.selection.IsValid()) {
|
| @@ -588,20 +602,20 @@ void TextfieldViewsModel::SetCompositionText(
|
| std::min(range.start() + composition.selection.start(), range.end());
|
| size_t end =
|
| std::min(range.start() + composition.selection.end(), range.end());
|
| - render_text_->SelectRange(ui::Range(start, end));
|
| + render_text_->SelectRange(ToDisplayRange(ui::Range(start, end)));
|
| } else {
|
| - render_text_->SetCursorPosition(range.end());
|
| + render_text_->SetCursorPosition(ToDisplayIndex(range.end()));
|
| }
|
| }
|
|
|
| void TextfieldViewsModel::ConfirmCompositionText() {
|
| DCHECK(HasCompositionText());
|
| - ui::Range range = render_text_->GetCompositionRange();
|
| + ui::Range range = FromDisplayRange(render_text_->GetCompositionRange());
|
| string16 text = GetText().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, text, range.start()));
|
| - render_text_->SetCursorPosition(range.end());
|
| + render_text_->SetCursorPosition(ToDisplayIndex(range.end()));
|
| ClearComposition();
|
| if (delegate_)
|
| delegate_->OnCompositionTextConfirmedOrCleared();
|
| @@ -609,11 +623,11 @@ void TextfieldViewsModel::ConfirmCompositionText() {
|
|
|
| void TextfieldViewsModel::CancelCompositionText() {
|
| DCHECK(HasCompositionText());
|
| - ui::Range range = render_text_->GetCompositionRange();
|
| + ui::Range range = FromDisplayRange(render_text_->GetCompositionRange());
|
| ClearComposition();
|
| string16 new_text = GetText();
|
| - render_text_->SetText(new_text.erase(range.start(), range.length()));
|
| - render_text_->SetCursorPosition(range.start());
|
| + SetTextInternal(new_text.erase(range.start(), range.length()));
|
| + render_text_->SetCursorPosition(ToDisplayIndex(range.start()));
|
| if (delegate_)
|
| delegate_->OnCompositionTextConfirmedOrCleared();
|
| }
|
| @@ -623,7 +637,7 @@ void TextfieldViewsModel::ClearComposition() {
|
| }
|
|
|
| void TextfieldViewsModel::GetCompositionTextRange(ui::Range* range) const {
|
| - *range = ui::Range(render_text_->GetCompositionRange());
|
| + *range = ui::Range(FromDisplayRange(render_text_->GetCompositionRange()));
|
| }
|
|
|
| bool TextfieldViewsModel::HasCompositionText() const {
|
| @@ -702,7 +716,7 @@ void TextfieldViewsModel::ExecuteAndRecordDelete(size_t from,
|
|
|
| void TextfieldViewsModel::ExecuteAndRecordReplaceSelection(
|
| MergeType merge_type, const string16& new_text) {
|
| - size_t new_text_start = render_text_->MinOfSelection();
|
| + size_t new_text_start = FromDisplayIndex(render_text_->MinOfSelection());
|
| size_t new_cursor_pos = new_text_start + new_text.length();
|
| ExecuteAndRecordReplace(merge_type,
|
| GetCursorPosition(),
|
| @@ -716,7 +730,7 @@ void TextfieldViewsModel::ExecuteAndRecordReplace(MergeType merge_type,
|
| size_t new_cursor_pos,
|
| const string16& new_text,
|
| size_t new_text_start) {
|
| - size_t old_text_start = render_text_->MinOfSelection();
|
| + size_t old_text_start = FromDisplayIndex(render_text_->MinOfSelection());
|
| bool backward =
|
| render_text_->GetSelectionStart() > render_text_->GetCursorPosition();
|
| Edit* edit = new ReplaceEdit(merge_type,
|
| @@ -772,12 +786,62 @@ void TextfieldViewsModel::ModifyText(size_t delete_from,
|
| string16 text = GetText();
|
| ClearComposition();
|
| if (delete_from != delete_to)
|
| - render_text_->SetText(text.erase(delete_from, delete_to - delete_from));
|
| + SetTextInternal(text.erase(delete_from, delete_to - delete_from));
|
| if (!new_text.empty())
|
| - render_text_->SetText(text.insert(new_text_insert_at, new_text));
|
| - render_text_->SetCursorPosition(new_cursor_pos);
|
| + SetTextInternal(text.insert(new_text_insert_at, new_text));
|
| + render_text_->SetCursorPosition(ToDisplayIndex(new_cursor_pos));
|
| // TODO(oshima): mac selects the text that is just undone (but gtk doesn't).
|
| // This looks fine feature and we may want to do the same.
|
| }
|
|
|
| +void TextfieldViewsModel::SetTextInternal(const string16& new_text) {
|
| + text_ = new_text;
|
| + render_text_->SetText(GetDisplayText());
|
| +}
|
| +
|
| +string16 TextfieldViewsModel::GetDisplayText() const {
|
| + if (!obscured_)
|
| + return text_;
|
| + size_t obscured_text_length =
|
| + static_cast<size_t>(Utf16IndexToOffset(text_, 0, text_.length()));
|
| + return string16(obscured_text_length, internal::kPasswordReplacementChar);
|
| +}
|
| +
|
| +size_t TextfieldViewsModel::FromDisplayIndex(size_t index) const {
|
| + if (!is_obscured())
|
| + return index;
|
| + return Utf16OffsetToIndex(GetText(), 0, index);
|
| +}
|
| +size_t TextfieldViewsModel::ToDisplayIndex(size_t index) const {
|
| + if (!is_obscured())
|
| + return index;
|
| + return Utf16IndexToOffset(GetText(), 0, index);
|
| +}
|
| +ui::Range TextfieldViewsModel::FromDisplayRange(ui::Range range) const {
|
| + if (!is_obscured() || !range.IsValid())
|
| + return range;
|
| + return ui::Range(FromDisplayIndex(range.start()),
|
| + FromDisplayIndex(range.end()));
|
| +}
|
| +ui::Range TextfieldViewsModel::ToDisplayRange(ui::Range range) const {
|
| + if (!is_obscured() || !range.IsValid())
|
| + return range;
|
| + return ui::Range(ToDisplayIndex(range.start()),
|
| + ToDisplayIndex(range.end()));
|
| +}
|
| +gfx::SelectionModel TextfieldViewsModel::FromDisplaySelection(
|
| + const gfx::SelectionModel& model) const {
|
| + return gfx::SelectionModel(FromDisplayIndex(model.selection_start()),
|
| + FromDisplayIndex(model.selection_end()),
|
| + FromDisplayIndex(model.caret_pos()),
|
| + model.caret_placement());
|
| +}
|
| +gfx::SelectionModel TextfieldViewsModel::ToDisplaySelection(
|
| + const gfx::SelectionModel& model) const {
|
| + return gfx::SelectionModel(ToDisplayIndex(model.selection_start()),
|
| + ToDisplayIndex(model.selection_end()),
|
| + ToDisplayIndex(model.caret_pos()),
|
| + model.caret_placement());
|
| +}
|
| +
|
| } // namespace views
|
|
|