| 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);
|
| }
|
|
|
|
|