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 |