summaryrefslogtreecommitdiffstats
path: root/views/controls/textfield/textfield_views_model.cc
diff options
context:
space:
mode:
authormsw@chromium.org <msw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-07-25 05:45:36 +0000
committermsw@chromium.org <msw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-07-25 05:45:36 +0000
commit1a048518abc4766f489068a3a833963d6a98f5c3 (patch)
tree3bd2acf2d73b46c7f3935119ca3451e19ff1e224 /views/controls/textfield/textfield_views_model.cc
parentf077210dfebac7d8355038dc5c2dbeb81f692017 (diff)
downloadchromium_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.cc522
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