diff options
author | msw@chromium.org <msw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-07-25 05:45:36 +0000 |
---|---|---|
committer | msw@chromium.org <msw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-07-25 05:45:36 +0000 |
commit | 1a048518abc4766f489068a3a833963d6a98f5c3 (patch) | |
tree | 3bd2acf2d73b46c7f3935119ca3451e19ff1e224 /views/controls/textfield/textfield_views_model.cc | |
parent | f077210dfebac7d8355038dc5c2dbeb81f692017 (diff) | |
download | chromium_src-1a048518abc4766f489068a3a833963d6a98f5c3.zip chromium_src-1a048518abc4766f489068a3a833963d6a98f5c3.tar.gz chromium_src-1a048518abc4766f489068a3a833963d6a98f5c3.tar.bz2 |
Revert 93840 - Add gfx::RenderText and support code.
RenderText is NativeTextFieldViews' interface to platform-specific text rendering engines.
This change doesn't hook in any new Pango or Uniscribe functionality, it will just setup the necessary API.
Review URL: http://codereview.chromium.org/7265011
TBR=msw@chromium.org
Review URL: http://codereview.chromium.org/7492041
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@93842 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'views/controls/textfield/textfield_views_model.cc')
-rw-r--r-- | views/controls/textfield/textfield_views_model.cc | 522 |
1 files changed, 424 insertions, 98 deletions
diff --git a/views/controls/textfield/textfield_views_model.cc b/views/controls/textfield/textfield_views_model.cc index 548e014..1605546 100644 --- a/views/controls/textfield/textfield_views_model.cc +++ b/views/controls/textfield/textfield_views_model.cc @@ -13,9 +13,8 @@ #include "ui/base/clipboard/clipboard.h" #include "ui/base/clipboard/scoped_clipboard_writer.h" #include "ui/base/range/range.h" -#include "ui/gfx/canvas.h" #include "ui/gfx/font.h" -#include "ui/gfx/render_text.h" +#include "views/controls/textfield/text_style.h" #include "views/controls/textfield/textfield.h" #include "views/views_delegate.h" @@ -257,8 +256,116 @@ class DeleteEdit : public Edit { } }; +struct TextStyleRange { + TextStyleRange(const TextStyle* s, size_t start, size_t end) + : style(s), + range(start, end) { + } + TextStyleRange(const TextStyle* s, const ui::Range& r) + : style(s), + range(r) { + } + const TextStyle *style; + ui::Range range; +}; + } // namespace internal +namespace { + +using views::internal::TextStyleRange; + +static bool TextStyleRangeComparator(const TextStyleRange* i, + const TextStyleRange* j) { + return i->range.start() < j->range.start(); +} + +#ifndef NDEBUG +// A test function to check TextStyleRanges' invariant condition: +// "no overlapping range". +bool CheckInvariant(const TextStyleRanges* style_ranges) { + TextStyleRanges copy = *style_ranges; + std::sort(copy.begin(), copy.end(), TextStyleRangeComparator); + + for (TextStyleRanges::size_type i = 0; i < copy.size() - 1; i++) { + ui::Range& former = copy[i]->range; + ui::Range& latter = copy[i + 1]->range; + if (former.is_empty()) { + LOG(WARNING) << "Empty range at " << i << " :" << former; + return false; + } + if (!former.IsValid()) { + LOG(WARNING) << "Invalid range at " << i << " :" << former; + return false; + } + if (former.GetMax() > latter.GetMin()) { + LOG(WARNING) << + "Sorting error. former:" << former << " latter:" << latter; + return false; + } + if (former.Intersects(latter)) { + LOG(ERROR) << "overlapping style range found: former=" << former + << ", latter=" << latter; + return false; + } + } + if ((*copy.rbegin())->range.is_empty()) { + LOG(WARNING) << "Empty range at end"; + return false; + } + if (!(*copy.rbegin())->range.IsValid()) { + LOG(WARNING) << "Invalid range at end"; + return false; + } + return true; +} +#endif + +void InsertStyle(TextStyleRanges* style_ranges, + TextStyleRange* text_style_range) { + const ui::Range& range = text_style_range->range; + if (range.is_empty() || !range.IsValid()) { + delete text_style_range; + return; + } + CHECK(!range.is_reversed()); + + // Invariant condition: all items in the range has no overlaps. + TextStyleRanges::size_type index = 0; + while (index < style_ranges->size()) { + TextStyleRange* current = (*style_ranges)[index]; + if (range.Contains(current->range)) { + style_ranges->erase(style_ranges->begin() + index); + delete current; + continue; + } else if (current->range.Contains(range) && + range.start() != current->range.start() && + range.end() != current->range.end()) { + // Split current style into two styles. + style_ranges->push_back( + new TextStyleRange(current->style, + range.GetMax(), current->range.GetMax())); + current->range.set_end(range.GetMin()); + } else if (range.Intersects(current->range)) { + if (current->range.GetMax() <= range.GetMax()) { + current->range.set_end(range.GetMin()); + } else { + current->range.set_start(range.GetMax()); + } + } else { + // No change needed. Pass it through. + } + index ++; + } + // Add the new range at the end. + style_ranges->push_back(text_style_range); +#ifndef NDEBUG + DCHECK(CheckInvariant(style_ranges)); +#endif +} + +} // namespace + using internal::Edit; using internal::DeleteEdit; using internal::InsertEdit; @@ -276,18 +383,70 @@ TextfieldViewsModel::Delegate::~Delegate() { TextfieldViewsModel::TextfieldViewsModel(Delegate* delegate) : delegate_(delegate), - render_text_(gfx::RenderText::CreateRenderText()), + cursor_pos_(0), + selection_start_(0), + composition_start_(0), + composition_end_(0), is_password_(false), - current_edit_(edit_history_.end()) { + current_edit_(edit_history_.end()), + sort_style_ranges_(false) { } TextfieldViewsModel::~TextfieldViewsModel() { ClearEditHistory(); ClearComposition(); + ClearAllTextStyles(); + TextStyles::iterator begin = text_styles_.begin(); + TextStyles::iterator end = text_styles_.end(); + while (begin != end) { + TextStyles::iterator temp = begin; + ++begin; + delete *temp; + } } -const string16& TextfieldViewsModel::GetText() const { - return render_text_->text(); +void TextfieldViewsModel::GetFragments(TextFragments* fragments) { + static const TextStyle* kNormalStyle = new TextStyle(); + + if (sort_style_ranges_) { + sort_style_ranges_ = false; + std::sort(style_ranges_.begin(), style_ranges_.end(), + TextStyleRangeComparator); + } + + // If a user is compositing text, use composition's style. + // TODO(oshima): ask suzhe for expected behavior. + const TextStyleRanges& ranges = composition_style_ranges_.size() > 0 ? + composition_style_ranges_ : style_ranges_; + TextStyleRanges::const_iterator next_ = ranges.begin(); + + DCHECK(fragments); + fragments->clear(); + size_t current = 0; + size_t end = text_.length(); + while(next_ != ranges.end()) { + const TextStyleRange* text_style_range = *next_++; + const ui::Range& range = text_style_range->range; + const TextStyle* style = text_style_range->style; + + DCHECK(!range.is_empty()); + DCHECK(range.IsValid()); + if (range.is_empty() || !range.IsValid()) + continue; + + size_t start = std::min(range.start(), end); + + if (start == end) // Exit loop if it reached the end. + break; + else if (current < start) // Fill the gap to next style with normal text. + fragments->push_back(TextFragment(current, start, kNormalStyle)); + + current = std::min(range.end(), end); + fragments->push_back(TextFragment(start, current, style)); + } + // If there is any text left add it as normal text. + if (current != end) + fragments->push_back(TextFragment(current, end, kNormalStyle)); } bool TextfieldViewsModel::SetText(const string16& text) { @@ -296,10 +455,10 @@ bool TextfieldViewsModel::SetText(const string16& text) { ConfirmCompositionText(); changed = true; } - if (GetText() != text) { + if (text_ != text) { if (changed) // No need to remember composition. Undo(); - size_t old_cursor = GetCursorPosition(); + size_t old_cursor = cursor_pos_; size_t new_cursor = old_cursor > text.length() ? text.length() : old_cursor; SelectAll(); // If there is a composition text, don't merge with previous edit. @@ -310,7 +469,7 @@ bool TextfieldViewsModel::SetText(const string16& text) { new_cursor, text, 0U); - render_text_->SetCursorPosition(new_cursor); + cursor_pos_ = new_cursor; } ClearSelection(); return changed; @@ -319,10 +478,10 @@ bool TextfieldViewsModel::SetText(const string16& text) { void TextfieldViewsModel::Append(const string16& text) { if (HasCompositionText()) ConfirmCompositionText(); - size_t save = GetCursorPosition(); - MoveCursorRight(gfx::LINE_BREAK, false); + size_t save = cursor_pos_; + MoveCursorToEnd(false); InsertText(text); - render_text_->SetCursorPosition(save); + cursor_pos_ = save; ClearSelection(); } @@ -336,9 +495,8 @@ bool TextfieldViewsModel::Delete() { DeleteSelection(); return true; } - if (GetText().length() > GetCursorPosition()) { - size_t cursor_position = GetCursorPosition(); - ExecuteAndRecordDelete(cursor_position, cursor_position + 1, true); + if (text_.length() > cursor_pos_) { + ExecuteAndRecordDelete(cursor_pos_, cursor_pos_ + 1, true); return true; } return false; @@ -354,79 +512,194 @@ bool TextfieldViewsModel::Backspace() { DeleteSelection(); return true; } - if (GetCursorPosition() > 0) { - size_t cursor_position = GetCursorPosition(); - ExecuteAndRecordDelete(cursor_position, cursor_position - 1, true); + if (cursor_pos_ > 0) { + ExecuteAndRecordDelete(cursor_pos_, cursor_pos_ - 1, true); return true; } return false; } -size_t TextfieldViewsModel::GetCursorPosition() const { - return render_text_->GetCursorPosition(); +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_start_); + else if (cursor_pos_ > 0) + cursor_pos_--; + ClearSelection(); + } } -void TextfieldViewsModel::MoveCursorLeft(gfx::BreakType break_type, - bool select) { +void TextfieldViewsModel::MoveCursorRight(bool select) { if (HasCompositionText()) ConfirmCompositionText(); - render_text_->MoveCursorLeft(break_type, select); + // 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_start_); + else + cursor_pos_ = std::min(text_.length(), cursor_pos_ + 1); + ClearSelection(); + } } -void TextfieldViewsModel::MoveCursorRight(gfx::BreakType break_type, - bool select) { +void TextfieldViewsModel::MoveCursorToPreviousWord(bool select) { if (HasCompositionText()) ConfirmCompositionText(); - render_text_->MoveCursorRight(break_type, select); + // 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 + // middle of string and advance backwards. + base::i18n::BreakIterator iter(text_, base::i18n::BreakIterator::BREAK_WORD); + bool success = iter.Init(); + DCHECK(success); + if (!success) + return; + int last = 0; + while (iter.Advance()) { + if (iter.IsWord()) { + size_t begin = iter.pos() - iter.GetString().length(); + if (begin == cursor_pos_) { + // The cursor is at the beginning of a word. + // Move to previous word. + break; + } else if(iter.pos() >= cursor_pos_) { + // The cursor is in the middle or at the end of a word. + // Move to the top of current word. + last = begin; + break; + } else { + last = iter.pos() - iter.GetString().length(); + } + } + } + + cursor_pos_ = last; + if (!select) + ClearSelection(); } -bool TextfieldViewsModel::MoveCursorTo(size_t pos, bool select) { +void TextfieldViewsModel::MoveCursorToNextWord(bool select) { if (HasCompositionText()) ConfirmCompositionText(); - return render_text_->MoveCursorTo(pos, select); + base::i18n::BreakIterator iter(text_, base::i18n::BreakIterator::BREAK_WORD); + bool success = iter.Init(); + DCHECK(success); + if (!success) + return; + size_t pos = 0; + while (iter.Advance()) { + pos = iter.pos(); + if (iter.IsWord() && pos > cursor_pos_) { + break; + } + } + cursor_pos_ = pos; + if (!select) + ClearSelection(); +} + +void TextfieldViewsModel::MoveCursorToHome(bool select) { + MoveCursorTo(0, select); +} + +void TextfieldViewsModel::MoveCursorToEnd(bool select) { + MoveCursorTo(text_.length(), select); } -bool TextfieldViewsModel::MoveCursorTo(const gfx::Point& point, bool select) { +bool TextfieldViewsModel::MoveCursorTo(size_t pos, bool select) { if (HasCompositionText()) ConfirmCompositionText(); - return render_text_->MoveCursorTo(point, select); + bool changed = cursor_pos_ != pos || select != HasSelection(); + cursor_pos_ = pos; + if (!select) + ClearSelection(); + return changed; } -std::vector<gfx::Rect> TextfieldViewsModel::GetSelectionBounds() const { - return render_text_->GetSubstringBounds(render_text_->GetSelection()); +gfx::Rect TextfieldViewsModel::GetSelectionBounds(const gfx::Font& font) const { + if (!HasSelection()) + return gfx::Rect(); + size_t start = std::min(selection_start_, cursor_pos_); + size_t end = std::max(selection_start_, cursor_pos_); + int start_x = font.GetStringWidth(text_.substr(0, start)); + int end_x = font.GetStringWidth(text_.substr(0, end)); + return gfx::Rect(start_x, 0, end_x - start_x, font.GetHeight()); } string16 TextfieldViewsModel::GetSelectedText() const { - ui::Range selection = render_text_->GetSelection(); - return GetText().substr(selection.GetMin(), selection.length()); + return text_.substr( + std::min(cursor_pos_, selection_start_), + std::abs(static_cast<long>(cursor_pos_ - selection_start_))); } void TextfieldViewsModel::GetSelectedRange(ui::Range* range) const { - *range = render_text_->GetSelection(); + *range = ui::Range(selection_start_, cursor_pos_); } void TextfieldViewsModel::SelectRange(const ui::Range& range) { if (HasCompositionText()) ConfirmCompositionText(); - render_text_->SetSelection(range); + selection_start_ = GetSafePosition(range.start()); + cursor_pos_ = GetSafePosition(range.end()); } void TextfieldViewsModel::SelectAll() { if (HasCompositionText()) ConfirmCompositionText(); - render_text_->SelectAll(); + // SelectAll selects towards the end. + cursor_pos_ = text_.length(); + selection_start_ = 0; } void TextfieldViewsModel::SelectWord() { if (HasCompositionText()) ConfirmCompositionText(); - render_text_->SelectWord(); + // 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_start_ = cursor_pos_; + cursor_pos_++; + } else + selection_start_ = cursor_pos_ - 1; + } else if (cursor_pos_ == 0) { + selection_start_ = cursor_pos_; + if (text_.length() > 0) + cursor_pos_++; + } else { + selection_start_ = cursor_pos_ - 1; + } + + // 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_start_ > 0; selection_start_--) { + if (IsPositionAtWordSelectionBoundary(selection_start_)) + break; + } + + // Now we move cursor_pos_ to end 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 (; cursor_pos_ < text_.length(); cursor_pos_++) { + if (IsPositionAtWordSelectionBoundary(cursor_pos_)) + break; + } } void TextfieldViewsModel::ClearSelection() { if (HasCompositionText()) ConfirmCompositionText(); - render_text_->ClearSelection(); + selection_start_ = cursor_pos_; } bool TextfieldViewsModel::CanUndo() { @@ -450,8 +723,8 @@ bool TextfieldViewsModel::Undo() { if (HasCompositionText()) // safe guard for release build. CancelCompositionText(); - string16 old = GetText(); - size_t old_cursor = GetCursorPosition(); + string16 old = text_; + size_t old_cursor = cursor_pos_; (*current_edit_)->Commit(); (*current_edit_)->Undo(this); @@ -459,7 +732,7 @@ bool TextfieldViewsModel::Undo() { current_edit_ = edit_history_.end(); else current_edit_--; - return old != GetText() || old_cursor != GetCursorPosition(); + return old != text_ || old_cursor != cursor_pos_; } bool TextfieldViewsModel::Redo() { @@ -473,14 +746,10 @@ bool TextfieldViewsModel::Redo() { current_edit_ = edit_history_.begin(); else current_edit_ ++; - string16 old = GetText(); - size_t old_cursor = GetCursorPosition(); + string16 old = text_; + size_t old_cursor = cursor_pos_; (*current_edit_)->Redo(this); - return old != GetText() || old_cursor != GetCursorPosition(); -} - -string16 TextfieldViewsModel::GetVisibleText() const { - return GetVisibleText(0U, GetText().length()); + return old != text_ || old_cursor != cursor_pos_; } bool TextfieldViewsModel::Cut() { @@ -492,8 +761,7 @@ bool TextfieldViewsModel::Cut() { // than beginning, unlike Delete/Backspace. // TODO(oshima): Change Delete/Backspace to use DeleteSelection, // update DeleteEdit and remove this trick. - ui::Range selection = render_text_->GetSelection(); - render_text_->SetSelection(ui::Range(selection.end(), selection.start())); + std::swap(cursor_pos_, selection_start_); DeleteSelection(); return true; } @@ -519,14 +787,13 @@ bool TextfieldViewsModel::Paste() { } bool TextfieldViewsModel::HasSelection() const { - return !render_text_->GetSelection().is_empty(); + return selection_start_ != cursor_pos_; } void TextfieldViewsModel::DeleteSelection() { DCHECK(!HasCompositionText()); DCHECK(HasSelection()); - ui::Range selection = render_text_->GetSelection(); - ExecuteAndRecordDelete(selection.start(), selection.end(), false); + ExecuteAndRecordDelete(selection_start_, cursor_pos_, false); } void TextfieldViewsModel::DeleteSelectionAndInsertTextAt( @@ -534,24 +801,26 @@ void TextfieldViewsModel::DeleteSelectionAndInsertTextAt( if (HasCompositionText()) CancelCompositionText(); ExecuteAndRecordReplace(DO_NOT_MERGE, - GetCursorPosition(), + cursor_pos_, position + text.length(), text, position); } string16 TextfieldViewsModel::GetTextFromRange(const ui::Range& range) const { - if (range.IsValid() && range.GetMin() < GetText().length()) - return GetText().substr(range.GetMin(), range.length()); + 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, GetText().length()); + *range = ui::Range(0, text_.length()); } void TextfieldViewsModel::SetCompositionText( const ui::CompositionText& composition) { + static const TextStyle* composition_style = CreateUnderlineStyle(); + if (HasCompositionText()) CancelCompositionText(); else if (HasSelection()) @@ -560,55 +829,95 @@ void TextfieldViewsModel::SetCompositionText( if (composition.text.empty()) return; - size_t cursor = GetCursorPosition(); - string16 new_text = GetText(); - render_text_->SetText(new_text.insert(cursor, composition.text)); - ui::Range range(cursor, cursor + composition.text.length()); - render_text_->SetCompositionRange(range); - // TODO(msw): Support multiple composition underline ranges. - - if (composition.selection.IsValid()) - render_text_->SetSelection(ui::Range( - std::min(range.start() + composition.selection.start(), range.end()), - std::min(range.start() + composition.selection.end(), range.end()))); - else - render_text_->SetCursorPosition(range.end()); + size_t length = composition.text.length(); + text_.insert(cursor_pos_, composition.text); + composition_start_ = cursor_pos_; + composition_end_ = composition_start_ + length; + for (ui::CompositionUnderlines::const_iterator iter = + composition.underlines.begin(); + iter != composition.underlines.end(); + iter++) { + size_t start = composition_start_ + iter->start_offset; + size_t end = composition_start_ + iter->end_offset; + InsertStyle(&composition_style_ranges_, + new TextStyleRange(composition_style, start, end)); + } + std::sort(composition_style_ranges_.begin(), + composition_style_ranges_.end(), TextStyleRangeComparator); + + 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()); - ui::Range range = render_text_->GetCompositionRange(); - string16 text = GetText().substr(range.start(), range.length()); + string16 new_text = + text_.substr(composition_start_, composition_end_ - composition_start_); // 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()); + AddOrMergeEditHistory(new InsertEdit(false, new_text, composition_start_)); + cursor_pos_ = composition_end_; ClearComposition(); + ClearSelection(); if (delegate_) delegate_->OnCompositionTextConfirmedOrCleared(); } void TextfieldViewsModel::CancelCompositionText() { DCHECK(HasCompositionText()); - ui::Range range = render_text_->GetCompositionRange(); - string16 new_text = GetText(); - render_text_->SetText(new_text.erase(range.start(), range.length())); - render_text_->SetCursorPosition(range.start()); + text_.erase(composition_start_, composition_end_ - composition_start_); + cursor_pos_ = composition_start_; ClearComposition(); + ClearSelection(); if (delegate_) delegate_->OnCompositionTextConfirmedOrCleared(); } void TextfieldViewsModel::ClearComposition() { - render_text_->SetCompositionRange(ui::Range::InvalidRange()); + composition_start_ = composition_end_ = string16::npos; + STLDeleteContainerPointers(composition_style_ranges_.begin(), + composition_style_ranges_.end()); + composition_style_ranges_.clear(); +} + +void TextfieldViewsModel::ApplyTextStyle(const TextStyle* style, + const ui::Range& range) { + TextStyleRange* new_text_style_range = range.is_reversed() ? + new TextStyleRange(style, ui::Range(range.end(), range.start())) : + new TextStyleRange(style, range); + InsertStyle(&style_ranges_, new_text_style_range); + sort_style_ranges_ = true; } void TextfieldViewsModel::GetCompositionTextRange(ui::Range* range) const { - *range = ui::Range(render_text_->GetCompositionRange()); + if (HasCompositionText()) + *range = ui::Range(composition_start_, composition_end_); + else + *range = ui::Range::InvalidRange(); } bool TextfieldViewsModel::HasCompositionText() const { - return !render_text_->GetCompositionRange().is_empty(); + return composition_start_ != composition_end_; +} + +TextStyle* TextfieldViewsModel::CreateTextStyle() { + TextStyle* style = new TextStyle(); + text_styles_.push_back(style); + return style; +} + +void TextfieldViewsModel::ClearAllTextStyles() { + STLDeleteContainerPointers(style_ranges_.begin(), style_ranges_.end()); + style_ranges_.clear(); } ///////////////////////////////////////////////////////////////// @@ -618,7 +927,19 @@ string16 TextfieldViewsModel::GetVisibleText(size_t begin, size_t end) const { DCHECK(end >= begin); if (is_password_) return string16(end - begin, '*'); - return GetText().substr(begin, end - begin); + return text_.substr(begin, end - begin); +} + +bool TextfieldViewsModel::IsPositionAtWordSelectionBoundary(size_t pos) { + return (isalnum(text_[pos - 1]) && !isalnum(text_[pos])) || + (!isalnum(text_[pos - 1]) && isalnum(text_[pos])); +} + +size_t TextfieldViewsModel::GetSafePosition(size_t position) const { + if (position > text_.length()) { + return text_.length(); + } + return position; } void TextfieldViewsModel::InsertTextInternal(const string16& text, @@ -636,12 +957,10 @@ void TextfieldViewsModel::InsertTextInternal(const string16& text, void TextfieldViewsModel::ReplaceTextInternal(const string16& text, bool mergeable) { - if (HasCompositionText()) { + if (HasCompositionText()) CancelCompositionText(); - } else if (!HasSelection()) { - size_t cursor = GetCursorPosition(); - render_text_->SetSelection(ui::Range(cursor + text.length(), cursor)); - } + else if (!HasSelection()) + SelectRange(ui::Range(cursor_pos_ + text.length(), cursor_pos_)); // Edit history is recorded in InsertText. InsertTextInternal(text, mergeable); } @@ -670,7 +989,7 @@ void TextfieldViewsModel::ExecuteAndRecordDelete(size_t from, size_t to, bool mergeable) { size_t old_text_start = std::min(from, to); - const string16 text = GetText().substr(old_text_start, + const string16 text = text_.substr(old_text_start, std::abs(static_cast<long>(from - to))); bool backward = from > to; Edit* edit = new DeleteEdit(mergeable, text, old_text_start, backward); @@ -682,10 +1001,10 @@ void TextfieldViewsModel::ExecuteAndRecordDelete(size_t from, void TextfieldViewsModel::ExecuteAndRecordReplaceSelection( MergeType merge_type, const string16& new_text) { - size_t new_text_start = render_text_->GetSelection().GetMin(); + size_t new_text_start = std::min(cursor_pos_, selection_start_); size_t new_cursor_pos = new_text_start + new_text.length(); ExecuteAndRecordReplace(merge_type, - GetCursorPosition(), + cursor_pos_, new_cursor_pos, new_text, new_text_start); @@ -696,8 +1015,8 @@ 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_->GetSelection().GetMin(); - bool backward = render_text_->GetSelection().is_reversed(); + size_t old_text_start = std::min(cursor_pos_, selection_start_); + bool backward = selection_start_ > cursor_pos_; Edit* edit = new ReplaceEdit(merge_type, GetSelectedText(), old_cursor_pos, @@ -714,7 +1033,7 @@ void TextfieldViewsModel::ExecuteAndRecordReplace(MergeType merge_type, void TextfieldViewsModel::ExecuteAndRecordInsert(const string16& text, bool mergeable) { - Edit* edit = new InsertEdit(mergeable, text, GetCursorPosition()); + Edit* edit = new InsertEdit(mergeable, text, cursor_pos_); bool delete_edit = AddOrMergeEditHistory(edit); edit->Redo(this); if (delete_edit) @@ -748,14 +1067,21 @@ void TextfieldViewsModel::ModifyText(size_t delete_from, size_t new_text_insert_at, size_t new_cursor_pos) { DCHECK_LE(delete_from, delete_to); - string16 text = GetText(); if (delete_from != delete_to) - render_text_->SetText(text.erase(delete_from, delete_to - delete_from)); + 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); + text_.insert(new_text_insert_at, new_text); + cursor_pos_ = new_cursor_pos; + ClearSelection(); // 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. } +// static +TextStyle* TextfieldViewsModel::CreateUnderlineStyle() { + TextStyle* style = new TextStyle(); + style->set_underline(true); + return style; +} + } // namespace views |