Index: views/controls/textfield/textfield_views_model.cc |
diff --git a/views/controls/textfield/textfield_views_model.cc b/views/controls/textfield/textfield_views_model.cc |
index ef12404fdfd4f1094dfa9c5682f2753b84982f21..6e3ebf7f5301159719ae4bde025e9882d2dcbd7a 100644 |
--- a/views/controls/textfield/textfield_views_model.cc |
+++ b/views/controls/textfield/textfield_views_model.cc |
@@ -18,9 +18,15 @@ |
namespace views { |
-TextfieldViewsModel::TextfieldViewsModel() |
- : cursor_pos_(0), |
- selection_begin_(0), |
+TextfieldViewsModel::Delegate::~Delegate() { |
+} |
+ |
+TextfieldViewsModel::TextfieldViewsModel(Delegate* delegate) |
+ : delegate_(delegate), |
+ cursor_pos_(0), |
+ selection_start_(0), |
+ composition_start_(0), |
+ composition_end_(0), |
is_password_(false) { |
} |
@@ -30,57 +36,159 @@ TextfieldViewsModel::~TextfieldViewsModel() { |
void TextfieldViewsModel::GetFragments(TextFragments* fragments) const { |
DCHECK(fragments); |
fragments->clear(); |
- if (HasSelection()) { |
- int begin = std::min(selection_begin_, cursor_pos_); |
- int end = std::max(selection_begin_, cursor_pos_); |
- if (begin != 0) { |
- fragments->push_back(TextFragment(0, begin, false)); |
+ if (HasCompositionText()) { |
+ if (composition_start_) |
+ fragments->push_back(TextFragment(0, composition_start_, false, false)); |
+ |
+ size_t selection_start = std::min(selection_start_, cursor_pos_); |
+ size_t selection_end = std::max(selection_start_, cursor_pos_); |
+ size_t last_end = composition_start_; |
+ for (ui::CompositionUnderlines::const_iterator i = |
+ composition_underlines_.begin(); |
+ i != composition_underlines_.end(); ++i) { |
+ size_t fragment_start = |
+ std::min(i->start_offset, i->end_offset) + composition_start_; |
+ size_t fragment_end = |
+ std::max(i->start_offset, i->end_offset) + composition_start_; |
+ |
+ fragment_start = std::max(last_end, fragment_start); |
+ fragment_end = std::min(fragment_end, composition_end_); |
+ |
+ if (fragment_start >= fragment_end) |
+ break; |
+ |
+ // If there is no selection, then just add a text fragment with underline. |
+ if (selection_start == selection_end) { |
+ if (last_end < fragment_start) { |
+ fragments->push_back( |
+ TextFragment(last_end, fragment_start, false, false)); |
+ } |
+ fragments->push_back( |
+ TextFragment(fragment_start, fragment_end, false, true)); |
+ last_end = fragment_end; |
+ continue; |
+ } |
+ |
+ size_t end = std::min(fragment_start, selection_start); |
+ if (last_end < end) |
+ fragments->push_back(TextFragment(last_end, end, false, false)); |
+ |
+ last_end = fragment_end; |
+ |
+ if (selection_start < fragment_start) { |
+ end = std::min(selection_end, fragment_start); |
+ fragments->push_back(TextFragment(selection_start, end, true, false)); |
+ selection_start = end; |
+ } else if (selection_start > fragment_start) { |
+ end = std::min(selection_start, fragment_end); |
+ fragments->push_back(TextFragment(fragment_start, end, false, true)); |
+ fragment_start = end; |
+ if (fragment_start == fragment_end) |
+ continue; |
+ } |
+ |
+ if (fragment_start < selection_end) { |
+ DCHECK_EQ(selection_start, fragment_start); |
+ end = std::min(fragment_end, selection_end); |
+ fragments->push_back(TextFragment(fragment_start, end, true, true)); |
+ fragment_start = end; |
+ selection_start = end; |
+ if (fragment_start == fragment_end) |
+ continue; |
+ } |
+ |
+ DCHECK_LT(fragment_start, fragment_end); |
+ fragments->push_back( |
+ TextFragment(fragment_start, fragment_end, false, true)); |
} |
- fragments->push_back(TextFragment(begin, end, true)); |
- int len = text_.length(); |
- if (end != len) { |
- fragments->push_back(TextFragment(end, len, false)); |
+ |
+ if (last_end < composition_end_) { |
+ if (selection_start < selection_end) { |
+ DCHECK_LE(last_end, selection_start); |
+ if (last_end < selection_start) { |
+ fragments->push_back( |
+ TextFragment(last_end, selection_start, false, false)); |
+ } |
+ fragments->push_back( |
+ TextFragment(selection_start, selection_end, true, false)); |
+ if (selection_end < composition_end_) { |
+ fragments->push_back( |
+ TextFragment(selection_end, composition_end_, false, false)); |
+ } |
+ } else { |
+ fragments->push_back( |
+ TextFragment(last_end, composition_end_, false, false)); |
+ } |
} |
+ |
+ size_t len = text_.length(); |
+ if (composition_end_ < len) |
+ fragments->push_back(TextFragment(composition_end_, len, false, false)); |
+ } else if (HasSelection()) { |
+ size_t start = std::min(selection_start_, cursor_pos_); |
+ size_t end = std::max(selection_start_, cursor_pos_); |
+ if (start) |
+ fragments->push_back(TextFragment(0, start, false, false)); |
+ fragments->push_back(TextFragment(start, end, true, false)); |
+ size_t len = text_.length(); |
+ if (end != len) |
+ fragments->push_back(TextFragment(end, len, false, false)); |
} else { |
- fragments->push_back(TextFragment(0, text_.length(), false)); |
+ fragments->push_back(TextFragment(0, text_.length(), false, false)); |
} |
} |
bool TextfieldViewsModel::SetText(const string16& text) { |
- bool changed = text_ != text; |
- if (changed) { |
+ bool changed = false; |
+ if (HasCompositionText()) { |
+ ConfirmCompositionText(); |
+ changed = true; |
+ } |
+ if (text_ != text) { |
text_ = text; |
if (cursor_pos_ > text.length()) { |
cursor_pos_ = text.length(); |
} |
+ changed = true; |
} |
ClearSelection(); |
return changed; |
} |
-void TextfieldViewsModel::Insert(char16 c) { |
- if (HasSelection()) |
+void TextfieldViewsModel::InsertText(const string16& text) { |
+ if (HasCompositionText()) |
+ ClearCompositionText(); |
+ else if (HasSelection()) |
DeleteSelection(); |
- text_.insert(cursor_pos_, 1, c); |
- cursor_pos_++; |
+ text_.insert(cursor_pos_, text); |
+ cursor_pos_ += text.size(); |
ClearSelection(); |
} |
-void TextfieldViewsModel::Replace(char16 c) { |
- if (!HasSelection()) |
- Delete(); |
- Insert(c); |
+void TextfieldViewsModel::ReplaceText(const string16& text) { |
+ if (HasCompositionText()) |
+ ClearCompositionText(); |
+ else if (!HasSelection()) |
+ SelectRange(ui::Range(cursor_pos_, cursor_pos_ + text.length())); |
+ InsertText(text); |
} |
void TextfieldViewsModel::Append(const string16& text) { |
+ if (HasCompositionText()) |
+ ConfirmCompositionText(); |
text_ += text; |
} |
bool TextfieldViewsModel::Delete() { |
+ if (HasCompositionText()) { |
+ ClearCompositionText(); |
+ return true; |
+ } |
if (HasSelection()) { |
DeleteSelection(); |
return true; |
- } else if (text_.length() > cursor_pos_) { |
+ } |
+ if (text_.length() > cursor_pos_) { |
text_.erase(cursor_pos_, 1); |
return true; |
} |
@@ -88,10 +196,15 @@ bool TextfieldViewsModel::Delete() { |
} |
bool TextfieldViewsModel::Backspace() { |
+ if (HasCompositionText()) { |
+ ClearCompositionText(); |
+ return true; |
+ } |
if (HasSelection()) { |
DeleteSelection(); |
return true; |
- } else if (cursor_pos_ > 0) { |
+ } |
+ if (cursor_pos_ > 0) { |
cursor_pos_--; |
text_.erase(cursor_pos_, 1); |
ClearSelection(); |
@@ -101,13 +214,15 @@ bool TextfieldViewsModel::Backspace() { |
} |
void TextfieldViewsModel::MoveCursorLeft(bool select) { |
+ if (HasCompositionText()) |
+ ConfirmCompositionText(); |
// TODO(oshima): support BIDI |
if (select) { |
if (cursor_pos_ > 0) |
cursor_pos_--; |
} else { |
if (HasSelection()) |
- cursor_pos_ = std::min(cursor_pos_, selection_begin_); |
+ cursor_pos_ = std::min(cursor_pos_, selection_start_); |
else if (cursor_pos_ > 0) |
cursor_pos_--; |
ClearSelection(); |
@@ -115,12 +230,14 @@ void TextfieldViewsModel::MoveCursorLeft(bool select) { |
} |
void TextfieldViewsModel::MoveCursorRight(bool select) { |
+ if (HasCompositionText()) |
+ ConfirmCompositionText(); |
// TODO(oshima): support BIDI |
if (select) { |
cursor_pos_ = std::min(text_.length(), cursor_pos_ + 1); |
} else { |
if (HasSelection()) |
- cursor_pos_ = std::max(cursor_pos_, selection_begin_); |
+ cursor_pos_ = std::max(cursor_pos_, selection_start_); |
else |
cursor_pos_ = std::min(text_.length(), cursor_pos_ + 1); |
ClearSelection(); |
@@ -128,6 +245,8 @@ void TextfieldViewsModel::MoveCursorRight(bool select) { |
} |
void TextfieldViewsModel::MoveCursorToPreviousWord(bool select) { |
+ if (HasCompositionText()) |
+ ConfirmCompositionText(); |
// Notes: We always iterate words from the begining. |
// This is probably fast enough for our usage, but we may |
// want to modify WordIterator so that it can start from the |
@@ -162,6 +281,8 @@ void TextfieldViewsModel::MoveCursorToPreviousWord(bool select) { |
} |
void TextfieldViewsModel::MoveCursorToNextWord(bool select) { |
+ if (HasCompositionText()) |
+ ConfirmCompositionText(); |
base::BreakIterator iter(&text_, base::BreakIterator::BREAK_WORD); |
bool success = iter.Init(); |
DCHECK(success); |
@@ -179,19 +300,25 @@ void TextfieldViewsModel::MoveCursorToNextWord(bool select) { |
ClearSelection(); |
} |
-void TextfieldViewsModel::MoveCursorToStart(bool select) { |
+void TextfieldViewsModel::MoveCursorToHome(bool select) { |
+ if (HasCompositionText()) |
+ ConfirmCompositionText(); |
cursor_pos_ = 0; |
if (!select) |
ClearSelection(); |
} |
void TextfieldViewsModel::MoveCursorToEnd(bool select) { |
+ if (HasCompositionText()) |
+ ConfirmCompositionText(); |
cursor_pos_ = text_.length(); |
if (!select) |
ClearSelection(); |
} |
bool TextfieldViewsModel::MoveCursorTo(size_t pos, bool select) { |
+ if (HasCompositionText()) |
+ ConfirmCompositionText(); |
bool cursor_changed = false; |
if (cursor_pos_ != pos) { |
cursor_pos_ = pos; |
@@ -217,48 +344,54 @@ gfx::Rect TextfieldViewsModel::GetCursorBounds(const gfx::Font& font) const { |
string16 TextfieldViewsModel::GetSelectedText() const { |
return text_.substr( |
- std::min(cursor_pos_, selection_begin_), |
- std::abs(static_cast<long>(cursor_pos_ - selection_begin_))); |
+ std::min(cursor_pos_, selection_start_), |
+ std::abs(static_cast<long>(cursor_pos_ - selection_start_))); |
} |
void TextfieldViewsModel::GetSelectedRange(ui::Range* range) const { |
- *range = ui::Range(selection_begin_, cursor_pos_); |
+ *range = ui::Range(selection_start_, cursor_pos_); |
} |
void TextfieldViewsModel::SelectRange(const ui::Range& range) { |
- selection_begin_ = GetSafePosition(range.start()); |
+ if (HasCompositionText()) |
+ ConfirmCompositionText(); |
+ selection_start_ = GetSafePosition(range.start()); |
cursor_pos_ = GetSafePosition(range.end()); |
} |
void TextfieldViewsModel::SelectAll() { |
+ if (HasCompositionText()) |
+ ConfirmCompositionText(); |
// SelectAll selects towards the end. |
cursor_pos_ = text_.length(); |
- selection_begin_ = 0; |
+ selection_start_ = 0; |
} |
void TextfieldViewsModel::SelectWord() { |
- // First we setup selection_begin_ and cursor_pos_. There are so many cases |
+ if (HasCompositionText()) |
+ ConfirmCompositionText(); |
+ // First we setup selection_start_ and cursor_pos_. There are so many cases |
// because we try to emulate what select-word looks like in a gtk textfield. |
// See associated testcase for different cases. |
if (cursor_pos_ > 0 && cursor_pos_ < text_.length()) { |
if (isalnum(text_[cursor_pos_])) { |
- selection_begin_ = cursor_pos_; |
+ selection_start_ = cursor_pos_; |
cursor_pos_++; |
} else |
- selection_begin_ = cursor_pos_ - 1; |
+ selection_start_ = cursor_pos_ - 1; |
} else if (cursor_pos_ == 0) { |
- selection_begin_ = cursor_pos_; |
+ selection_start_ = cursor_pos_; |
if (text_.length() > 0) |
cursor_pos_++; |
} else { |
- selection_begin_ = cursor_pos_ - 1; |
+ selection_start_ = cursor_pos_ - 1; |
} |
- // Now we move selection_begin_ to beginning of selection. Selection boundary |
+ // Now we move selection_start_ to beginning of selection. Selection boundary |
// is defined as the position where we have alpha-num character on one side |
// and non-alpha-num char on the other side. |
- for (; selection_begin_ > 0; selection_begin_--) { |
- if (IsPositionAtWordSelectionBoundary(selection_begin_)) |
+ for (; selection_start_ > 0; selection_start_--) { |
+ if (IsPositionAtWordSelectionBoundary(selection_start_)) |
break; |
} |
@@ -272,11 +405,13 @@ void TextfieldViewsModel::SelectWord() { |
} |
void TextfieldViewsModel::ClearSelection() { |
- selection_begin_ = cursor_pos_; |
+ if (HasCompositionText()) |
+ ConfirmCompositionText(); |
+ selection_start_ = cursor_pos_; |
} |
bool TextfieldViewsModel::Cut() { |
- if (HasSelection()) { |
+ if (!HasCompositionText() && HasSelection()) { |
ui::ScopedClipboardWriter(views::ViewsDelegate::views_delegate |
->GetClipboard()).WriteText(GetSelectedText()); |
DeleteSelection(); |
@@ -286,7 +421,7 @@ bool TextfieldViewsModel::Cut() { |
} |
void TextfieldViewsModel::Copy() { |
- if (HasSelection()) { |
+ if (!HasCompositionText() && HasSelection()) { |
ui::ScopedClipboardWriter(views::ViewsDelegate::views_delegate |
->GetClipboard()).WriteText(GetSelectedText()); |
} |
@@ -297,7 +432,9 @@ bool TextfieldViewsModel::Paste() { |
views::ViewsDelegate::views_delegate->GetClipboard() |
->ReadText(ui::Clipboard::BUFFER_STANDARD, &result); |
if (!result.empty()) { |
- if (HasSelection()) |
+ if (HasCompositionText()) |
+ ConfirmCompositionText(); |
+ else if (HasSelection()) |
DeleteSelection(); |
text_.insert(cursor_pos_, result); |
cursor_pos_ += result.length(); |
@@ -308,23 +445,94 @@ bool TextfieldViewsModel::Paste() { |
} |
bool TextfieldViewsModel::HasSelection() const { |
- return selection_begin_ != cursor_pos_; |
+ return selection_start_ != cursor_pos_; |
} |
void TextfieldViewsModel::DeleteSelection() { |
+ DCHECK(!HasCompositionText()); |
DCHECK(HasSelection()); |
- size_t n = std::abs(static_cast<long>(cursor_pos_ - selection_begin_)); |
- size_t begin = std::min(cursor_pos_, selection_begin_); |
+ size_t n = std::abs(static_cast<long>(cursor_pos_ - selection_start_)); |
+ size_t begin = std::min(cursor_pos_, selection_start_); |
text_.erase(begin, n); |
cursor_pos_ = begin; |
ClearSelection(); |
} |
+string16 TextfieldViewsModel::GetTextFromRange(const ui::Range& range) const { |
+ if (range.IsValid() && range.GetMin() < text_.length()) |
+ return text_.substr(range.GetMin(), range.length()); |
+ return string16(); |
+} |
+ |
+void TextfieldViewsModel::GetTextRange(ui::Range* range) const { |
+ *range = ui::Range(0, text_.length()); |
+} |
+ |
+void TextfieldViewsModel::SetCompositionText( |
+ const ui::CompositionText& composition) { |
+ if (HasCompositionText()) |
+ ClearCompositionText(); |
+ else if (HasSelection()) |
+ DeleteSelection(); |
+ |
+ if (composition.text.empty()) |
+ return; |
+ |
+ size_t length = composition.text.length(); |
+ text_.insert(cursor_pos_, composition.text); |
+ composition_start_ = cursor_pos_; |
+ composition_end_ = composition_start_ + length; |
+ composition_underlines_ = composition.underlines; |
+ |
+ if (composition.selection.IsValid()) { |
+ selection_start_ = |
+ std::min(composition_start_ + composition.selection.start(), |
+ composition_end_); |
+ cursor_pos_ = |
+ std::min(composition_start_ + composition.selection.end(), |
+ composition_end_); |
+ } else { |
+ cursor_pos_ = composition_end_; |
+ ClearSelection(); |
+ } |
+} |
+ |
+void TextfieldViewsModel::ConfirmCompositionText() { |
+ DCHECK(HasCompositionText()); |
+ cursor_pos_ = composition_end_; |
+ composition_start_ = composition_end_ = string16::npos; |
+ composition_underlines_.clear(); |
+ ClearSelection(); |
+ if (delegate_) |
+ delegate_->OnCompositionTextConfirmedOrCleared(); |
+} |
+ |
+void TextfieldViewsModel::ClearCompositionText() { |
+ DCHECK(HasCompositionText()); |
+ text_.erase(composition_start_, composition_end_ - composition_start_); |
+ cursor_pos_ = composition_start_; |
+ composition_start_ = composition_end_ = string16::npos; |
+ composition_underlines_.clear(); |
+ ClearSelection(); |
+ if (delegate_) |
+ delegate_->OnCompositionTextConfirmedOrCleared(); |
+} |
+ |
+void TextfieldViewsModel::GetCompositionTextRange(ui::Range* range) const { |
+ if (HasCompositionText()) |
+ *range = ui::Range(composition_start_, composition_end_); |
+ else |
+ *range = ui::Range::InvalidRange(); |
+} |
+ |
+bool TextfieldViewsModel::HasCompositionText() const { |
+ return composition_start_ != composition_end_; |
+} |
+ |
string16 TextfieldViewsModel::GetVisibleText(size_t begin, size_t end) const { |
DCHECK(end >= begin); |
- if (is_password_) { |
+ if (is_password_) |
return string16(end - begin, '*'); |
- } |
return text_.substr(begin, end - begin); |
} |