summaryrefslogtreecommitdiffstats
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
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
-rw-r--r--chrome/browser/ui/views/omnibox/omnibox_view_views.cc58
-rw-r--r--chrome/browser/ui/views/omnibox/omnibox_view_views.h14
-rwxr-xr-xui/gfx/render_text.cc506
-rwxr-xr-xui/gfx/render_text.h224
-rwxr-xr-xui/gfx/render_text_linux.cc20
-rwxr-xr-xui/gfx/render_text_linux.h25
-rwxr-xr-xui/gfx/render_text_unittest.cc180
-rwxr-xr-xui/gfx/render_text_win.cc20
-rwxr-xr-xui/gfx/render_text_win.h25
-rw-r--r--ui/ui.gyp16
-rw-r--r--ui/ui_unittests.gypi5
-rw-r--r--views/controls/textfield/native_textfield_gtk.cc10
-rw-r--r--views/controls/textfield/native_textfield_gtk.h6
-rw-r--r--views/controls/textfield/native_textfield_views.cc295
-rw-r--r--views/controls/textfield/native_textfield_views.h32
-rw-r--r--views/controls/textfield/native_textfield_views_unittest.cc10
-rw-r--r--views/controls/textfield/native_textfield_win.cc10
-rw-r--r--views/controls/textfield/native_textfield_win.h6
-rw-r--r--views/controls/textfield/native_textfield_wrapper.h17
-rw-r--r--views/controls/textfield/text_style.cc54
-rw-r--r--views/controls/textfield/text_style.h65
-rw-r--r--views/controls/textfield/textfield.cc14
-rw-r--r--views/controls/textfield/textfield.h31
-rw-r--r--views/controls/textfield/textfield_views_model.cc522
-rw-r--r--views/controls/textfield/textfield_views_model.h122
-rw-r--r--views/controls/textfield/textfield_views_model_unittest.cc922
-rw-r--r--views/examples/textfield_example.cc36
-rw-r--r--views/examples/textfield_example.h9
-rw-r--r--views/views.gyp2
29 files changed, 1613 insertions, 1643 deletions
diff --git a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
index 015cbbf..08b65b9 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
@@ -27,8 +27,8 @@
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/font.h"
-#include "ui/gfx/render_text.h"
#include "views/border.h"
+#include "views/controls/textfield/text_style.h"
#include "views/controls/textfield/textfield.h"
#include "views/layout/fill_layout.h"
@@ -105,19 +105,6 @@ PropertyAccessor<AutocompleteEditState>* GetStateAccessor() {
return &state;
}
-// A convenience method for applying URL styles.
-void ApplyURLStyle(views::Textfield* textfield,
- size_t start,
- size_t end,
- SkColor color,
- bool strike) {
- gfx::StyleRange style;
- style.foreground = color;
- style.range = ui::Range(start, end);
- style.strike = strike;
- textfield->ApplyStyleRange(style);
-}
-
const int kAutocompleteVerticalMargin = 4;
// TODO(oshima): I'm currently using slightly different color than
@@ -148,7 +135,11 @@ OmniboxViewViews::OmniboxViewViews(AutocompleteEditController* controller,
popup_window_mode_(popup_window_mode),
security_level_(ToolbarModel::NONE),
ime_composing_before_change_(false),
- delete_at_end_pressed_(false) {
+ delete_at_end_pressed_(false),
+ faded_text_style_(NULL),
+ normal_text_style_(NULL),
+ security_error_scheme_style_(NULL),
+ secure_scheme_style_(NULL) {
set_border(views::Border::CreateEmptyBorder(kAutocompleteVerticalMargin, 0,
kAutocompleteVerticalMargin, 0));
}
@@ -632,6 +623,7 @@ size_t OmniboxViewViews::GetTextLength() const {
}
void OmniboxViewViews::EmphasizeURLComponents() {
+ InitTextStyles();
// See whether the contents are a URL with a non-empty host portion, which we
// should emphasize. To check for a URL, rather than using the type returned
// by Parse(), ask the model, which will check the desired page transition for
@@ -643,24 +635,29 @@ void OmniboxViewViews::EmphasizeURLComponents() {
AutocompleteInput::ParseForEmphasizeComponents(
text, model_->GetDesiredTLD(), &scheme, &host);
const bool emphasize = model_->CurrentTextIsURL() && (host.len > 0);
- SkColor base_color = emphasize ? kFadedTextColor : kNormalTextColor;
- ApplyURLStyle(textfield_, 0, text.length(), base_color, false);
- if (emphasize)
- ApplyURLStyle(textfield_, host.begin, host.end(), kNormalTextColor, false);
+
+ textfield_->ClearAllTextStyles();
+ if (emphasize) {
+ textfield_->ApplyTextStyle(faded_text_style_, ui::Range(0, text.length()));
+ textfield_->ApplyTextStyle(normal_text_style_,
+ ui::Range(host.begin, host.end()));
+ } else {
+ textfield_->ApplyTextStyle(normal_text_style_, ui::Range(0, text.length()));
+ }
// Emphasize the scheme for security UI display purposes (if necessary).
if (!model_->user_input_in_progress() && scheme.is_nonempty() &&
(security_level_ != ToolbarModel::NONE)) {
- const size_t start = scheme.begin, end = scheme.end();
+ ui::Range scheme_range(scheme.begin, scheme.end());
switch (security_level_) {
case ToolbarModel::SECURITY_ERROR:
- ApplyURLStyle(textfield_, start, end, kSecurityErrorSchemeColor, true);
+ textfield_->ApplyTextStyle(security_error_scheme_style_, scheme_range);
break;
case ToolbarModel::SECURITY_WARNING:
- ApplyURLStyle(textfield_, start, end, kFadedTextColor, false);
+ textfield_->ApplyTextStyle(faded_text_style_, scheme_range);
break;
case ToolbarModel::EV_SECURE:
case ToolbarModel::SECURE:
- ApplyURLStyle(textfield_, start, end, kSecureSchemeColor, false);
+ textfield_->ApplyTextStyle(secure_scheme_style_, scheme_range);
break;
default:
NOTREACHED() << "Unknown SecurityLevel:" << security_level_;
@@ -701,3 +698,18 @@ AutocompletePopupView* OmniboxViewViews::CreatePopupView(
return new AutocompleteContentsView(
gfx::Font(), this, model_.get(), profile, location_bar);
}
+
+void OmniboxViewViews::InitTextStyles() {
+ if (faded_text_style_)
+ return;
+ faded_text_style_ = textfield_->CreateTextStyle();
+ normal_text_style_ = textfield_->CreateTextStyle();
+ security_error_scheme_style_ = textfield_->CreateTextStyle();
+ secure_scheme_style_ = textfield_->CreateTextStyle();
+
+ faded_text_style_->set_foreground(kFadedTextColor);
+ normal_text_style_->set_foreground(kNormalTextColor);
+ secure_scheme_style_->set_foreground(kSecureSchemeColor);
+ security_error_scheme_style_->set_foreground(kSecurityErrorSchemeColor);
+ security_error_scheme_style_->set_strike(true);
+}
diff --git a/chrome/browser/ui/views/omnibox/omnibox_view_views.h b/chrome/browser/ui/views/omnibox/omnibox_view_views.h
index 553b335..7b3ae8f 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_view_views.h
+++ b/chrome/browser/ui/views/omnibox/omnibox_view_views.h
@@ -26,6 +26,10 @@ class AutocompletePopupView;
class Profile;
class TabContents;
+namespace views {
+class TextStyle;
+}
+
// Views-implementation of OmniboxView. This is based on gtk implementation.
// The following features are not yet supported.
//
@@ -159,6 +163,10 @@ class OmniboxViewViews : public views::View,
AutocompletePopupView* CreatePopupView(Profile* profile,
const View* location_bar);
+ // Lazy initialization of TextStyle objects. They can not be
+ // initialized in Init as native wrapper isn't available at that time.
+ void InitTextStyles();
+
views::Textfield* textfield_;
scoped_ptr<AutocompleteEditModel> model_;
@@ -188,6 +196,12 @@ class OmniboxViewViews : public views::View,
// Was the delete key pressed with an empty selection at the end of the edit?
bool delete_at_end_pressed_;
+ // TextStyles for URL decoration. They're owned by textfield_.
+ views::TextStyle* faded_text_style_;
+ views::TextStyle* normal_text_style_;
+ views::TextStyle* security_error_scheme_style_;
+ views::TextStyle* secure_scheme_style_;
+
DISALLOW_COPY_AND_ASSIGN(OmniboxViewViews);
};
diff --git a/ui/gfx/render_text.cc b/ui/gfx/render_text.cc
deleted file mode 100755
index c656792..0000000
--- a/ui/gfx/render_text.cc
+++ /dev/null
@@ -1,506 +0,0 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ui/gfx/render_text.h"
-
-#include <algorithm>
-
-#include "base/i18n/break_iterator.h"
-#include "base/logging.h"
-#include "base/stl_util.h"
-#include "ui/gfx/canvas.h"
-#include "ui/gfx/canvas_skia.h"
-
-namespace {
-
-#ifndef NDEBUG
-// Check StyleRanges invariant conditions: sorted and non-overlapping ranges.
-void CheckStyleRanges(const gfx::StyleRanges& style_ranges, size_t length) {
- if (length == 0) {
- DCHECK(style_ranges.empty()) << "Style ranges exist for empty text.";
- return;
- }
- for (gfx::StyleRanges::size_type i = 0; i < style_ranges.size() - 1; i++) {
- const ui::Range& former = style_ranges[i].range;
- const ui::Range& latter = style_ranges[i + 1].range;
- DCHECK(!former.is_empty()) << "Empty range at " << i << ":" << former;
- DCHECK(former.IsValid()) << "Invalid range at " << i << ":" << former;
- DCHECK(!former.is_reversed()) << "Reversed range at " << i << ":" << former;
- DCHECK(former.end() == latter.start()) << "Ranges gap/overlap/unsorted." <<
- "former:" << former << ", latter:" << latter;
- }
- const gfx::StyleRange& end_style = *style_ranges.rbegin();
- DCHECK(!end_style.range.is_empty()) << "Empty range at end.";
- DCHECK(end_style.range.IsValid()) << "Invalid range at end.";
- DCHECK(!end_style.range.is_reversed()) << "Reversed range at end.";
- DCHECK(end_style.range.end() == length) << "Style and text length mismatch.";
-}
-#endif
-
-void ApplyStyleRangeImpl(gfx::StyleRanges& style_ranges,
- gfx::StyleRange style_range) {
- const ui::Range& new_range = style_range.range;
- // Follow StyleRanges invariant conditions: sorted and non-overlapping ranges.
- gfx::StyleRanges::iterator i;
- for (i = style_ranges.begin(); i != style_ranges.end();) {
- if (i->range.end() < new_range.start()) {
- i++;
- } else if (i->range.start() == new_range.end()) {
- break;
- } else if (new_range.Contains(i->range)) {
- i = style_ranges.erase(i);
- if (i == style_ranges.end())
- break;
- } else if (i->range.start() < new_range.start() &&
- i->range.end() > new_range.end()) {
- // Split the current style into two styles.
- gfx::StyleRange split_style = gfx::StyleRange(*i);
- split_style.range.set_end(new_range.start());
- i = style_ranges.insert(i, split_style) + 1;
- i->range.set_start(new_range.end());
- break;
- } else if (i->range.start() < new_range.start()) {
- i->range.set_end(new_range.start());
- i++;
- } else if (i->range.end() > new_range.end()) {
- i->range.set_start(new_range.end());
- break;
- } else
- NOTREACHED();
- }
- // Add the new range in its sorted location.
- style_ranges.insert(i, style_range);
-}
-
-} // namespace
-
-namespace gfx {
-
-StyleRange::StyleRange()
- : font(),
- foreground(SK_ColorBLACK),
- strike(false),
- underline(false),
- range() {
-}
-
-void RenderText::SetText(const string16& text) {
- size_t old_text_length = text_.length();
- text_ = text;
-
- // Update the style ranges as needed.
- if (text_.empty()) {
- style_ranges_.clear();
- } else if (style_ranges_.empty()) {
- ApplyDefaultStyle();
- } else if (text_.length() > old_text_length) {
- style_ranges_.back().range.set_end(text_.length());
- } else if (text_.length() < old_text_length) {
- StyleRanges::iterator i;
- for (i = style_ranges_.begin(); i != style_ranges_.end(); i++) {
- if (i->range.start() >= text_.length()) {
- i = style_ranges_.erase(i);
- if (i == style_ranges_.end())
- break;
- } else if (i->range.end() > text_.length()) {
- i->range.set_end(text_.length());
- }
- }
- style_ranges_.back().range.set_end(text_.length());
- }
-#ifndef NDEBUG
- CheckStyleRanges(style_ranges_, text_.length());
-#endif
-}
-
-size_t RenderText::GetCursorPosition() const {
- return GetSelection().end();
-}
-
-void RenderText::SetCursorPosition(const size_t position) {
- SetSelection(ui::Range(position, position));
-}
-
-void RenderText::MoveCursorLeft(BreakType break_type, bool select) {
- if (break_type == LINE_BREAK) {
- MoveCursorTo(0, select);
- return;
- }
- size_t position = GetCursorPosition();
- // Cancelling a selection moves to the edge of the selection.
- if (!GetSelection().is_empty() && !select) {
- // Use the selection start if it is left of the selection end.
- if (GetCursorBounds(GetSelection().start(), false).x() <
- GetCursorBounds(position, false).x())
- position = GetSelection().start();
- // If |move_by_word|, use the nearest word boundary left of the selection.
- if (break_type == WORD_BREAK)
- position = GetLeftCursorPosition(position, true);
- } else {
- position = GetLeftCursorPosition(position, break_type == WORD_BREAK);
- }
- MoveCursorTo(position, select);
-}
-
-void RenderText::MoveCursorRight(BreakType break_type, bool select) {
- if (break_type == LINE_BREAK) {
- MoveCursorTo(text().length(), select);
- return;
- }
- size_t position = GetCursorPosition();
- // Cancelling a selection moves to the edge of the selection.
- if (!GetSelection().is_empty() && !select) {
- // Use the selection start if it is right of the selection end.
- if (GetCursorBounds(GetSelection().start(), false).x() >
- GetCursorBounds(position, false).x())
- position = GetSelection().start();
- // If |move_by_word|, use the nearest word boundary right of the selection.
- if (break_type == WORD_BREAK)
- position = GetRightCursorPosition(position, true);
- } else {
- position = GetRightCursorPosition(position, break_type == WORD_BREAK);
- }
- MoveCursorTo(position, select);
-}
-
-bool RenderText::MoveCursorTo(size_t position, bool select) {
- bool changed = GetCursorPosition() != position ||
- select == GetSelection().is_empty();
- if (select)
- SetSelection(ui::Range(GetSelection().start(), position));
- else
- SetSelection(ui::Range(position, position));
- return changed;
-}
-
-bool RenderText::MoveCursorTo(const gfx::Point& point, bool select) {
- // TODO(msw): Make this function support cursor placement via mouse near BiDi
- // level changes. The visual cursor appearance will depend on the location
- // clicked, not solely the resulting logical cursor position. See the TODO
- // note pertaining to selection_range_ for more information.
- return MoveCursorTo(FindCursorPosition(point), select);
-}
-
-const ui::Range& RenderText::GetSelection() const {
- return selection_range_;
-}
-
-void RenderText::SetSelection(const ui::Range& range) {
- selection_range_.set_end(std::min(range.end(), text().length()));
- selection_range_.set_start(std::min(range.start(), text().length()));
-
- // Update |display_offset_| to ensure the current cursor is visible.
- gfx::Rect cursor_bounds(GetCursorBounds(GetCursorPosition(), insert_mode()));
- int display_width = display_rect_.width();
- int string_width = GetStringWidth();
- if (string_width < display_width) {
- // Show all text whenever the text fits to the size.
- display_offset_.set_x(0);
- } else if ((display_offset_.x() + cursor_bounds.right()) > display_width) {
- // Pan to show the cursor when it overflows to the right,
- display_offset_.set_x(display_width - cursor_bounds.right());
- } else if ((display_offset_.x() + cursor_bounds.x()) < 0) {
- // Pan to show the cursor when it overflows to the left.
- display_offset_.set_x(-cursor_bounds.x());
- }
-}
-
-bool RenderText::IsPointInSelection(const gfx::Point& point) const {
- size_t pos = FindCursorPosition(point);
- return (pos >= GetSelection().GetMin() && pos < GetSelection().GetMax());
-}
-
-void RenderText::ClearSelection() {
- SetCursorPosition(GetCursorPosition());
-}
-
-void RenderText::SelectAll() {
- SetSelection(ui::Range(0, text().length()));
-}
-
-void RenderText::SelectWord() {
- size_t selection_start = GetSelection().start();
- size_t cursor_position = GetCursorPosition();
- // 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_position > 0 && cursor_position < text().length()) {
- if (isalnum(text()[cursor_position])) {
- selection_start = cursor_position;
- cursor_position++;
- } else
- selection_start = cursor_position - 1;
- } else if (cursor_position == 0) {
- selection_start = cursor_position;
- if (text().length() > 0)
- cursor_position++;
- } else {
- selection_start = cursor_position - 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_position < text().length(); cursor_position++) {
- if (IsPositionAtWordSelectionBoundary(cursor_position))
- break;
- }
-
- SetSelection(ui::Range(selection_start, cursor_position));
-}
-
-const ui::Range& RenderText::GetCompositionRange() const {
- return composition_range_;
-}
-
-void RenderText::SetCompositionRange(const ui::Range& composition_range) {
- CHECK(!composition_range.IsValid() ||
- ui::Range(0, text_.length()).Contains(composition_range));
- composition_range_.set_end(composition_range.end());
- composition_range_.set_start(composition_range.start());
-}
-
-void RenderText::ApplyStyleRange(StyleRange style_range) {
- const ui::Range& new_range = style_range.range;
- if (!new_range.IsValid() || new_range.is_empty())
- return;
- CHECK(!new_range.is_reversed());
- CHECK(ui::Range(0, text_.length()).Contains(new_range));
- ApplyStyleRangeImpl(style_ranges_, style_range);
-#ifndef NDEBUG
- CheckStyleRanges(style_ranges_, text_.length());
-#endif
-}
-
-void RenderText::ApplyDefaultStyle() {
- style_ranges_.clear();
- StyleRange style = StyleRange(default_style_);
- style.range.set_end(text_.length());
- style_ranges_.push_back(style);
-}
-
-base::i18n::TextDirection RenderText::GetTextDirection() const {
- // TODO(msw): Bidi implementation, intended to replace the functionality added
- // in crrev.com/91881 (discussed in codereview.chromium.org/7324011).
- return base::i18n::LEFT_TO_RIGHT;
-}
-
-int RenderText::GetStringWidth() const {
- return GetSubstringBounds(ui::Range(0, text_.length()))[0].width();
-}
-
-void RenderText::Draw(gfx::Canvas* canvas) {
- // Clip the canvas to the text display area.
- canvas->ClipRectInt(display_rect_.x(), display_rect_.y(),
- display_rect_.width(), display_rect_.height());
-
- // Draw the selection.
- std::vector<gfx::Rect> selection(GetSubstringBounds(GetSelection()));
- SkColor selection_color =
- focused() ? kFocusedSelectionColor : kUnfocusedSelectionColor;
- for (std::vector<gfx::Rect>::const_iterator i = selection.begin();
- i < selection.end(); ++i) {
- gfx::Rect r(*i);
- r.Offset(display_offset_);
- canvas->FillRectInt(selection_color, r.x(), r.y(), r.width(), r.height());
- }
-
- // Create a temporary copy of the style ranges for composition and selection.
- // TODO(msw): This pattern ought to be reconsidered; what about composition
- // and selection overlaps, retain existing local style features?
- StyleRanges style_ranges(style_ranges_);
- // Apply a composition style override to a copy of the style ranges.
- if (composition_range_.IsValid() && !composition_range_.is_empty()) {
- StyleRange composition_style(default_style_);
- composition_style.underline = true;
- composition_style.range.set_start(composition_range_.start());
- composition_style.range.set_end(composition_range_.end());
- ApplyStyleRangeImpl(style_ranges, composition_style);
- }
- // Apply a selection style override to a copy of the style ranges.
- if (selection_range_.IsValid() && !selection_range_.is_empty()) {
- StyleRange selection_style(default_style_);
- selection_style.foreground = kSelectedTextColor;
- selection_style.range.set_start(selection_range_.GetMin());
- selection_style.range.set_end(selection_range_.GetMax());
- ApplyStyleRangeImpl(style_ranges, selection_style);
- }
-
- // Draw the text.
- gfx::Rect bounds(display_rect_);
- bounds.Offset(display_offset_);
- for (StyleRanges::const_iterator i = style_ranges.begin();
- i < style_ranges.end(); ++i) {
- Font font = !i->underline ? i->font :
- i->font.DeriveFont(0, i->font.GetStyle() | Font::UNDERLINED);
- string16 text = text_.substr(i->range.start(), i->range.length());
- bounds.set_width(font.GetStringWidth(text));
- canvas->DrawStringInt(text, font, i->foreground, bounds);
-
- // Draw the strikethrough.
- if (i->strike) {
- SkPaint paint;
- paint.setAntiAlias(true);
- paint.setStyle(SkPaint::kFill_Style);
- paint.setColor(i->foreground);
- paint.setStrokeWidth(kStrikeWidth);
- canvas->AsCanvasSkia()->drawLine(SkIntToScalar(bounds.x()),
- SkIntToScalar(bounds.bottom()),
- SkIntToScalar(bounds.right()),
- SkIntToScalar(bounds.y()),
- paint);
- }
-
- bounds.set_x(bounds.x() + bounds.width());
- }
-
- // Paint cursor. Replace cursor is drawn as rectangle for now.
- if (cursor_visible() && focused()) {
- bounds = GetCursorBounds(GetCursorPosition(), insert_mode());
- bounds.Offset(display_offset_);
- if (!bounds.IsEmpty())
- canvas->DrawRectInt(kCursorColor,
- bounds.x(),
- bounds.y(),
- bounds.width(),
- bounds.height());
- }
-}
-
-size_t RenderText::FindCursorPosition(const gfx::Point& point) const {
- const gfx::Font& font = Font();
- int left = 0;
- int left_pos = 0;
- int right = font.GetStringWidth(text());
- int right_pos = text().length();
-
- int x = point.x();
- if (x <= left) return left_pos;
- if (x >= right) return right_pos;
- // binary searching the cursor position.
- // TODO(oshima): use the center of character instead of edge.
- // Binary search may not work for language like arabic.
- while (std::abs(static_cast<long>(right_pos - left_pos) > 1)) {
- int pivot_pos = left_pos + (right_pos - left_pos) / 2;
- int pivot = font.GetStringWidth(text().substr(0, pivot_pos));
- if (pivot < x) {
- left = pivot;
- left_pos = pivot_pos;
- } else if (pivot == x) {
- return pivot_pos;
- } else {
- right = pivot;
- right_pos = pivot_pos;
- }
- }
- return left_pos;
-}
-
-std::vector<gfx::Rect> RenderText::GetSubstringBounds(
- const ui::Range& range) const {
- size_t start = range.GetMin();
- size_t end = range.GetMax();
- gfx::Font font;
- int start_x = font.GetStringWidth(text().substr(0, start));
- int end_x = font.GetStringWidth(text().substr(0, end));
- std::vector<gfx::Rect> bounds;
- bounds.push_back(gfx::Rect(start_x, 0, end_x - start_x, font.GetHeight()));
- return bounds;
-}
-
-gfx::Rect RenderText::GetCursorBounds(size_t cursor_pos,
- bool insert_mode) const {
- gfx::Font font;
- int x = font.GetStringWidth(text_.substr(0U, cursor_pos));
- DCHECK_GE(x, 0);
- int h = std::min(display_rect_.height(), font.GetHeight());
- gfx::Rect bounds(x, (display_rect_.height() - h) / 2, 1, h);
- if (!insert_mode && text_.length() != cursor_pos)
- bounds.set_width(font.GetStringWidth(text_.substr(0, cursor_pos + 1)) - x);
- return bounds;
-}
-
-size_t RenderText::GetLeftCursorPosition(size_t position,
- bool move_by_word) const {
- if (!move_by_word)
- return position == 0? position : position - 1;
- // 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 position;
- int last = 0;
- while (iter.Advance()) {
- if (iter.IsWord()) {
- size_t begin = iter.pos() - iter.GetString().length();
- if (begin == position) {
- // The cursor is at the beginning of a word.
- // Move to previous word.
- break;
- } else if(iter.pos() >= position) {
- // 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();
- }
- }
- }
-
- return last;
-}
-
-size_t RenderText::GetRightCursorPosition(size_t position,
- bool move_by_word) const {
- if (!move_by_word)
- return std::min(position + 1, text().length());
- base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD);
- bool success = iter.Init();
- DCHECK(success);
- if (!success)
- return position;
- size_t pos = 0;
- while (iter.Advance()) {
- pos = iter.pos();
- if (iter.IsWord() && pos > position) {
- break;
- }
- }
- return pos;
-}
-
-RenderText::RenderText()
- : text_(),
- selection_range_(),
- cursor_visible_(false),
- insert_mode_(true),
- composition_range_(),
- style_ranges_(),
- default_style_(),
- display_rect_(),
- display_offset_() {
-}
-
-RenderText::~RenderText() {
-}
-
-bool RenderText::IsPositionAtWordSelectionBoundary(size_t pos) {
- return pos == 0 || (isalnum(text()[pos - 1]) && !isalnum(text()[pos])) ||
- (!isalnum(text()[pos - 1]) && isalnum(text()[pos]));
-}
-
-} // namespace gfx
diff --git a/ui/gfx/render_text.h b/ui/gfx/render_text.h
deleted file mode 100755
index bcc2175..0000000
--- a/ui/gfx/render_text.h
+++ /dev/null
@@ -1,224 +0,0 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef UI_GFX_RENDER_TEXT_H_
-#define UI_GFX_RENDER_TEXT_H_
-#pragma once
-
-#include <vector>
-
-#include "base/gtest_prod_util.h"
-#include "base/i18n/rtl.h"
-#include "base/string16.h"
-#include "third_party/skia/include/core/SkColor.h"
-#include "ui/base/range/range.h"
-#include "ui/gfx/font.h"
-#include "ui/gfx/rect.h"
-#include "ui/gfx/point.h"
-
-namespace {
-
-// Strike line width.
-const int kStrikeWidth = 2;
-
-// Color settings for text, backgrounds and cursor.
-// These are tentative, and should be derived from theme, system
-// settings and current settings.
-// TODO(oshima): Change this to match the standard chrome
-// before dogfooding textfield views.
-const SkColor kSelectedTextColor = SK_ColorWHITE;
-const SkColor kFocusedSelectionColor = SK_ColorCYAN;
-const SkColor kUnfocusedSelectionColor = SK_ColorLTGRAY;
-const SkColor kCursorColor = SK_ColorBLACK;
-
-} // namespace
-
-namespace gfx {
-
-class Canvas;
-class RenderTextTest;
-
-// A visual style applicable to a range of text.
-struct StyleRange {
- StyleRange();
-
- Font font;
- SkColor foreground;
- bool strike;
- bool underline;
- ui::Range range;
-};
-
-typedef std::vector<StyleRange> StyleRanges;
-
-// TODO(msw): Distinguish between logical character and glyph?
-enum BreakType {
- CHARACTER_BREAK,
- WORD_BREAK,
- LINE_BREAK,
-};
-
-// TODO(msw): Implement RenderText[Win|Linux] for Uniscribe/Pango BiDi...
-
-// RenderText represents an abstract model of styled text and its corresponding
-// visual layout. Support is built in for a cursor, a selection, simple styling,
-// complex scripts, and bi-directional text. Implementations provide mechanisms
-// for rendering and translation between logical and visual data.
-class RenderText {
-
- public:
- virtual ~RenderText();
-
- // Creates a platform-specific RenderText instance.
- static RenderText* CreateRenderText();
-
- const string16& text() const { return text_; }
- virtual void SetText(const string16& text);
-
- bool cursor_visible() const { return cursor_visible_; }
- void set_cursor_visible(bool visible) { cursor_visible_ = visible; }
-
- bool insert_mode() const { return insert_mode_; }
- void toggle_insert_mode() { insert_mode_ = !insert_mode_; }
-
- bool focused() const { return focused_; }
- void set_focused(bool focused) { focused_ = focused; }
-
- const StyleRange& default_style() const { return default_style_; }
- void set_default_style(StyleRange style) { default_style_ = style; }
-
- const gfx::Rect& display_rect() const { return display_rect_; }
- void set_display_rect(const gfx::Rect& r) { display_rect_ = r; }
-
- size_t GetCursorPosition() const;
- void SetCursorPosition(const size_t position);
-
- // Moves the cursor left or right. Cursor movement is visual, meaning that
- // left and right are relative to screen, not the directionality of the text.
- // If |select| is false, the selection range is emptied at the new position.
- // If |break_type| is CHARACTER_BREAK, move to the neighboring character.
- // If |break_type| is WORD_BREAK, move to the nearest word boundary.
- // If |break_type| is LINE_BREAK, move to text edge as shown on screen.
- void MoveCursorLeft(BreakType break_type, bool select);
- void MoveCursorRight(BreakType break_type, bool select);
-
- // Moves the cursor to the specified logical |position|.
- // If |select| is false, the selection range is emptied at the new position.
- // Returns true if the cursor position or selection range changed.
- bool MoveCursorTo(size_t position, bool select);
-
- // Move the cursor to the position associated with the clicked point.
- // If |select| is false, the selection range is emptied at the new position.
- bool MoveCursorTo(const gfx::Point& point, bool select);
-
- const ui::Range& GetSelection() const;
- void SetSelection(const ui::Range& range);
-
- // Returns true if the local point is over selected text.
- bool IsPointInSelection(const gfx::Point& point) const;
-
- // Selects no text, all text, or the word at the current cursor position.
- void ClearSelection();
- void SelectAll();
- void SelectWord();
-
- const ui::Range& GetCompositionRange() const;
- void SetCompositionRange(const ui::Range& composition_range);
-
- // Apply |style_range| to the internal style model.
- virtual void ApplyStyleRange(StyleRange style_range);
-
- // Apply |default_style_| over the entire text range.
- virtual void ApplyDefaultStyle();
-
- base::i18n::TextDirection GetTextDirection() const;
-
- // Get the width of the entire string.
- int GetStringWidth() const;
-
- virtual void Draw(gfx::Canvas* canvas);
-
- // TODO(msw): Deprecate this function. Logical and visual cursors are not
- // mapped one-to-one. See the selection_range_ TODO for more information.
- // Get the logical cursor position from a visual point in local coordinates.
- virtual size_t FindCursorPosition(const gfx::Point& point) const;
-
- // Get the visual bounds containing the logical substring within |range|.
- // These bounds could be visually discontiguous if the logical selection range
- // is split by an odd number of LTR/RTL level change.
- virtual std::vector<gfx::Rect> GetSubstringBounds(
- const ui::Range& range) const;
-
- // Get the visual bounds describing the cursor at |position|. These bounds
- // typically represent a vertical line, but if |insert_mode| is true they
- // contain the bounds of the associated glyph.
- virtual gfx::Rect GetCursorBounds(size_t position, bool insert_mode) const;
-
- protected:
- RenderText();
-
- const StyleRanges& style_ranges() const { return style_ranges_; }
-
- const gfx::Point& display_offset() const { return display_offset_; }
-
- // Get the cursor position that visually neighbors |position|.
- // If |move_by_word| is true, return the neighboring word delimiter position.
- virtual size_t GetLeftCursorPosition(size_t position,
- bool move_by_word) const;
- virtual size_t GetRightCursorPosition(size_t position,
- bool move_by_word) const;
-
- private:
- friend class RenderTextTest;
-
- FRIEND_TEST_ALL_PREFIXES(RenderTextTest, DefaultStyle);
- FRIEND_TEST_ALL_PREFIXES(RenderTextTest, CustomDefaultStyle);
- FRIEND_TEST_ALL_PREFIXES(RenderTextTest, ApplyStyleRange);
- FRIEND_TEST_ALL_PREFIXES(RenderTextTest, StyleRangesAdjust);
-
- // Clear out |style_ranges_|.
- void ClearStyleRanges();
-
- bool IsPositionAtWordSelectionBoundary(size_t pos);
-
- // Logical UTF-16 string data to be drawn.
- string16 text_;
-
- // TODO(msw): A single logical cursor position doesn't support two potential
- // visual cursor positions. For example, clicking right of 'c' & 'D' yeilds:
- // (visually: 'abc|FEDghi' and 'abcFED|ghi', both logically: 'abc|DEFghi').
- // Similarly, one visual position may have two associated logical positions.
- // For example, clicking the right side of 'D' and left side of 'g' yields:
- // (both visually: 'abcFED|ghi', logically: 'abc|DEFghi' and 'abcDEF|ghi').
- // Update the cursor model with a leading/trailing flag, a level association,
- // or a disjoint visual position to satisfy the proposed visual behavior.
- // Logical selection range; the range end is also the logical cursor position.
- ui::Range selection_range_;
-
- // The cursor visibility and insert mode.
- bool cursor_visible_;
- bool insert_mode_;
-
- // The focus state of the text.
- bool focused_;
-
- // Composition text range.
- ui::Range composition_range_;
-
- // List of style ranges. Elements in the list never overlap each other.
- StyleRanges style_ranges_;
- // The default text style.
- StyleRange default_style_;
-
- // The local display area for rendering the text.
- gfx::Rect display_rect_;
- // The offset for the text to be drawn, relative to the display area.
- gfx::Point display_offset_;
-
- DISALLOW_COPY_AND_ASSIGN(RenderText);
-};
-
-} // namespace gfx
-
-#endif // UI_GFX_RENDER_TEXT_H_
diff --git a/ui/gfx/render_text_linux.cc b/ui/gfx/render_text_linux.cc
deleted file mode 100755
index 21b3ac4..0000000
--- a/ui/gfx/render_text_linux.cc
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ui/gfx/render_text_linux.h"
-
-namespace gfx {
-
-RenderTextLinux::RenderTextLinux()
- : RenderText() {
-}
-
-RenderTextLinux::~RenderTextLinux() {
-}
-
-RenderText* RenderText::CreateRenderText() {
- return new RenderTextLinux;
-}
-
-} // namespace gfx
diff --git a/ui/gfx/render_text_linux.h b/ui/gfx/render_text_linux.h
deleted file mode 100755
index 2709bea..0000000
--- a/ui/gfx/render_text_linux.h
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef UI_GFX_RENDER_TEXT_LINUX_H_
-#define UI_GFX_RENDER_TEXT_LINUX_H_
-#pragma once
-
-#include "ui/gfx/render_text.h"
-
-namespace gfx {
-
-// RenderTextLinux is the Linux implementation of RenderText using Pango.
-class RenderTextLinux : public RenderText {
- public:
- RenderTextLinux();
- virtual ~RenderTextLinux();
-
-private:
- DISALLOW_COPY_AND_ASSIGN(RenderTextLinux);
-};
-
-} // namespace gfx;
-
-#endif // UI_GFX_RENDER_TEXT_LINUX_H_
diff --git a/ui/gfx/render_text_unittest.cc b/ui/gfx/render_text_unittest.cc
deleted file mode 100755
index e809d41..0000000
--- a/ui/gfx/render_text_unittest.cc
+++ /dev/null
@@ -1,180 +0,0 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ui/gfx/render_text.h"
-
-#include "base/utf_string_conversions.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace gfx {
-
-class RenderTextTest : public testing::Test {
-};
-
-TEST_F(RenderTextTest, DefaultStyle) {
- // Defaults to empty text with no styles.
- gfx::RenderText* render_text = gfx::RenderText::CreateRenderText();
- EXPECT_TRUE(render_text->text().empty());
- EXPECT_TRUE(render_text->style_ranges().empty());
-
- // Test that the built-in default style is applied for new text.
- render_text->SetText(ASCIIToUTF16("abc"));
- EXPECT_EQ(1U, render_text->style_ranges().size());
- gfx::StyleRange style;
- EXPECT_EQ(style.font.GetFontName(),
- render_text->style_ranges()[0].font.GetFontName());
- EXPECT_EQ(style.foreground, render_text->style_ranges()[0].foreground);
- EXPECT_EQ(ui::Range(0, 3), render_text->style_ranges()[0].range);
- EXPECT_EQ(style.strike, render_text->style_ranges()[0].strike);
- EXPECT_EQ(style.underline, render_text->style_ranges()[0].underline);
-
- // Test that clearing the text also clears the styles.
- render_text->SetText(string16());
- EXPECT_TRUE(render_text->text().empty());
- EXPECT_TRUE(render_text->style_ranges().empty());
-}
-
-TEST_F(RenderTextTest, CustomDefaultStyle) {
- // Test a custom default style.
- gfx::RenderText* render_text = gfx::RenderText::CreateRenderText();
- gfx::StyleRange color;
- color.foreground = SK_ColorRED;
- render_text->set_default_style(color);
- render_text->SetText(ASCIIToUTF16("abc"));
- EXPECT_EQ(1U, render_text->style_ranges().size());
- EXPECT_EQ(color.foreground, render_text->style_ranges()[0].foreground);
-
- // Test that the custom default style persists across clearing text.
- render_text->SetText(string16());
- EXPECT_TRUE(render_text->style_ranges().empty());
- render_text->SetText(ASCIIToUTF16("abc"));
- EXPECT_EQ(1U, render_text->style_ranges().size());
- EXPECT_EQ(color.foreground, render_text->style_ranges()[0].foreground);
-
- // Test ApplyDefaultStyle after setting a new default.
- gfx::StyleRange strike;
- strike.strike = true;
- render_text->set_default_style(strike);
- render_text->ApplyDefaultStyle();
- EXPECT_EQ(1U, render_text->style_ranges().size());
- EXPECT_EQ(true, render_text->style_ranges()[0].strike);
- EXPECT_EQ(strike.foreground, render_text->style_ranges()[0].foreground);
-}
-
-TEST_F(RenderTextTest, ApplyStyleRange) {
- gfx::RenderText* render_text = gfx::RenderText::CreateRenderText();
- render_text->SetText(ASCIIToUTF16("01234"));
- EXPECT_EQ(1U, render_text->style_ranges().size());
-
- // Test ApplyStyleRange (no-op on empty range).
- gfx::StyleRange empty;
- empty.range = ui::Range(1, 1);
- render_text->ApplyStyleRange(empty);
- EXPECT_EQ(1U, render_text->style_ranges().size());
-
- // Test ApplyStyleRange (no-op on invalid range).
- gfx::StyleRange invalid;
- invalid.range = ui::Range::InvalidRange();
- render_text->ApplyStyleRange(invalid);
- EXPECT_EQ(1U, render_text->style_ranges().size());
-
- // Apply a style with a range contained by an existing range.
- gfx::StyleRange underline;
- underline.underline = true;
- underline.range = ui::Range(2, 3);
- render_text->ApplyStyleRange(underline);
- EXPECT_EQ(3U, render_text->style_ranges().size());
- EXPECT_EQ(ui::Range(0, 2), render_text->style_ranges()[0].range);
- EXPECT_FALSE(render_text->style_ranges()[0].underline);
- EXPECT_EQ(ui::Range(2, 3), render_text->style_ranges()[1].range);
- EXPECT_TRUE(render_text->style_ranges()[1].underline);
- EXPECT_EQ(ui::Range(3, 5), render_text->style_ranges()[2].range);
- EXPECT_FALSE(render_text->style_ranges()[2].underline);
-
- // Apply a style with a range equal to another range.
- gfx::StyleRange color;
- color.foreground = SK_ColorWHITE;
- color.range = ui::Range(2, 3);
- render_text->ApplyStyleRange(color);
- EXPECT_EQ(3U, render_text->style_ranges().size());
- EXPECT_EQ(ui::Range(0, 2), render_text->style_ranges()[0].range);
- EXPECT_NE(SK_ColorWHITE, render_text->style_ranges()[0].foreground);
- EXPECT_FALSE(render_text->style_ranges()[0].underline);
- EXPECT_EQ(ui::Range(2, 3), render_text->style_ranges()[1].range);
- EXPECT_EQ(SK_ColorWHITE, render_text->style_ranges()[1].foreground);
- EXPECT_FALSE(render_text->style_ranges()[1].underline);
- EXPECT_EQ(ui::Range(3, 5), render_text->style_ranges()[2].range);
- EXPECT_NE(SK_ColorWHITE, render_text->style_ranges()[2].foreground);
- EXPECT_FALSE(render_text->style_ranges()[2].underline);
-
- // Apply a style with a range containing an existing range.
- // This new style also overlaps portions of neighboring ranges.
- gfx::StyleRange strike;
- strike.strike = true;
- strike.range = ui::Range(1, 4);
- render_text->ApplyStyleRange(strike);
- EXPECT_EQ(3U, render_text->style_ranges().size());
- EXPECT_EQ(ui::Range(0, 1), render_text->style_ranges()[0].range);
- EXPECT_FALSE(render_text->style_ranges()[0].strike);
- EXPECT_EQ(ui::Range(1, 4), render_text->style_ranges()[1].range);
- EXPECT_TRUE(render_text->style_ranges()[1].strike);
- EXPECT_EQ(ui::Range(4, 5), render_text->style_ranges()[2].range);
- EXPECT_FALSE(render_text->style_ranges()[2].strike);
-
- // Apply a style overlapping all ranges.
- gfx::StyleRange strike_underline;
- strike_underline.strike = true;
- strike_underline.underline = true;
- strike_underline.range = ui::Range(0, render_text->text().length());
- render_text->ApplyStyleRange(strike_underline);
- EXPECT_EQ(1U, render_text->style_ranges().size());
- EXPECT_EQ(ui::Range(0, 5), render_text->style_ranges()[0].range);
- EXPECT_TRUE(render_text->style_ranges()[0].underline);
- EXPECT_TRUE(render_text->style_ranges()[0].strike);
-
- // Apply the default style.
- render_text->ApplyDefaultStyle();
- EXPECT_EQ(1U, render_text->style_ranges().size());
- EXPECT_EQ(ui::Range(0, 5), render_text->style_ranges()[0].range);
- EXPECT_FALSE(render_text->style_ranges()[0].underline);
- EXPECT_FALSE(render_text->style_ranges()[0].strike);
-}
-
-TEST_F(RenderTextTest, StyleRangesAdjust) {
- // Test that style ranges adjust to the text size.
- gfx::RenderText* render_text = gfx::RenderText::CreateRenderText();
- render_text->SetText(ASCIIToUTF16("abcdef"));
- EXPECT_EQ(1U, render_text->style_ranges().size());
- EXPECT_EQ(ui::Range(0, 6), render_text->style_ranges()[0].range);
-
- // Test that the range is clipped to the length of shorter text.
- render_text->SetText(ASCIIToUTF16("abc"));
- EXPECT_EQ(1U, render_text->style_ranges().size());
- EXPECT_EQ(ui::Range(0, 3), render_text->style_ranges()[0].range);
-
- // Test that the last range extends to the length of longer text.
- gfx::StyleRange strike;
- strike.strike = true;
- strike.range = ui::Range(2, 3);
- render_text->ApplyStyleRange(strike);
- render_text->SetText(ASCIIToUTF16("abcdefghi"));
- EXPECT_EQ(2U, render_text->style_ranges().size());
- EXPECT_EQ(ui::Range(0, 2), render_text->style_ranges()[0].range);
- EXPECT_EQ(ui::Range(2, 9), render_text->style_ranges()[1].range);
- EXPECT_TRUE(render_text->style_ranges()[1].strike);
-
- // Test that ranges are removed if they're outside the range of shorter text.
- render_text->SetText(ASCIIToUTF16("ab"));
- EXPECT_EQ(1U, render_text->style_ranges().size());
- EXPECT_EQ(ui::Range(0, 2), render_text->style_ranges()[0].range);
- EXPECT_FALSE(render_text->style_ranges()[0].strike);
-
- // Test that previously removed ranges don't return.
- render_text->SetText(ASCIIToUTF16("abcdef"));
- EXPECT_EQ(1U, render_text->style_ranges().size());
- EXPECT_EQ(ui::Range(0, 6), render_text->style_ranges()[0].range);
- EXPECT_FALSE(render_text->style_ranges()[0].strike);
-}
-
-} // namespace gfx
diff --git a/ui/gfx/render_text_win.cc b/ui/gfx/render_text_win.cc
deleted file mode 100755
index 9f47946..0000000
--- a/ui/gfx/render_text_win.cc
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ui/gfx/render_text_win.h"
-
-namespace gfx {
-
-RenderTextWin::RenderTextWin()
- : RenderText() {
-}
-
-RenderTextWin::~RenderTextWin() {
-}
-
-RenderText* RenderText::CreateRenderText() {
- return new RenderTextWin;
-}
-
-} // namespace gfx
diff --git a/ui/gfx/render_text_win.h b/ui/gfx/render_text_win.h
deleted file mode 100755
index 829888b..0000000
--- a/ui/gfx/render_text_win.h
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef UI_GFX_RENDER_TEXT_WIN_H_
-#define UI_GFX_RENDER_TEXT_WIN_H_
-#pragma once
-
-#include "ui/gfx/render_text.h"
-
-namespace gfx {
-
-// RenderTextWin is the Windows implementation of RenderText using Uniscribe.
-class RenderTextWin : public RenderText {
- public:
- RenderTextWin();
- virtual ~RenderTextWin();
-
- private:
- DISALLOW_COPY_AND_ASSIGN(RenderTextWin);
-};
-
-} // namespace gfx;
-
-#endif // UI_GFX_RENDER_TEXT_WIN_H_
diff --git a/ui/ui.gyp b/ui/ui.gyp
index 9306181..39cedb6 100644
--- a/ui/ui.gyp
+++ b/ui/ui.gyp
@@ -249,12 +249,6 @@
'gfx/point.h',
'gfx/rect.cc',
'gfx/rect.h',
- 'gfx/render_text.cc',
- 'gfx/render_text.h',
- 'gfx/render_text_linux.cc',
- 'gfx/render_text_linux.h',
- 'gfx/render_text_win.cc',
- 'gfx/render_text_win.h',
'gfx/screen.h',
'gfx/screen_gtk.cc',
'gfx/screen_win.cc',
@@ -402,16 +396,6 @@
'gfx/native_theme_chromeos.h',
],
}],
- ['toolkit_views==0', {
- 'sources/': [
- ['exclude', '^gfx/render_text.cc'],
- ['exclude', '^gfx/render_text.h'],
- ['exclude', '^gfx/render_text_linux.cc'],
- ['exclude', '^gfx/render_text_linux.h'],
- ['exclude', '^gfx/render_text_win.cc'],
- ['exclude', '^gfx/render_text_win.h'],
- ],
- }],
],
},
{
diff --git a/ui/ui_unittests.gypi b/ui/ui_unittests.gypi
index 9839df6..5c7f9b7 100644
--- a/ui/ui_unittests.gypi
+++ b/ui/ui_unittests.gypi
@@ -126,11 +126,6 @@
}],
],
}],
- ['toolkit_views==1', {
- 'sources': [
- 'gfx/render_text_unittest.cc',
- ],
- }],
],
},
],
diff --git a/views/controls/textfield/native_textfield_gtk.cc b/views/controls/textfield/native_textfield_gtk.cc
index 3d799a8..78fb2f9 100644
--- a/views/controls/textfield/native_textfield_gtk.cc
+++ b/views/controls/textfield/native_textfield_gtk.cc
@@ -283,11 +283,17 @@ TextInputClient* NativeTextfieldGtk::GetTextInputClient() {
return NULL;
}
-void NativeTextfieldGtk::ApplyStyleRange(const gfx::StyleRange& style) {
+TextStyle* NativeTextfieldGtk::CreateTextStyle() {
+ NOTREACHED();
+ return NULL;
+}
+
+void NativeTextfieldGtk::ApplyTextStyle(const TextStyle* style,
+ const ui::Range& range) {
NOTREACHED();
}
-void NativeTextfieldGtk::ApplyDefaultStyle() {
+void NativeTextfieldGtk::ClearAllTextStyles() {
NOTREACHED();
}
diff --git a/views/controls/textfield/native_textfield_gtk.h b/views/controls/textfield/native_textfield_gtk.h
index 8420fcb..92b4e36 100644
--- a/views/controls/textfield/native_textfield_gtk.h
+++ b/views/controls/textfield/native_textfield_gtk.h
@@ -57,8 +57,10 @@ class NativeTextfieldGtk : public NativeControlGtk,
virtual void HandleFocus() OVERRIDE;
virtual void HandleBlur() OVERRIDE;
virtual TextInputClient* GetTextInputClient() OVERRIDE;
- virtual void ApplyStyleRange(const gfx::StyleRange& style) OVERRIDE;
- virtual void ApplyDefaultStyle() OVERRIDE;
+ virtual TextStyle* CreateTextStyle() OVERRIDE;
+ virtual void ApplyTextStyle(const TextStyle* style,
+ const ui::Range& range) OVERRIDE;
+ virtual void ClearAllTextStyles() OVERRIDE;
virtual void ClearEditHistory() OVERRIDE;
// Overridden from NativeControlGtk:
diff --git a/views/controls/textfield/native_textfield_views.cc b/views/controls/textfield/native_textfield_views.cc
index b3f005d..adc6288 100644
--- a/views/controls/textfield/native_textfield_views.cc
+++ b/views/controls/textfield/native_textfield_views.cc
@@ -16,12 +16,12 @@
#include "ui/base/range/range.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/insets.h"
-#include "ui/gfx/render_text.h"
#include "views/background.h"
#include "views/border.h"
#include "views/controls/focusable_border.h"
#include "views/controls/menu/menu_item_view.h"
#include "views/controls/menu/menu_model_adapter.h"
+#include "views/controls/textfield/text_style.h"
#include "views/controls/textfield/textfield.h"
#include "views/controls/textfield/textfield_controller.h"
#include "views/controls/textfield/textfield_views_model.h"
@@ -37,8 +37,15 @@
namespace {
-// Text color for read only.
-const SkColor kReadonlyTextColor = SK_ColorDKGRAY;
+// Color settings for text, backgrounds and cursor.
+// These are tentative, and should be derived from theme, system
+// settings and current settings.
+// TODO(oshima): Change this to match the standard chrome
+// before dogfooding textfield views.
+const SkColor kSelectedTextColor = SK_ColorWHITE;
+const SkColor kFocusedSelectionColor = SK_ColorCYAN;
+const SkColor kUnfocusedSelectionColor = SK_ColorLTGRAY;
+const SkColor kCursorColor = SK_ColorBLACK;
// Parameters to control cursor blinking.
const int kCursorVisibleTimeMs = 800;
@@ -55,8 +62,9 @@ NativeTextfieldViews::NativeTextfieldViews(Textfield* parent)
: textfield_(parent),
ALLOW_THIS_IN_INITIALIZER_LIST(model_(new TextfieldViewsModel(this))),
text_border_(new FocusableBorder()),
+ text_offset_(0),
+ insert_(true),
is_cursor_visible_(false),
- is_drop_cursor_visible_(false),
skip_input_method_cancel_composition_(false),
initiating_drag_(false),
ALLOW_THIS_IN_INITIALIZER_LIST(cursor_timer_(this)),
@@ -68,12 +76,6 @@ NativeTextfieldViews::NativeTextfieldViews(Textfield* parent)
// Lowercase is not supported.
DCHECK_NE(parent->style(), Textfield::STYLE_LOWERCASE);
- // Set the default text style.
- gfx::StyleRange default_style;
- default_style.font = textfield_->font();
- default_style.foreground = textfield_->text_color();
- GetRenderText()->set_default_style(default_style);
-
set_context_menu_controller(this);
set_drag_controller(this);
}
@@ -103,7 +105,7 @@ bool NativeTextfieldViews::OnMousePressed(const MouseEvent& event) {
initiating_drag_ = false;
switch(aggregated_clicks_) {
case 0:
- if (!GetRenderText()->IsPointInSelection(event.location()))
+ if (!IsPointInSelection(event.location()))
MoveCursorTo(event.location(), event.IsShiftDown());
else
initiating_drag_ = true;
@@ -174,14 +176,14 @@ bool NativeTextfieldViews::CanDrop(const OSExchangeData& data) {
int NativeTextfieldViews::OnDragUpdated(const DropTargetEvent& event) {
DCHECK(CanDrop(event.data()));
- bool in_selection = GetRenderText()->IsPointInSelection(event.location());
- is_drop_cursor_visible_ = !in_selection;
+ bool is_point_in_selection = IsPointInSelection(event.location());
+ is_drop_cursor_visible_ = !is_point_in_selection;
// TODO(msw): Pan over text when the user drags to the visible text edge.
- OnCaretBoundsChanged();
+ UpdateCursorBoundsAndTextOffset(FindCursorPosition(event.location()), true);
SchedulePaint();
if (initiating_drag_) {
- if (in_selection)
+ if (is_point_in_selection)
return ui::DragDropTypes::DRAG_NONE;
return event.IsControlDown() ? ui::DragDropTypes::DRAG_COPY :
ui::DragDropTypes::DRAG_MOVE;
@@ -191,14 +193,11 @@ int NativeTextfieldViews::OnDragUpdated(const DropTargetEvent& event) {
int NativeTextfieldViews::OnPerformDrop(const DropTargetEvent& event) {
DCHECK(CanDrop(event.data()));
- DCHECK(!initiating_drag_ ||
- !GetRenderText()->IsPointInSelection(event.location()));
+ DCHECK(!initiating_drag_ || !IsPointInSelection(event.location()));
OnBeforeUserAction();
skip_input_method_cancel_composition_ = true;
- // TODO(msw): Remove final reference to FindCursorPosition.
- size_t drop_destination =
- GetRenderText()->FindCursorPosition(event.location());
+ size_t drop_destination = FindCursorPosition(event.location());
string16 text;
event.data().GetString(&text);
@@ -216,7 +215,7 @@ int NativeTextfieldViews::OnPerformDrop(const DropTargetEvent& event) {
model_->DeleteSelectionAndInsertTextAt(text, drop_destination);
} else {
model_->MoveCursorTo(drop_destination, false);
- // Drop always inserts text even if the textfield is not in insert mode.
+ // Drop always inserts a text even if insert_ == false.
model_->InsertText(text);
}
skip_input_method_cancel_composition_ = false;
@@ -247,15 +246,14 @@ void NativeTextfieldViews::OnBlur() {
}
gfx::NativeCursor NativeTextfieldViews::GetCursor(const MouseEvent& event) {
- bool in_selection = GetRenderText()->IsPointInSelection(event.location());
- bool drag_event = event.type() == ui::ET_MOUSE_DRAGGED;
- bool text_cursor = !initiating_drag_ && (drag_event || !in_selection);
+ bool text = !initiating_drag_ && (event.type() == ui::ET_MOUSE_DRAGGED ||
+ !IsPointInSelection(event.location()));
#if defined(OS_WIN)
static HCURSOR ibeam = LoadCursor(NULL, IDC_IBEAM);
static HCURSOR arrow = LoadCursor(NULL, IDC_ARROW);
- return text_cursor ? ibeam : arrow;
+ return text ? ibeam : arrow;
#else
- return text_cursor ? gfx::GetCursor(GDK_XTERM) : NULL;
+ return text ? gfx::GetCursor(GDK_XTERM) : NULL;
#endif
}
@@ -284,7 +282,7 @@ void NativeTextfieldViews::WriteDragDataForView(views::View* sender,
int NativeTextfieldViews::GetDragOperationsForView(views::View* sender,
const gfx::Point& p) {
- if (!textfield_->IsEnabled() || !GetRenderText()->IsPointInSelection(p))
+ if (!textfield_->IsEnabled() || !IsPointInSelection(p))
return ui::DragDropTypes::DRAG_NONE;
if (sender == this && !textfield_->read_only())
return ui::DragDropTypes::DRAG_MOVE | ui::DragDropTypes::DRAG_COPY;
@@ -294,19 +292,19 @@ int NativeTextfieldViews::GetDragOperationsForView(views::View* sender,
bool NativeTextfieldViews::CanStartDragForView(View* sender,
const gfx::Point& press_pt,
const gfx::Point& p) {
- return GetRenderText()->IsPointInSelection(press_pt);
+ return IsPointInSelection(press_pt);
}
/////////////////////////////////////////////////////////////////
// NativeTextfieldViews, NativeTextifieldWrapper overrides:
string16 NativeTextfieldViews::GetText() const {
- return model_->GetText();
+ return model_->text();
}
void NativeTextfieldViews::UpdateText() {
model_->SetText(textfield_->text());
- OnCaretBoundsChanged();
+ UpdateCursorBoundsAndTextOffset(model_->cursor_pos(), insert_);
SchedulePaint();
}
@@ -314,7 +312,7 @@ void NativeTextfieldViews::AppendText(const string16& text) {
if (text.empty())
return;
model_->Append(text);
- OnCaretBoundsChanged();
+ UpdateCursorBoundsAndTextOffset(model_->cursor_pos(), insert_);
SchedulePaint();
}
@@ -355,24 +353,17 @@ void NativeTextfieldViews::UpdateBackgroundColor() {
}
void NativeTextfieldViews::UpdateReadOnly() {
- // Update the default text style.
- gfx::StyleRange default_style(GetRenderText()->default_style());
- default_style.foreground = textfield_->read_only() ? kReadonlyTextColor :
- textfield_->text_color();
- GetRenderText()->set_default_style(default_style);
- GetRenderText()->ApplyDefaultStyle();
-
SchedulePaint();
OnTextInputTypeChanged();
}
void NativeTextfieldViews::UpdateFont() {
- OnCaretBoundsChanged();
+ UpdateCursorBoundsAndTextOffset(model_->cursor_pos(), insert_);
}
void NativeTextfieldViews::UpdateIsPassword() {
model_->set_is_password(textfield_->IsPassword());
- OnCaretBoundsChanged();
+ UpdateCursorBoundsAndTextOffset(model_->cursor_pos(), insert_);
SchedulePaint();
OnTextInputTypeChanged();
}
@@ -394,7 +385,7 @@ void NativeTextfieldViews::UpdateHorizontalMargins() {
gfx::Insets inset = GetInsets();
text_border_->SetInsets(inset.top(), left, inset.bottom(), right);
- OnCaretBoundsChanged();
+ UpdateCursorBoundsAndTextOffset(model_->cursor_pos(), insert_);
}
void NativeTextfieldViews::UpdateVerticalMargins() {
@@ -402,8 +393,9 @@ void NativeTextfieldViews::UpdateVerticalMargins() {
if (!textfield_->GetVerticalMargins(&top, &bottom))
return;
gfx::Insets inset = GetInsets();
+
text_border_->SetInsets(top, inset.left(), bottom, inset.right());
- OnCaretBoundsChanged();
+ UpdateCursorBoundsAndTextOffset(model_->cursor_pos(), insert_);
}
bool NativeTextfieldViews::SetFocus() {
@@ -429,12 +421,12 @@ void NativeTextfieldViews::GetSelectedRange(ui::Range* range) const {
void NativeTextfieldViews::SelectRange(const ui::Range& range) {
model_->SelectRange(range);
- OnCaretBoundsChanged();
+ UpdateCursorBoundsAndTextOffset(model_->cursor_pos(), insert_);
SchedulePaint();
}
size_t NativeTextfieldViews::GetCursorPosition() const {
- return model_->GetCursorPosition();
+ return model_->cursor_pos();
}
bool NativeTextfieldViews::HandleKeyPressed(const KeyEvent& e) {
@@ -450,7 +442,6 @@ bool NativeTextfieldViews::HandleKeyReleased(const KeyEvent& e) {
}
void NativeTextfieldViews::HandleFocus() {
- GetRenderText()->set_focused(true);
is_cursor_visible_ = true;
SchedulePaint();
OnCaretBoundsChanged();
@@ -462,7 +453,6 @@ void NativeTextfieldViews::HandleFocus() {
}
void NativeTextfieldViews::HandleBlur() {
- GetRenderText()->set_focused(false);
// Stop blinking cursor.
cursor_timer_.RevokeAll();
if (is_cursor_visible_) {
@@ -546,25 +536,23 @@ void NativeTextfieldViews::ExecuteCommand(int command_id) {
OnAfterUserAction();
}
-void NativeTextfieldViews::ApplyStyleRange(const gfx::StyleRange& style) {
- GetRenderText()->ApplyStyleRange(style);
+TextStyle* NativeTextfieldViews::CreateTextStyle() {
+ return model_->CreateTextStyle();
+}
+
+void NativeTextfieldViews::ApplyTextStyle(const TextStyle* style,
+ const ui::Range& range) {
+ model_->ApplyTextStyle(style, range);
SchedulePaint();
}
-void NativeTextfieldViews::ApplyDefaultStyle() {
- GetRenderText()->ApplyDefaultStyle();
+void NativeTextfieldViews::ClearAllTextStyles() {
+ model_->ClearAllTextStyles();
SchedulePaint();
}
void NativeTextfieldViews::OnBoundsChanged(const gfx::Rect& previous_bounds) {
- // Set the RenderText display area.
- gfx::Insets insets = GetInsets();
- gfx::Rect display_rect(insets.left(),
- insets.top(),
- width() - insets.width(),
- height() - insets.height());
- GetRenderText()->set_display_rect(display_rect);
- OnCaretBoundsChanged();
+ UpdateCursorBoundsAndTextOffset(model_->cursor_pos(), insert_);
}
///////////////////////////////////////////////////////////////////////////////
@@ -614,7 +602,7 @@ void NativeTextfieldViews::InsertText(const string16& text) {
OnBeforeUserAction();
skip_input_method_cancel_composition_ = true;
- if (GetRenderText()->insert_mode())
+ if (insert_)
model_->InsertText(text);
else
model_->ReplaceText(text);
@@ -631,7 +619,7 @@ void NativeTextfieldViews::InsertChar(char16 ch, int flags) {
OnBeforeUserAction();
skip_input_method_cancel_composition_ = true;
- if (GetRenderText()->insert_mode())
+ if (insert_)
model_->InsertChar(ch);
else
model_->ReplaceChar(ch);
@@ -649,9 +637,7 @@ ui::TextInputType NativeTextfieldViews::GetTextInputType() {
}
gfx::Rect NativeTextfieldViews::GetCaretBounds() {
- gfx::RenderText* render_text = GetRenderText();
- return render_text->GetCursorBounds(render_text->GetCursorPosition(),
- render_text->insert_mode());
+ return cursor_bounds_;
}
bool NativeTextfieldViews::HasCompositionText() {
@@ -739,8 +725,12 @@ void NativeTextfieldViews::OnCompositionTextConfirmedOrCleared() {
textfield_->GetInputMethod()->CancelComposition(textfield_);
}
-gfx::RenderText* NativeTextfieldViews::GetRenderText() const {
- return model_->render_text();
+const gfx::Font& NativeTextfieldViews::GetFont() const {
+ return textfield_->font();
+}
+
+SkColor NativeTextfieldViews::GetTextColor() const {
+ return textfield_->text_color();
}
void NativeTextfieldViews::UpdateCursor() {
@@ -753,18 +743,108 @@ void NativeTextfieldViews::UpdateCursor() {
}
void NativeTextfieldViews::RepaintCursor() {
- gfx::Rect r(GetCaretBounds());
+ gfx::Rect r = cursor_bounds_;
r.Inset(-1, -1, -1, -1);
SchedulePaintInRect(r);
}
+gfx::Rect NativeTextfieldViews::GetCursorBounds(size_t cursor_pos,
+ bool insert_mode) const {
+ string16 text = model_->GetVisibleText();
+ const gfx::Font& font = GetFont();
+ int x = font.GetStringWidth(text.substr(0U, cursor_pos));
+ DCHECK_GE(x, 0);
+ int h = std::min(height() - GetInsets().height(), font.GetHeight());
+ gfx::Rect bounds(x, (height() - h) / 2, 0, h);
+ if (!insert_mode && text.length() != cursor_pos)
+ bounds.set_width(font.GetStringWidth(text.substr(0, cursor_pos + 1)) - x);
+ return bounds;
+}
+
+
+void NativeTextfieldViews::UpdateCursorBoundsAndTextOffset(size_t cursor_pos,
+ bool insert_mode) {
+ if (bounds().IsEmpty())
+ return;
+
+ // TODO(oshima): bidi
+ int width = bounds().width() - GetInsets().width();
+ int full_width = GetFont().GetStringWidth(model_->GetVisibleText());
+ cursor_bounds_ = GetCursorBounds(cursor_pos, insert_mode);
+
+ if (full_width < width) {
+ // Show all text whenever the text fits to the size.
+ text_offset_ = 0;
+ } else if ((text_offset_ + cursor_bounds_.right()) > width) {
+ // when the cursor overflows to the right
+ text_offset_ = width - cursor_bounds_.right();
+ } else if ((text_offset_ + cursor_bounds_.x()) < 0) {
+ // when the cursor overflows to the left
+ text_offset_ = -cursor_bounds_.x();
+ } else if (full_width > width && text_offset_ + full_width < width) {
+ // when the cursor moves within the textfield with the text
+ // longer than the field.
+ text_offset_ = width - full_width;
+ } else {
+ // move cursor freely.
+ }
+ // shift cursor bounds to fit insets.
+ cursor_bounds_.set_x(cursor_bounds_.x() + text_offset_ + GetInsets().left());
+
+ OnCaretBoundsChanged();
+}
+
void NativeTextfieldViews::PaintTextAndCursor(gfx::Canvas* canvas) {
+ gfx::Insets insets = GetInsets();
+
canvas->Save();
- GetRenderText()->set_cursor_visible(is_drop_cursor_visible_ ||
- (is_cursor_visible_ && !model_->HasSelection()));
- // Draw the text, cursor, and selection.
- GetRenderText()->Draw(canvas);
+ canvas->ClipRectInt(insets.left(), insets.top(),
+ width() - insets.width(), height() - insets.height());
+
+ // TODO(oshima): bidi support
+ // TODO(varunjain): re-implement this so only that dirty text is painted.
+ TextfieldViewsModel::TextFragments fragments;
+ model_->GetFragments(&fragments);
+ int x_offset = text_offset_ + insets.left();
+ int y = insets.top();
+ int text_height = height() - insets.height();
+ SkColor selection_color =
+ textfield_->HasFocus() ?
+ kFocusedSelectionColor : kUnfocusedSelectionColor;
+ gfx::Font font = GetFont();
+ gfx::Rect selection_bounds = model_->GetSelectionBounds(font);
+
+ if (!selection_bounds.IsEmpty()) {
+ canvas->FillRectInt(selection_color,
+ x_offset + selection_bounds.x(),
+ (height() - selection_bounds.height()) / 2,
+ selection_bounds.width(),
+ selection_bounds.height());
+ }
+
+ for (TextfieldViewsModel::TextFragments::const_iterator iter =
+ fragments.begin();
+ iter != fragments.end();
+ iter++) {
+ string16 text = model_->GetVisibleText(iter->range.start(),
+ iter->range.end());
+ // TODO(oshima): This does not give the accurate position due to
+ // kerning. Figure out how to do.
+ int width = font.GetStringWidth(text);
+ iter->style->DrawString(canvas, text, font, textfield_->read_only(),
+ x_offset, y, width, text_height);
+ x_offset += width;
+ }
canvas->Restore();
+
+ // Paint cursor. Replace cursor is drawn as rectangle for now.
+ if (textfield_->IsEnabled() && (is_drop_cursor_visible_ ||
+ (is_cursor_visible_ && !model_->HasSelection())))
+ canvas->DrawRectInt(kCursorColor,
+ cursor_bounds_.x(),
+ cursor_bounds_.y(),
+ cursor_bounds_.width(),
+ cursor_bounds_.height());
}
bool NativeTextfieldViews::HandleKeyEvent(const KeyEvent& key_event) {
@@ -809,21 +889,21 @@ bool NativeTextfieldViews::HandleKeyEvent(const KeyEvent& key_event) {
cursor_changed = text_changed = Paste();
break;
case ui::VKEY_RIGHT:
- model_->MoveCursorRight(
- control ? gfx::WORD_BREAK : gfx::CHARACTER_BREAK, selection);
+ control ? model_->MoveCursorToNextWord(selection)
+ : model_->MoveCursorRight(selection);
cursor_changed = true;
break;
case ui::VKEY_LEFT:
- model_->MoveCursorLeft(
- control ? gfx::WORD_BREAK : gfx::CHARACTER_BREAK, selection);
+ control ? model_->MoveCursorToPreviousWord(selection)
+ : model_->MoveCursorLeft(selection);
cursor_changed = true;
break;
case ui::VKEY_END:
- model_->MoveCursorRight(gfx::LINE_BREAK, selection);
+ model_->MoveCursorToEnd(selection);
cursor_changed = true;
break;
case ui::VKEY_HOME:
- model_->MoveCursorLeft(gfx::LINE_BREAK, selection);
+ model_->MoveCursorToHome(selection);
cursor_changed = true;
break;
case ui::VKEY_BACK:
@@ -836,11 +916,11 @@ bool NativeTextfieldViews::HandleKeyEvent(const KeyEvent& key_event) {
#if defined(OS_WIN)
break;
#else
- model_->MoveCursorLeft(gfx::LINE_BREAK, true);
+ model_->MoveCursorToHome(true);
#endif
} else if (control) {
// If only control is pressed, then erase the previous word.
- model_->MoveCursorLeft(gfx::WORD_BREAK, true);
+ model_->MoveCursorToPreviousWord(true);
}
}
text_changed = model_->Backspace();
@@ -856,17 +936,17 @@ bool NativeTextfieldViews::HandleKeyEvent(const KeyEvent& key_event) {
#if defined(OS_WIN)
break;
#else
- model_->MoveCursorRight(gfx::LINE_BREAK, true);
+ model_->MoveCursorToEnd(true);
#endif
} else if (control) {
// If only control is pressed, then erase the next word.
- model_->MoveCursorRight(gfx::WORD_BREAK, true);
+ model_->MoveCursorToNextWord(true);
}
}
cursor_changed = text_changed = model_->Delete();
break;
case ui::VKEY_INSERT:
- GetRenderText()->toggle_insert_mode();
+ insert_ = !insert_;
cursor_changed = true;
break;
default:
@@ -883,11 +963,52 @@ bool NativeTextfieldViews::HandleKeyEvent(const KeyEvent& key_event) {
return false;
}
+size_t NativeTextfieldViews::FindCursorPosition(const gfx::Point& point) const {
+ // TODO(oshima): BIDI/i18n support.
+ gfx::Font font = GetFont();
+ gfx::Insets insets = GetInsets();
+ string16 text = model_->GetVisibleText();
+ int left = 0;
+ int left_pos = 0;
+ int right = font.GetStringWidth(text);
+ int right_pos = text.length();
+
+ int x = point.x() - insets.left() - text_offset_;
+ if (x <= left) return left_pos;
+ if (x >= right) return right_pos;
+ // binary searching the cursor position.
+ // TODO(oshima): use the center of character instead of edge.
+ // Binary search may not work for language like arabic.
+ while (std::abs(static_cast<long>(right_pos - left_pos) > 1)) {
+ int pivot_pos = left_pos + (right_pos - left_pos) / 2;
+ int pivot = font.GetStringWidth(text.substr(0, pivot_pos));
+ if (pivot < x) {
+ left = pivot;
+ left_pos = pivot_pos;
+ } else if (pivot == x) {
+ return pivot_pos;
+ } else {
+ right = pivot;
+ right_pos = pivot_pos;
+ }
+ }
+ return left_pos;
+}
+
+bool NativeTextfieldViews::IsPointInSelection(const gfx::Point& point) const {
+ ui::Range range;
+ GetSelectedRange(&range);
+ size_t pos = FindCursorPosition(point);
+ return (pos >= range.GetMin() && pos < range.GetMax());
+}
+
bool NativeTextfieldViews::MoveCursorTo(const gfx::Point& point, bool select) {
- if (!model_->MoveCursorTo(point, select))
- return false;
- OnCaretBoundsChanged();
- return true;
+ size_t pos = FindCursorPosition(point);
+ if (model_->MoveCursorTo(pos, select)) {
+ UpdateCursorBoundsAndTextOffset(model_->cursor_pos(), insert_);
+ return true;
+ }
+ return false;
}
void NativeTextfieldViews::PropagateTextChange() {
@@ -903,7 +1024,7 @@ void NativeTextfieldViews::UpdateAfterChange(bool text_changed,
RepaintCursor();
}
if (text_changed || cursor_changed) {
- OnCaretBoundsChanged();
+ UpdateCursorBoundsAndTextOffset(model_->cursor_pos(), insert_);
SchedulePaint();
}
}
@@ -957,7 +1078,7 @@ bool NativeTextfieldViews::Paste() {
// Calls TextfieldController::ContentsChanged() explicitly if the paste action
// did not change the content at all. See http://crbug.com/79002
- if (success && GetText() == textfield_->text()) {
+ if (success && model_->text() == textfield_->text()) {
TextfieldController* controller = textfield_->GetController();
if (controller)
controller->ContentsChanged(textfield_, textfield_->text());
diff --git a/views/controls/textfield/native_textfield_views.h b/views/controls/textfield/native_textfield_views.h
index ea6fec8..4851549 100644
--- a/views/controls/textfield/native_textfield_views.h
+++ b/views/controls/textfield/native_textfield_views.h
@@ -114,8 +114,10 @@ class NativeTextfieldViews : public View,
virtual void HandleFocus() OVERRIDE;
virtual void HandleBlur() OVERRIDE;
virtual TextInputClient* GetTextInputClient() OVERRIDE;
- virtual void ApplyStyleRange(const gfx::StyleRange& style) OVERRIDE;
- virtual void ApplyDefaultStyle() OVERRIDE;
+ virtual TextStyle* CreateTextStyle() OVERRIDE;
+ virtual void ApplyTextStyle(const TextStyle* style,
+ const ui::Range& range) OVERRIDE;
+ virtual void ClearAllTextStyles() OVERRIDE;
virtual void ClearEditHistory() OVERRIDE;
// ui::SimpleMenuModel::Delegate overrides
@@ -162,8 +164,11 @@ class NativeTextfieldViews : public View,
// Overridden from TextfieldViewsModel::Delegate:
virtual void OnCompositionTextConfirmedOrCleared() OVERRIDE;
- // Returns the TextfieldViewsModel's text/cursor/selection rendering model.
- gfx::RenderText* GetRenderText() const;
+ // Returns the Textfield's font.
+ const gfx::Font& GetFont() const;
+
+ // Returns the Textfield's text color.
+ SkColor GetTextColor() const;
// A callback function to periodically update the cursor state.
void UpdateCursor();
@@ -171,6 +176,9 @@ class NativeTextfieldViews : public View,
// Repaint the cursor.
void RepaintCursor();
+ // Returns the bounds of character at the current cursor.
+ gfx::Rect GetCursorBounds(size_t cursor_pos, bool insert_mode) const;
+
// Update the cursor_bounds and text_offset.
void UpdateCursorBoundsAndTextOffset(size_t cursor_pos, bool insert_mode);
@@ -179,6 +187,12 @@ class NativeTextfieldViews : public View,
// Handle the keyevent.
bool HandleKeyEvent(const KeyEvent& key_event);
+ // Find a cusor position for given |point| in this views coordinates.
+ size_t FindCursorPosition(const gfx::Point& point) const;
+
+ // Returns true if the local point is over the selected range of text.
+ bool IsPointInSelection(const gfx::Point& point) const;
+
// Helper function to call MoveCursorTo on the TextfieldViewsModel.
bool MoveCursorTo(const gfx::Point& point, bool select);
@@ -222,8 +236,16 @@ class NativeTextfieldViews : public View,
// The reference to the border class. The object is owned by View::border_.
FocusableBorder* text_border_;
- // The textfield's text and drop cursor visibility.
+ // The x offset for the text to be drawn, without insets;
+ int text_offset_;
+
+ // True if the textfield is in insert mode.
+ bool insert_;
+
+ // The local bounds and visibility of the textfield's text cursor.
+ gfx::Rect cursor_bounds_;
bool is_cursor_visible_;
+
// The drop cursor is a visual cue for where dragged text will be dropped.
bool is_drop_cursor_visible_;
diff --git a/views/controls/textfield/native_textfield_views_unittest.cc b/views/controls/textfield/native_textfield_views_unittest.cc
index 0c8ec2e..31a5e51 100644
--- a/views/controls/textfield/native_textfield_views_unittest.cc
+++ b/views/controls/textfield/native_textfield_views_unittest.cc
@@ -15,7 +15,6 @@
#include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "ui/base/dragdrop/drag_drop_types.h"
#include "ui/base/keycodes/keyboard_codes.h"
-#include "ui/gfx/render_text.h"
#include "views/controls/textfield/native_textfield_views.h"
#include "views/controls/textfield/textfield.h"
#include "views/controls/textfield/textfield_controller.h"
@@ -219,8 +218,9 @@ class NativeTextfieldViewsTest : public ViewsTestBase,
}
int GetCursorPositionX(int cursor_pos) {
- gfx::RenderText* render_text = textfield_view_->GetRenderText();
- return render_text->GetCursorBounds(cursor_pos, false).x();
+ const string16 text = textfield_->text().substr(0, cursor_pos);
+ return textfield_view_->GetInsets().left() + textfield_view_->text_offset_ +
+ textfield_view_->GetFont().GetStringWidth(text);
}
// We need widget to populate wrapper class.
@@ -254,12 +254,12 @@ TEST_F(NativeTextfieldViewsTest, ModelChangesTest) {
last_contents_.clear();
textfield_->SetText(ASCIIToUTF16("this is"));
- EXPECT_STR_EQ("this is", model_->GetText());
+ EXPECT_STR_EQ("this is", model_->text());
EXPECT_STR_EQ("this is", textfield_->text());
EXPECT_TRUE(last_contents_.empty());
textfield_->AppendText(ASCIIToUTF16(" a test"));
- EXPECT_STR_EQ("this is a test", model_->GetText());
+ EXPECT_STR_EQ("this is a test", model_->text());
EXPECT_STR_EQ("this is a test", textfield_->text());
EXPECT_TRUE(last_contents_.empty());
diff --git a/views/controls/textfield/native_textfield_win.cc b/views/controls/textfield/native_textfield_win.cc
index 97e876d6..f53e67b 100644
--- a/views/controls/textfield/native_textfield_win.cc
+++ b/views/controls/textfield/native_textfield_win.cc
@@ -367,11 +367,17 @@ TextInputClient* NativeTextfieldWin::GetTextInputClient() {
return NULL;
}
-void NativeTextfieldWin::ApplyStyleRange(const gfx::StyleRange& style) {
+TextStyle* NativeTextfieldWin::CreateTextStyle() {
+ NOTREACHED();
+ return NULL;
+}
+
+void NativeTextfieldWin::ApplyTextStyle(const TextStyle* style,
+ const ui::Range& range) {
NOTREACHED();
}
-void NativeTextfieldWin::ApplyDefaultStyle() {
+void NativeTextfieldWin::ClearAllTextStyles() {
NOTREACHED();
}
diff --git a/views/controls/textfield/native_textfield_win.h b/views/controls/textfield/native_textfield_win.h
index ceabb34..bb0fc2b 100644
--- a/views/controls/textfield/native_textfield_win.h
+++ b/views/controls/textfield/native_textfield_win.h
@@ -86,8 +86,10 @@ class NativeTextfieldWin
virtual void HandleFocus() OVERRIDE;
virtual void HandleBlur() OVERRIDE;
virtual TextInputClient* GetTextInputClient() OVERRIDE;
- virtual void ApplyStyleRange(const gfx::StyleRange& style) OVERRIDE;
- virtual void ApplyDefaultStyle() OVERRIDE;
+ virtual TextStyle* CreateTextStyle() OVERRIDE;
+ virtual void ApplyTextStyle(const TextStyle* style,
+ const ui::Range& range) OVERRIDE;
+ virtual void ClearAllTextStyles() OVERRIDE;
virtual void ClearEditHistory() OVERRIDE;
// Overridden from ui::SimpleMenuModel::Delegate:
diff --git a/views/controls/textfield/native_textfield_wrapper.h b/views/controls/textfield/native_textfield_wrapper.h
index 6cef09f..8d694eb 100644
--- a/views/controls/textfield/native_textfield_wrapper.h
+++ b/views/controls/textfield/native_textfield_wrapper.h
@@ -11,7 +11,6 @@
namespace gfx {
class Insets;
-struct StyleRange;
} // namespace gfx
namespace ui {
@@ -23,6 +22,7 @@ namespace views {
class KeyEvent;
class Textfield;
class TextInputClient;
+class TextStyle;
class View;
// An interface implemented by an object that provides a platform-native
@@ -126,12 +126,17 @@ class NativeTextfieldWrapper {
// support text input.
virtual TextInputClient* GetTextInputClient() = 0;
- // Applies the |style| to the text specified by its range.
- // See |Textfield::ApplyStyleRange| for detail.
- virtual void ApplyStyleRange(const gfx::StyleRange& style) = 0;
+ // Creates a new TextStyle for this textfield.
+ // See |Textfield::CreateTextStyle| for detail.
+ virtual TextStyle* CreateTextStyle() = 0;
- // Applies the default style to the textfield.
- virtual void ApplyDefaultStyle() = 0;
+ // Applies the |style| to the text specified by the |range|.
+ // See |Textfield::ApplyTextStyle| for detail.
+ virtual void ApplyTextStyle(const TextStyle* style,
+ const ui::Range& range) = 0;
+
+ // Clears all text styles in this textfield.
+ virtual void ClearAllTextStyles() = 0;
// Clears Edit history.
virtual void ClearEditHistory() = 0;
diff --git a/views/controls/textfield/text_style.cc b/views/controls/textfield/text_style.cc
new file mode 100644
index 0000000..18400c6
--- /dev/null
+++ b/views/controls/textfield/text_style.cc
@@ -0,0 +1,54 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/controls/textfield/text_style.h"
+
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/canvas_skia.h"
+#include "ui/gfx/font.h"
+
+namespace {
+// Text color for read only.
+const SkColor kReadonlyTextColor = SK_ColorDKGRAY;
+
+// Strike line width.
+const int kStrikeWidth = 2;
+}
+
+namespace views {
+
+TextStyle::TextStyle()
+ : foreground_(SK_ColorBLACK),
+ strike_(false),
+ underline_(false) {
+}
+
+TextStyle::~TextStyle() {
+}
+
+void TextStyle::DrawString(gfx::Canvas* canvas,
+ string16& text,
+ gfx::Font& base_font,
+ bool readonly,
+ int x, int y, int width, int height) const {
+ SkColor text_color = readonly ? kReadonlyTextColor : foreground_;
+
+ gfx::Font font = underline_ ?
+ base_font.DeriveFont(0, base_font.GetStyle() | gfx::Font::UNDERLINED) :
+ base_font;
+ canvas->DrawStringInt(text, font, text_color, x, y, width, height);
+ if (strike_) {
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setStyle(SkPaint::kFill_Style);
+ paint.setColor(text_color);
+ paint.setStrokeWidth(kStrikeWidth);
+ canvas->AsCanvasSkia()->drawLine(
+ SkIntToScalar(x), SkIntToScalar(y + height),
+ SkIntToScalar(x + width), SkIntToScalar(y),
+ paint);
+ }
+}
+
+} // namespace views
diff --git a/views/controls/textfield/text_style.h b/views/controls/textfield/text_style.h
new file mode 100644
index 0000000..e489aee
--- /dev/null
+++ b/views/controls/textfield/text_style.h
@@ -0,0 +1,65 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_CONTROLS_TEXTFIELD_TEXT_STYLE_H_
+#define VIEWS_CONTROLS_TEXTFIELD_TEXT_STYLE_H_
+#pragma once
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/string16.h"
+#include "third_party/skia/include/core/SkColor.h"
+
+namespace gfx {
+class Canvas;
+class Font;
+}
+
+namespace views {
+
+// A class that specifies text style for TextfieldViews.
+// TODO(suzhe|oshima): support underline color and thick style.
+class TextStyle {
+ public:
+ // Foreground color for the text.
+ void set_foreground(SkColor color) { foreground_ = color; }
+
+ // Draws diagnoal strike acrosss the text.
+ void set_strike(bool strike) { strike_ = strike; }
+
+ // Adds underline to the text.
+ void set_underline(bool underline) { underline_ = underline; }
+
+ private:
+ friend class NativeTextfieldViews;
+ friend class TextfieldViewsModel;
+
+ FRIEND_TEST_ALL_PREFIXES(TextfieldViewsModelTest, TextStyleTest);
+ FRIEND_TEST_ALL_PREFIXES(TextfieldViewsModelTest, UndoRedo_CompositionText);
+ FRIEND_TEST_ALL_PREFIXES(TextfieldViewsModelTest, CompositionTextTest);
+
+ TextStyle();
+ virtual ~TextStyle();
+
+ SkColor foreground() const { return foreground_; }
+ bool underline() const { return underline_; }
+
+ // Draw string to the canvas within the region given
+ // by |x|,|y|,|width|,|height|.
+ void DrawString(gfx::Canvas* canvas,
+ string16& text,
+ gfx::Font& base_font,
+ bool read_only,
+ int x, int y, int width, int height) const;
+
+ SkColor foreground_;
+ bool strike_;
+ bool underline_;
+
+ DISALLOW_COPY_AND_ASSIGN(TextStyle);
+};
+
+} // namespace views
+
+#endif // VIEWS_CONTROLS_TEXTFIELD_TEXT_STYLE_H_
diff --git a/views/controls/textfield/textfield.cc b/views/controls/textfield/textfield.cc
index a51beda..9cd6185 100644
--- a/views/controls/textfield/textfield.cc
+++ b/views/controls/textfield/textfield.cc
@@ -261,14 +261,20 @@ size_t Textfield::GetCursorPosition() const {
return native_wrapper_->GetCursorPosition();
}
-void Textfield::ApplyStyleRange(const gfx::StyleRange& style) {
+TextStyle* Textfield::CreateTextStyle() {
DCHECK(native_wrapper_);
- return native_wrapper_->ApplyStyleRange(style);
+ return native_wrapper_->CreateTextStyle();
}
-void Textfield::ApplyDefaultStyle() {
+void Textfield::ApplyTextStyle(const TextStyle* style,
+ const ui::Range& range) {
DCHECK(native_wrapper_);
- native_wrapper_->ApplyDefaultStyle();
+ return native_wrapper_->ApplyTextStyle(style, range);
+}
+
+void Textfield::ClearAllTextStyles() {
+ DCHECK(native_wrapper_);
+ native_wrapper_->ClearAllTextStyles();
}
void Textfield::ClearEditHistory() {
diff --git a/views/controls/textfield/textfield.h b/views/controls/textfield/textfield.h
index 66675d5..3965050 100644
--- a/views/controls/textfield/textfield.h
+++ b/views/controls/textfield/textfield.h
@@ -30,10 +30,6 @@
#include "views/controls/textfield/native_textfield_wrapper.h"
#endif
-namespace gfx {
-struct StyleRange;
-} // namespace gfx
-
namespace ui {
class Range;
} // namespace ui
@@ -43,6 +39,7 @@ namespace views {
class KeyEvent;
class NativeTextfieldWrapper;
class TextfieldController;
+class TextStyle;
// This class implements a View that wraps a native text (edit) field.
class Textfield : public View {
@@ -189,14 +186,24 @@ class Textfield : public View {
// only and has to be called after the wrapper is created.
size_t GetCursorPosition() const;
- // Applies |style| to the text specified by its range. The style will be
- // ignored if range is empty or invalid. This is views-implementation only and
- // has to be called after the wrapper is created.
- void ApplyStyleRange(const gfx::StyleRange& style);
-
- // Applies the default style to the textfield. This is views-implementation
- // only and has to be called after the wrapper is created.
- void ApplyDefaultStyle();
+ // Creates a new TextStyle for this textfield. The object is owned
+ // by the textfield and gets deleted when the textfield is deleted.
+ // This is views-implementation only and has to be called after the
+ // wrapper is created.
+ TextStyle* CreateTextStyle();
+
+ // Applies the |style| to the text specified by the |range|. If
+ // there is already a style applied in the |range|, the style of the
+ // overlapping part will be replaced by this sytle. The style will
+ // be ignored if range is empty or invalid. This is
+ // views-implementation only and has to be called after the wrapper
+ // is created.
+ void ApplyTextStyle(const TextStyle* style, const ui::Range& range);
+
+ // Clears All TextStyles.
+ // This is views-implementation only and has to be called after the
+ // wrapper is created.
+ void ClearAllTextStyles();
// Clears Edit history.
void ClearEditHistory();
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
diff --git a/views/controls/textfield/textfield_views_model.h b/views/controls/textfield/textfield_views_model.h
index 3f63fe4..297c8ae 100644
--- a/views/controls/textfield/textfield_views_model.h
+++ b/views/controls/textfield/textfield_views_model.h
@@ -10,18 +10,13 @@
#include <vector>
#include "base/gtest_prod_util.h"
-#include "base/memory/scoped_ptr.h"
#include "base/string16.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/base/ime/composition_text.h"
#include "ui/gfx/rect.h"
-#include "ui/gfx/render_text.h"
namespace gfx {
-class Canvas;
class Font;
-class RenderText;
-struct StyleRange;
} // namespace gfx
namespace ui {
@@ -30,10 +25,15 @@ class Range;
namespace views {
+class TextStyle;
+typedef std::vector<TextStyle*> TextStyles;
+
namespace internal {
// Internal Edit class that keeps track of edits for undo/redo.
class Edit;
+struct TextStyleRange;
+
// C++ doesn't allow forward decl enum, so let's define here.
enum MergeType {
// The edit should not be merged with next edit. It still may
@@ -49,6 +49,8 @@ enum MergeType {
} // namespace internal
+typedef std::vector<internal::TextStyleRange*> TextStyleRanges;
+
// A model that represents a text content for TextfieldViews.
// It supports editing, selection and cursor manipulation.
class TextfieldViewsModel {
@@ -68,14 +70,31 @@ class TextfieldViewsModel {
explicit TextfieldViewsModel(Delegate* delegate);
virtual ~TextfieldViewsModel();
+ // Text fragment info. Used to draw selected text.
+ // We may replace this with TextAttribute class
+ // in the future to support multi-color text
+ // for omnibox.
+ struct TextFragment {
+ TextFragment(size_t start, size_t end, const views::TextStyle* s)
+ : range(start, end), style(s) {
+ }
+ // The start and end position of text fragment.
+ ui::Range range;
+ const TextStyle* style;
+ };
+ typedef std::vector<TextFragment> TextFragments;
+
+ // Gets the text element info.
+ void GetFragments(TextFragments* elements);
+
void set_is_password(bool is_password) {
is_password_ = is_password;
}
+ const string16& text() const { return text_; }
// Edit related methods.
- const string16& GetText() const;
- // Sets the text. Returns true if the text has been modified. The
+ // Sest the text. Returns true if the text has been modified. The
// current composition text will be confirmed first. Setting
// the same text will not add edit history because it's not user
// visible change nor user-initiated change. This allow a client
@@ -83,8 +102,6 @@ class TextfieldViewsModel {
// messing edit history.
bool SetText(const string16& text);
- gfx::RenderText* render_text() { return render_text_.get(); }
-
// Inserts given |text| at the current cursor position.
// The current composition text will be cleared.
void InsertText(const string16& text) {
@@ -126,23 +143,46 @@ class TextfieldViewsModel {
// Cursor related methods.
// Returns the current cursor position.
- size_t GetCursorPosition() const;
+ size_t cursor_pos() const { return cursor_pos_; }
+
+ // Moves the cursor left by one position (as if, the user has pressed the left
+ // arrow key). If |select| is true, it updates the selection accordingly.
+ // The current composition text will be confirmed.
+ void MoveCursorLeft(bool select);
- // Moves the cursor, see RenderText for additional details.
+ // Moves the cursor right by one position (as if, the user has pressed the
+ // right arrow key). If |select| is true, it updates the selection
+ // accordingly.
+ // The current composition text will be confirmed.
+ void MoveCursorRight(bool select);
+
+ // Moves the cursor left by one word (word boundry is defined by space).
+ // If |select| is true, it updates the selection accordingly.
+ // The current composition text will be confirmed.
+ void MoveCursorToPreviousWord(bool select);
+
+ // Moves the cursor right by one word (word boundry is defined by space).
+ // If |select| is true, it updates the selection accordingly.
+ // The current composition text will be confirmed.
+ void MoveCursorToNextWord(bool select);
+
+ // Moves the cursor to start of the textfield contents.
+ // If |select| is true, it updates the selection accordingly.
// The current composition text will be confirmed.
- void MoveCursorLeft(gfx::BreakType break_type, bool select);
- void MoveCursorRight(gfx::BreakType break_type, bool select);
+ void MoveCursorToHome(bool select);
+
+ // Moves the cursor to end of the textfield contents.
+ // If |select| is true, it updates the selection accordingly.
+ // The current composition text will be confirmed.
+ void MoveCursorToEnd(bool select);
// Moves the cursor to the specified |position|.
// If |select| is true, it updates the selection accordingly.
// The current composition text will be confirmed.
bool MoveCursorTo(size_t position, bool select);
- // Helper function to call MoveCursorTo on the TextfieldViewsModel.
- bool MoveCursorTo(const gfx::Point& point, bool select);
-
// Returns the bounds of selected text.
- std::vector<gfx::Rect> GetSelectionBounds() const;
+ gfx::Rect GetSelectionBounds(const gfx::Font& font) const;
// Selection related method
@@ -183,7 +223,9 @@ class TextfieldViewsModel {
// Returns visible text. If the field is password, it returns the
// sequence of "*".
- string16 GetVisibleText() const;
+ string16 GetVisibleText() const {
+ return GetVisibleText(0U, text_.length());
+ }
// Cuts the currently selected text and puts it to clipboard. Returns true
// if text has changed after cutting.
@@ -235,10 +277,15 @@ class TextfieldViewsModel {
// Returns true if there is composition text.
bool HasCompositionText() const;
+ TextStyle* CreateTextStyle();
+
+ void ClearAllTextStyles();
+
private:
friend class NativeTextfieldViews;
friend class NativeTextfieldViewsTest;
friend class TextfieldViewsModelTest;
+ friend class TextStyle;
friend class UndoRedo_BasicTest;
friend class UndoRedo_CutCopyPasteTest;
friend class UndoRedo_ReplaceTest;
@@ -247,10 +294,18 @@ class TextfieldViewsModel {
FRIEND_TEST_ALL_PREFIXES(TextfieldViewsModelTest, UndoRedo_BasicTest);
FRIEND_TEST_ALL_PREFIXES(TextfieldViewsModelTest, UndoRedo_CutCopyPasteTest);
FRIEND_TEST_ALL_PREFIXES(TextfieldViewsModelTest, UndoRedo_ReplaceTest);
+ FRIEND_TEST_ALL_PREFIXES(TextfieldViewsModelTest, TextStyleTest);
// Returns the visible text given |start| and |end|.
string16 GetVisibleText(size_t start, size_t end) const;
+ // Utility for SelectWord(). Checks whether position pos is at word boundary.
+ bool IsPositionAtWordSelectionBoundary(size_t pos);
+
+ // Returns the normalized cursor position that does not exceed the
+ // text length.
+ size_t GetSafePosition(size_t position) const;
+
// Insert the given |text|. |mergeable| indicates if this insert
// operation can be merged to previous edit in the edit history.
void InsertTextInternal(const string16& text, bool mergeable);
@@ -294,12 +349,26 @@ class TextfieldViewsModel {
void ClearComposition();
+ void ApplyTextStyle(const TextStyle* style, const ui::Range& range);
+
+ static TextStyle* CreateUnderlineStyle();
+
// Pointer to a TextfieldViewsModel::Delegate instance, should be provided by
// the View object.
Delegate* delegate_;
- // The stylized text, cursor, selection, and the visual layout model.
- scoped_ptr<gfx::RenderText> render_text_;
+ // The text in utf16 format.
+ string16 text_;
+
+ // Current cursor position.
+ size_t cursor_pos_;
+
+ // Selection range.
+ size_t selection_start_;
+
+ // Composition text range.
+ size_t composition_start_;
+ size_t composition_end_;
// True if the text is the password.
bool is_password_;
@@ -320,6 +389,19 @@ class TextfieldViewsModel {
// 3) redone all undone edits.
EditHistory::iterator current_edit_;
+ // This manages all styles objects.
+ TextStyles text_styles_;
+
+ // List of style ranges. Elements in the list never overlap each other.
+ // Elements are not sorted at the time of insertion, and gets sorted
+ // when it's painted (if necessary).
+ TextStyleRanges style_ranges_;
+ // True if the style_ranges_ needs to be sorted.
+ bool sort_style_ranges_;
+
+ // List of style ranges for composition text.
+ TextStyleRanges composition_style_ranges_;
+
DISALLOW_COPY_AND_ASSIGN(TextfieldViewsModel);
};
diff --git a/views/controls/textfield/textfield_views_model_unittest.cc b/views/controls/textfield/textfield_views_model_unittest.cc
index 907e353..7e2f7e6 100644
--- a/views/controls/textfield/textfield_views_model_unittest.cc
+++ b/views/controls/textfield/textfield_views_model_unittest.cc
@@ -10,7 +10,7 @@
#include "ui/base/clipboard/clipboard.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "ui/base/range/range.h"
-#include "ui/gfx/render_text.h"
+#include "views/controls/textfield/text_style.h"
#include "views/controls/textfield/textfield.h"
#include "views/controls/textfield/textfield_views_model.h"
#include "views/test/test_views_delegate.h"
@@ -19,6 +19,8 @@
namespace views {
+#include "views/test/views_test_base.h"
+
class TextfieldViewsModelTest : public ViewsTestBase,
public TextfieldViewsModel::Delegate {
public:
@@ -45,56 +47,57 @@ class TextfieldViewsModelTest : public ViewsTestBase,
#define EXPECT_STR_EQ(ascii, utf16) \
EXPECT_EQ(ASCIIToWide(ascii), UTF16ToWide(utf16))
+
TEST_F(TextfieldViewsModelTest, EditString) {
TextfieldViewsModel model(NULL);
// append two strings
model.Append(ASCIIToUTF16("HILL"));
- EXPECT_STR_EQ("HILL", model.GetText());
+ EXPECT_STR_EQ("HILL", model.text());
model.Append(ASCIIToUTF16("WORLD"));
- EXPECT_STR_EQ("HILLWORLD", model.GetText());
+ EXPECT_STR_EQ("HILLWORLD", model.text());
// Insert "E" to make hello
- model.MoveCursorRight(gfx::CHARACTER_BREAK, false);
+ model.MoveCursorRight(false);
model.InsertChar('E');
- EXPECT_STR_EQ("HEILLWORLD", model.GetText());
+ EXPECT_STR_EQ("HEILLWORLD", model.text());
// Replace "I" with "L"
model.ReplaceChar('L');
- EXPECT_STR_EQ("HELLLWORLD", model.GetText());
+ EXPECT_STR_EQ("HELLLWORLD", model.text());
model.ReplaceChar('L');
model.ReplaceChar('O');
- EXPECT_STR_EQ("HELLOWORLD", model.GetText());
+ EXPECT_STR_EQ("HELLOWORLD", model.text());
// Delete 6th char "W", then delete 5th char O"
- EXPECT_EQ(5U, model.GetCursorPosition());
+ EXPECT_EQ(5U, model.cursor_pos());
EXPECT_TRUE(model.Delete());
- EXPECT_STR_EQ("HELLOORLD", model.GetText());
+ EXPECT_STR_EQ("HELLOORLD", model.text());
EXPECT_TRUE(model.Backspace());
- EXPECT_EQ(4U, model.GetCursorPosition());
- EXPECT_STR_EQ("HELLORLD", model.GetText());
+ EXPECT_EQ(4U, model.cursor_pos());
+ EXPECT_STR_EQ("HELLORLD", model.text());
// Move the cursor to start. backspace should fail.
- model.MoveCursorLeft(gfx::LINE_BREAK, false);
+ model.MoveCursorToHome(false);
EXPECT_FALSE(model.Backspace());
- EXPECT_STR_EQ("HELLORLD", model.GetText());
+ EXPECT_STR_EQ("HELLORLD", model.text());
// Move the cursor to the end. delete should fail.
- model.MoveCursorRight(gfx::LINE_BREAK, false);
+ model.MoveCursorToEnd(false);
EXPECT_FALSE(model.Delete());
- EXPECT_STR_EQ("HELLORLD", model.GetText());
+ EXPECT_STR_EQ("HELLORLD", model.text());
// but backspace should work.
EXPECT_TRUE(model.Backspace());
- EXPECT_STR_EQ("HELLORL", model.GetText());
+ EXPECT_STR_EQ("HELLORL", model.text());
}
TEST_F(TextfieldViewsModelTest, EmptyString) {
TextfieldViewsModel model(NULL);
- EXPECT_EQ(string16(), model.GetText());
+ EXPECT_EQ(string16(), model.text());
EXPECT_EQ(string16(), model.GetSelectedText());
EXPECT_EQ(string16(), model.GetVisibleText());
- model.MoveCursorLeft(gfx::CHARACTER_BREAK, true);
- EXPECT_EQ(0U, model.GetCursorPosition());
- model.MoveCursorRight(gfx::CHARACTER_BREAK, true);
- EXPECT_EQ(0U, model.GetCursorPosition());
+ model.MoveCursorLeft(true);
+ EXPECT_EQ(0U, model.cursor_pos());
+ model.MoveCursorRight(true);
+ EXPECT_EQ(0U, model.cursor_pos());
EXPECT_EQ(string16(), model.GetSelectedText());
@@ -105,15 +108,15 @@ TEST_F(TextfieldViewsModelTest, EmptyString) {
TEST_F(TextfieldViewsModelTest, Selection) {
TextfieldViewsModel model(NULL);
model.Append(ASCIIToUTF16("HELLO"));
- model.MoveCursorRight(gfx::CHARACTER_BREAK, false);
- model.MoveCursorRight(gfx::CHARACTER_BREAK, true);
+ model.MoveCursorRight(false);
+ model.MoveCursorRight(true);
EXPECT_STR_EQ("E", model.GetSelectedText());
- model.MoveCursorRight(gfx::CHARACTER_BREAK, true);
+ model.MoveCursorRight(true);
EXPECT_STR_EQ("EL", model.GetSelectedText());
- model.MoveCursorLeft(gfx::LINE_BREAK, true);
+ model.MoveCursorToHome(true);
EXPECT_STR_EQ("H", model.GetSelectedText());
- model.MoveCursorRight(gfx::LINE_BREAK, true);
+ model.MoveCursorToEnd(true);
EXPECT_STR_EQ("ELLO", model.GetSelectedText());
model.ClearSelection();
EXPECT_EQ(string16(), model.GetSelectedText());
@@ -129,49 +132,49 @@ TEST_F(TextfieldViewsModelTest, Selection) {
model.MoveCursorTo(1U, false);
model.MoveCursorTo(3U, true);
EXPECT_STR_EQ("EL", model.GetSelectedText());
- model.MoveCursorLeft(gfx::CHARACTER_BREAK, false);
- EXPECT_EQ(1U, model.GetCursorPosition());
+ model.MoveCursorLeft(false);
+ EXPECT_EQ(1U, model.cursor_pos());
model.MoveCursorTo(1U, false);
model.MoveCursorTo(3U, true);
- model.MoveCursorRight(gfx::CHARACTER_BREAK, false);
- EXPECT_EQ(3U, model.GetCursorPosition());
+ model.MoveCursorRight(false);
+ EXPECT_EQ(3U, model.cursor_pos());
// Select all and move cursor
model.SelectAll();
- model.MoveCursorLeft(gfx::CHARACTER_BREAK, false);
- EXPECT_EQ(0U, model.GetCursorPosition());
+ model.MoveCursorLeft(false);
+ EXPECT_EQ(0U, model.cursor_pos());
model.SelectAll();
- model.MoveCursorRight(gfx::CHARACTER_BREAK, false);
- EXPECT_EQ(5U, model.GetCursorPosition());
+ model.MoveCursorRight(false);
+ EXPECT_EQ(5U, model.cursor_pos());
}
TEST_F(TextfieldViewsModelTest, SelectionAndEdit) {
TextfieldViewsModel model(NULL);
model.Append(ASCIIToUTF16("HELLO"));
- model.MoveCursorRight(gfx::CHARACTER_BREAK, false);
- model.MoveCursorRight(gfx::CHARACTER_BREAK, true);
- model.MoveCursorRight(gfx::CHARACTER_BREAK, true); // select "EL"
+ model.MoveCursorRight(false);
+ model.MoveCursorRight(true);
+ model.MoveCursorRight(true); // select "EL"
EXPECT_TRUE(model.Backspace());
- EXPECT_STR_EQ("HLO", model.GetText());
+ EXPECT_STR_EQ("HLO", model.text());
model.Append(ASCIIToUTF16("ILL"));
- model.MoveCursorRight(gfx::CHARACTER_BREAK, true);
- model.MoveCursorRight(gfx::CHARACTER_BREAK, true); // select "LO"
+ model.MoveCursorRight(true);
+ model.MoveCursorRight(true); // select "LO"
EXPECT_TRUE(model.Delete());
- EXPECT_STR_EQ("HILL", model.GetText());
- EXPECT_EQ(1U, model.GetCursorPosition());
- model.MoveCursorRight(gfx::CHARACTER_BREAK, true); // select "I"
+ EXPECT_STR_EQ("HILL", model.text());
+ EXPECT_EQ(1U, model.cursor_pos());
+ model.MoveCursorRight(true); // select "I"
model.InsertChar('E');
- EXPECT_STR_EQ("HELL", model.GetText());
- model.MoveCursorLeft(gfx::LINE_BREAK, false);
- model.MoveCursorRight(gfx::CHARACTER_BREAK, true); // select "H"
+ EXPECT_STR_EQ("HELL", model.text());
+ model.MoveCursorToHome(false);
+ model.MoveCursorRight(true); // select "H"
model.ReplaceChar('B');
- EXPECT_STR_EQ("BELL", model.GetText());
- model.MoveCursorRight(gfx::LINE_BREAK, false);
- model.MoveCursorLeft(gfx::CHARACTER_BREAK, true);
- model.MoveCursorLeft(gfx::CHARACTER_BREAK, true); // select "ELL"
+ EXPECT_STR_EQ("BELL", model.text());
+ model.MoveCursorToEnd(false);
+ model.MoveCursorLeft(true);
+ model.MoveCursorLeft(true); // select ">LL"
model.ReplaceChar('E');
- EXPECT_STR_EQ("BEE", model.GetText());
+ EXPECT_STR_EQ("BEE", model.text());
}
TEST_F(TextfieldViewsModelTest, Password) {
@@ -179,88 +182,110 @@ TEST_F(TextfieldViewsModelTest, Password) {
model.set_is_password(true);
model.Append(ASCIIToUTF16("HELLO"));
EXPECT_STR_EQ("*****", model.GetVisibleText());
- EXPECT_STR_EQ("HELLO", model.GetText());
+ EXPECT_STR_EQ("HELLO", model.text());
EXPECT_TRUE(model.Delete());
EXPECT_STR_EQ("****", model.GetVisibleText());
- EXPECT_STR_EQ("ELLO", model.GetText());
- EXPECT_EQ(0U, model.GetCursorPosition());
+ EXPECT_STR_EQ("ELLO", model.text());
+ EXPECT_EQ(0U, model.cursor_pos());
model.SelectAll();
EXPECT_STR_EQ("ELLO", model.GetSelectedText());
- EXPECT_EQ(4U, model.GetCursorPosition());
+ EXPECT_EQ(4U, model.cursor_pos());
model.InsertChar('X');
EXPECT_STR_EQ("*", model.GetVisibleText());
- EXPECT_STR_EQ("X", model.GetText());
+ EXPECT_STR_EQ("X", model.text());
}
TEST_F(TextfieldViewsModelTest, Word) {
TextfieldViewsModel model(NULL);
model.Append(
ASCIIToUTF16("The answer to Life, the Universe, and Everything"));
- model.MoveCursorRight(gfx::WORD_BREAK, false);
- EXPECT_EQ(3U, model.GetCursorPosition());
- model.MoveCursorRight(gfx::WORD_BREAK, false);
- EXPECT_EQ(10U, model.GetCursorPosition());
- model.MoveCursorRight(gfx::WORD_BREAK, false);
- model.MoveCursorRight(gfx::WORD_BREAK, false);
- EXPECT_EQ(18U, model.GetCursorPosition());
+ model.MoveCursorToNextWord(false);
+ EXPECT_EQ(3U, model.cursor_pos());
+ model.MoveCursorToNextWord(false);
+ EXPECT_EQ(10U, model.cursor_pos());
+ model.MoveCursorToNextWord(false);
+ model.MoveCursorToNextWord(false);
+ EXPECT_EQ(18U, model.cursor_pos());
// Should passes the non word char ','
- model.MoveCursorRight(gfx::WORD_BREAK, true);
- EXPECT_EQ(23U, model.GetCursorPosition());
+ model.MoveCursorToNextWord(true);
+ EXPECT_EQ(23U, model.cursor_pos());
EXPECT_STR_EQ(", the", model.GetSelectedText());
// Move to the end.
- model.MoveCursorRight(gfx::WORD_BREAK, true);
- model.MoveCursorRight(gfx::WORD_BREAK, true);
- model.MoveCursorRight(gfx::WORD_BREAK, true);
+ model.MoveCursorToNextWord(true);
+ model.MoveCursorToNextWord(true);
+ model.MoveCursorToNextWord(true);
EXPECT_STR_EQ(", the Universe, and Everything", model.GetSelectedText());
// Should be safe to go next word at the end.
- model.MoveCursorRight(gfx::WORD_BREAK, true);
+ model.MoveCursorToNextWord(true);
EXPECT_STR_EQ(", the Universe, and Everything", model.GetSelectedText());
model.InsertChar('2');
- EXPECT_EQ(19U, model.GetCursorPosition());
+ EXPECT_EQ(19U, model.cursor_pos());
// Now backwards.
- model.MoveCursorLeft(gfx::CHARACTER_BREAK, false); // leave 2.
- model.MoveCursorLeft(gfx::WORD_BREAK, true);
- EXPECT_EQ(14U, model.GetCursorPosition());
+ model.MoveCursorLeft(false); // leave 2.
+ model.MoveCursorToPreviousWord(true);
+ EXPECT_EQ(14U, model.cursor_pos());
EXPECT_STR_EQ("Life", model.GetSelectedText());
- model.MoveCursorLeft(gfx::WORD_BREAK, true);
+ model.MoveCursorToPreviousWord(true);
EXPECT_STR_EQ("to Life", model.GetSelectedText());
- model.MoveCursorLeft(gfx::WORD_BREAK, true);
- model.MoveCursorLeft(gfx::WORD_BREAK, true);
- model.MoveCursorLeft(gfx::WORD_BREAK, true); // Select to the begining.
+ model.MoveCursorToPreviousWord(true);
+ model.MoveCursorToPreviousWord(true);
+ model.MoveCursorToPreviousWord(true); // Select to the begining.
EXPECT_STR_EQ("The answer to Life", model.GetSelectedText());
// Should be safe to go pervious word at the begining.
- model.MoveCursorLeft(gfx::WORD_BREAK, true);
+ model.MoveCursorToPreviousWord(true);
EXPECT_STR_EQ("The answer to Life", model.GetSelectedText());
model.ReplaceChar('4');
EXPECT_EQ(string16(), model.GetSelectedText());
EXPECT_STR_EQ("42", model.GetVisibleText());
}
+TEST_F(TextfieldViewsModelTest, TextFragment) {
+ TextfieldViewsModel model(NULL);
+ TextfieldViewsModel::TextFragments fragments;
+ // Empty string has no fragment.
+ model.GetFragments(&fragments);
+ EXPECT_EQ(0U, fragments.size());
+
+ // Some string
+ model.Append(ASCIIToUTF16("Hello world"));
+ model.GetFragments(&fragments);
+ EXPECT_EQ(1U, fragments.size());
+ EXPECT_EQ(0U, fragments[0].range.start());
+ EXPECT_EQ(11U, fragments[0].range.end());
+
+ // Selection won't change fragment.
+ model.MoveCursorToNextWord(true);
+ model.GetFragments(&fragments);
+ EXPECT_EQ(1U, fragments.size());
+ EXPECT_EQ(0U, fragments[0].range.start());
+ EXPECT_EQ(11U, fragments[0].range.end());
+}
+
TEST_F(TextfieldViewsModelTest, SetText) {
TextfieldViewsModel model(NULL);
model.Append(ASCIIToUTF16("HELLO"));
- model.MoveCursorRight(gfx::LINE_BREAK, false);
+ model.MoveCursorToEnd(false);
model.SetText(ASCIIToUTF16("GOODBYE"));
- EXPECT_STR_EQ("GOODBYE", model.GetText());
+ EXPECT_STR_EQ("GOODBYE", model.text());
// SetText won't reset the cursor posistion.
- EXPECT_EQ(5U, model.GetCursorPosition());
+ EXPECT_EQ(5U, model.cursor_pos());
model.SelectAll();
EXPECT_STR_EQ("GOODBYE", model.GetSelectedText());
- model.MoveCursorRight(gfx::LINE_BREAK, false);
- EXPECT_EQ(7U, model.GetCursorPosition());
+ model.MoveCursorToEnd(false);
+ EXPECT_EQ(7U, model.cursor_pos());
model.SetText(ASCIIToUTF16("BYE"));
// Setting shorter string moves the cursor to the end of the new string.
- EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_EQ(3U, model.cursor_pos());
EXPECT_EQ(string16(), model.GetSelectedText());
model.SetText(ASCIIToUTF16(""));
- EXPECT_EQ(0U, model.GetCursorPosition());
+ EXPECT_EQ(0U, model.cursor_pos());
}
TEST_F(TextfieldViewsModelTest, Clipboard) {
@@ -271,29 +296,29 @@ TEST_F(TextfieldViewsModelTest, Clipboard) {
string16 clipboard_text;
TextfieldViewsModel model(NULL);
model.Append(ASCIIToUTF16("HELLO WORLD"));
- model.MoveCursorRight(gfx::LINE_BREAK, false);
+ model.MoveCursorToEnd(false);
// Test for cut: Empty selection.
EXPECT_FALSE(model.Cut());
clipboard->ReadText(ui::Clipboard::BUFFER_STANDARD, &clipboard_text);
EXPECT_STR_EQ(UTF16ToUTF8(initial_clipboard_text), clipboard_text);
- EXPECT_STR_EQ("HELLO WORLD", model.GetText());
- EXPECT_EQ(11U, model.GetCursorPosition());
+ EXPECT_STR_EQ("HELLO WORLD", model.text());
+ EXPECT_EQ(11U, model.cursor_pos());
// Test for cut: Non-empty selection.
- model.MoveCursorLeft(gfx::WORD_BREAK, true);
+ model.MoveCursorToPreviousWord(true);
EXPECT_TRUE(model.Cut());
clipboard->ReadText(ui::Clipboard::BUFFER_STANDARD, &clipboard_text);
EXPECT_STR_EQ("WORLD", clipboard_text);
- EXPECT_STR_EQ("HELLO ", model.GetText());
- EXPECT_EQ(6U, model.GetCursorPosition());
+ EXPECT_STR_EQ("HELLO ", model.text());
+ EXPECT_EQ(6U, model.cursor_pos());
// Test for copy: Empty selection.
model.Copy();
clipboard->ReadText(ui::Clipboard::BUFFER_STANDARD, &clipboard_text);
EXPECT_STR_EQ("WORLD", clipboard_text);
- EXPECT_STR_EQ("HELLO ", model.GetText());
- EXPECT_EQ(6U, model.GetCursorPosition());
+ EXPECT_STR_EQ("HELLO ", model.text());
+ EXPECT_EQ(6U, model.cursor_pos());
// Test for copy: Non-empty selection.
model.Append(ASCIIToUTF16("HELLO WORLD"));
@@ -301,24 +326,24 @@ TEST_F(TextfieldViewsModelTest, Clipboard) {
model.Copy();
clipboard->ReadText(ui::Clipboard::BUFFER_STANDARD, &clipboard_text);
EXPECT_STR_EQ("HELLO HELLO WORLD", clipboard_text);
- EXPECT_STR_EQ("HELLO HELLO WORLD", model.GetText());
- EXPECT_EQ(17U, model.GetCursorPosition());
+ EXPECT_STR_EQ("HELLO HELLO WORLD", model.text());
+ EXPECT_EQ(17U, model.cursor_pos());
// Test for paste.
model.ClearSelection();
- model.MoveCursorRight(gfx::LINE_BREAK, false);
- model.MoveCursorLeft(gfx::WORD_BREAK, true);
+ model.MoveCursorToEnd(false);
+ model.MoveCursorToPreviousWord(true);
EXPECT_TRUE(model.Paste());
clipboard->ReadText(ui::Clipboard::BUFFER_STANDARD, &clipboard_text);
EXPECT_STR_EQ("HELLO HELLO WORLD", clipboard_text);
- EXPECT_STR_EQ("HELLO HELLO HELLO HELLO WORLD", model.GetText());
- EXPECT_EQ(29U, model.GetCursorPosition());
+ EXPECT_STR_EQ("HELLO HELLO HELLO HELLO WORLD", model.text());
+ EXPECT_EQ(29U, model.cursor_pos());
}
void SelectWordTestVerifier(TextfieldViewsModel &model,
const std::string &expected_selected_string, size_t expected_cursor_pos) {
EXPECT_STR_EQ(expected_selected_string, model.GetSelectedText());
- EXPECT_EQ(expected_cursor_pos, model.GetCursorPosition());
+ EXPECT_EQ(expected_cursor_pos, model.cursor_pos());
}
TEST_F(TextfieldViewsModelTest, SelectWordTest) {
@@ -326,7 +351,7 @@ TEST_F(TextfieldViewsModelTest, SelectWordTest) {
model.Append(ASCIIToUTF16(" HELLO !! WO RLD "));
// Test when cursor is at the beginning.
- model.MoveCursorLeft(gfx::LINE_BREAK, false);
+ model.MoveCursorToHome(false);
model.SelectWord();
SelectWordTestVerifier(model, " ", 2U);
@@ -353,7 +378,7 @@ TEST_F(TextfieldViewsModelTest, SelectWordTest) {
SelectWordTestVerifier(model, " ", 20U);
// Test when cursor is at the end.
- model.MoveCursorRight(gfx::LINE_BREAK, false);
+ model.MoveCursorToEnd(false);
model.SelectWord();
SelectWordTestVerifier(model, " ", 24U);
}
@@ -361,61 +386,61 @@ TEST_F(TextfieldViewsModelTest, SelectWordTest) {
TEST_F(TextfieldViewsModelTest, RangeTest) {
TextfieldViewsModel model(NULL);
model.Append(ASCIIToUTF16("HELLO WORLD"));
- model.MoveCursorLeft(gfx::LINE_BREAK, false);
+ model.MoveCursorToHome(false);
ui::Range range;
model.GetSelectedRange(&range);
EXPECT_TRUE(range.is_empty());
EXPECT_EQ(0U, range.start());
EXPECT_EQ(0U, range.end());
- model.MoveCursorRight(gfx::WORD_BREAK, true);
+ model.MoveCursorToNextWord(true);
model.GetSelectedRange(&range);
EXPECT_FALSE(range.is_empty());
EXPECT_FALSE(range.is_reversed());
EXPECT_EQ(0U, range.start());
EXPECT_EQ(5U, range.end());
- model.MoveCursorLeft(gfx::CHARACTER_BREAK, true);
+ model.MoveCursorLeft(true);
model.GetSelectedRange(&range);
EXPECT_FALSE(range.is_empty());
EXPECT_EQ(0U, range.start());
EXPECT_EQ(4U, range.end());
- model.MoveCursorLeft(gfx::WORD_BREAK, true);
+ model.MoveCursorToPreviousWord(true);
model.GetSelectedRange(&range);
EXPECT_TRUE(range.is_empty());
EXPECT_EQ(0U, range.start());
EXPECT_EQ(0U, range.end());
// now from the end.
- model.MoveCursorRight(gfx::LINE_BREAK, false);
+ model.MoveCursorToEnd(false);
model.GetSelectedRange(&range);
EXPECT_TRUE(range.is_empty());
EXPECT_EQ(11U, range.start());
EXPECT_EQ(11U, range.end());
- model.MoveCursorLeft(gfx::WORD_BREAK, true);
+ model.MoveCursorToPreviousWord(true);
model.GetSelectedRange(&range);
EXPECT_FALSE(range.is_empty());
EXPECT_TRUE(range.is_reversed());
EXPECT_EQ(11U, range.start());
EXPECT_EQ(6U, range.end());
- model.MoveCursorRight(gfx::CHARACTER_BREAK, true);
+ model.MoveCursorRight(true);
model.GetSelectedRange(&range);
EXPECT_FALSE(range.is_empty());
EXPECT_TRUE(range.is_reversed());
EXPECT_EQ(11U, range.start());
EXPECT_EQ(7U, range.end());
- model.MoveCursorRight(gfx::WORD_BREAK, true);
+ model.MoveCursorToNextWord(true);
model.GetSelectedRange(&range);
EXPECT_TRUE(range.is_empty());
EXPECT_EQ(11U, range.start());
EXPECT_EQ(11U, range.end());
// Select All
- model.MoveCursorLeft(gfx::LINE_BREAK, true);
+ model.MoveCursorToHome(true);
model.GetSelectedRange(&range);
EXPECT_FALSE(range.is_empty());
EXPECT_TRUE(range.is_reversed());
@@ -472,7 +497,7 @@ TEST_F(TextfieldViewsModelTest, CompositionTextTest) {
model.Append(ASCIIToUTF16("1234590"));
model.SelectRange(ui::Range(5, 5));
EXPECT_FALSE(model.HasSelection());
- EXPECT_EQ(5U, model.GetCursorPosition());
+ EXPECT_EQ(5U, model.cursor_pos());
ui::Range range;
model.GetTextRange(&range);
@@ -489,7 +514,7 @@ TEST_F(TextfieldViewsModelTest, CompositionTextTest) {
model.GetTextRange(&range);
EXPECT_EQ(10U, range.end());
- EXPECT_STR_EQ("1234567890", model.GetText());
+ EXPECT_STR_EQ("1234567890", model.text());
model.GetCompositionTextRange(&range);
EXPECT_EQ(5U, range.start());
@@ -502,40 +527,55 @@ TEST_F(TextfieldViewsModelTest, CompositionTextTest) {
EXPECT_EQ(8U, range.end());
EXPECT_STR_EQ("8", model.GetSelectedText());
+ TextfieldViewsModel::TextFragments fragments;
+ model.GetFragments(&fragments);
+ EXPECT_EQ(3U, fragments.size());
+ EXPECT_EQ(0U, fragments[0].range.start());
+ EXPECT_EQ(5U, fragments[0].range.end());
+ EXPECT_FALSE(fragments[0].style->underline());
+
+ EXPECT_EQ(5U, fragments[1].range.start());
+ EXPECT_EQ(8U, fragments[1].range.end());
+ EXPECT_TRUE(fragments[1].style->underline());
+
+ EXPECT_EQ(8U, fragments[2].range.start());
+ EXPECT_EQ(10U, fragments[2].range.end());
+ EXPECT_FALSE(fragments[2].style->underline());
+
EXPECT_FALSE(composition_text_confirmed_or_cleared_);
model.CancelCompositionText();
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
EXPECT_FALSE(model.HasCompositionText());
EXPECT_FALSE(model.HasSelection());
- EXPECT_EQ(5U, model.GetCursorPosition());
+ EXPECT_EQ(5U, model.cursor_pos());
model.SetCompositionText(composition);
- EXPECT_STR_EQ("1234567890", model.GetText());
+ EXPECT_STR_EQ("1234567890", model.text());
EXPECT_TRUE(model.SetText(ASCIIToUTF16("1234567890")));
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
- model.MoveCursorRight(gfx::LINE_BREAK, false);
+ model.MoveCursorToEnd(false);
model.SetCompositionText(composition);
- EXPECT_STR_EQ("1234567890678", model.GetText());
+ EXPECT_STR_EQ("1234567890678", model.text());
model.InsertText(UTF8ToUTF16("-"));
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
- EXPECT_STR_EQ("1234567890-", model.GetText());
+ EXPECT_STR_EQ("1234567890-", model.text());
EXPECT_FALSE(model.HasCompositionText());
EXPECT_FALSE(model.HasSelection());
- model.MoveCursorLeft(gfx::CHARACTER_BREAK, true);
+ model.MoveCursorLeft(true);
EXPECT_STR_EQ("-", model.GetSelectedText());
model.SetCompositionText(composition);
- EXPECT_STR_EQ("1234567890678", model.GetText());
+ EXPECT_STR_EQ("1234567890678", model.text());
model.ReplaceText(UTF8ToUTF16("-"));
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
- EXPECT_STR_EQ("1234567890-", model.GetText());
+ EXPECT_STR_EQ("1234567890-", model.text());
EXPECT_FALSE(model.HasCompositionText());
EXPECT_FALSE(model.HasSelection());
@@ -543,82 +583,82 @@ TEST_F(TextfieldViewsModelTest, CompositionTextTest) {
model.Append(UTF8ToUTF16("-"));
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
- EXPECT_STR_EQ("1234567890-678-", model.GetText());
+ EXPECT_STR_EQ("1234567890-678-", model.text());
model.SetCompositionText(composition);
model.Delete();
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
- EXPECT_STR_EQ("1234567890-678-", model.GetText());
+ EXPECT_STR_EQ("1234567890-678-", model.text());
model.SetCompositionText(composition);
model.Backspace();
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
- EXPECT_STR_EQ("1234567890-678-", model.GetText());
+ EXPECT_STR_EQ("1234567890-678-", model.text());
model.SetText(string16());
model.SetCompositionText(composition);
- model.MoveCursorLeft(gfx::CHARACTER_BREAK, false);
+ model.MoveCursorLeft(false);
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
- EXPECT_STR_EQ("678", model.GetText());
- EXPECT_EQ(2U, model.GetCursorPosition());
+ EXPECT_STR_EQ("678", model.text());
+ EXPECT_EQ(2U, model.cursor_pos());
model.SetCompositionText(composition);
- model.MoveCursorRight(gfx::CHARACTER_BREAK, false);
+ model.MoveCursorRight(false);
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
- EXPECT_STR_EQ("676788", model.GetText());
- EXPECT_EQ(6U, model.GetCursorPosition());
+ EXPECT_STR_EQ("676788", model.text());
+ EXPECT_EQ(6U, model.cursor_pos());
model.SetCompositionText(composition);
- model.MoveCursorLeft(gfx::WORD_BREAK, false);
+ model.MoveCursorToPreviousWord(false);
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
- EXPECT_STR_EQ("676788678", model.GetText());
+ EXPECT_STR_EQ("676788678", model.text());
model.SetText(string16());
model.SetCompositionText(composition);
- model.MoveCursorRight(gfx::WORD_BREAK, false);
+ model.MoveCursorToNextWord(false);
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
model.SetCompositionText(composition);
- model.MoveCursorLeft(gfx::LINE_BREAK, true);
+ model.MoveCursorToHome(true);
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
- EXPECT_STR_EQ("678678", model.GetText());
+ EXPECT_STR_EQ("678678", model.text());
model.SetCompositionText(composition);
- model.MoveCursorRight(gfx::LINE_BREAK, false);
+ model.MoveCursorToEnd(false);
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
- EXPECT_STR_EQ("678", model.GetText());
+ EXPECT_STR_EQ("678", model.text());
model.SetCompositionText(composition);
model.MoveCursorTo(0, true);
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
- EXPECT_STR_EQ("678678", model.GetText());
+ EXPECT_STR_EQ("678678", model.text());
model.SetCompositionText(composition);
model.SelectRange(ui::Range(0, 3));
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
- EXPECT_STR_EQ("678", model.GetText());
+ EXPECT_STR_EQ("678", model.text());
model.SetCompositionText(composition);
model.SelectAll();
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
- EXPECT_STR_EQ("678", model.GetText());
+ EXPECT_STR_EQ("678", model.text());
model.SetCompositionText(composition);
model.SelectWord();
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
- EXPECT_STR_EQ("678", model.GetText());
+ EXPECT_STR_EQ("678", model.text());
model.SetCompositionText(composition);
model.ClearSelection();
@@ -635,157 +675,157 @@ TEST_F(TextfieldViewsModelTest, UndoRedo_BasicTest) {
model.InsertChar('a');
EXPECT_FALSE(model.Redo()); // nothing to redo
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("", model.GetText());
+ EXPECT_STR_EQ("", model.text());
EXPECT_TRUE(model.Redo());
- EXPECT_STR_EQ("a", model.GetText());
+ EXPECT_STR_EQ("a", model.text());
// Continuous inserts are treated as one edit.
model.InsertChar('b');
model.InsertChar('c');
- EXPECT_STR_EQ("abc", model.GetText());
+ EXPECT_STR_EQ("abc", model.text());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("a", model.GetText());
- EXPECT_EQ(1U, model.GetCursorPosition());
+ EXPECT_STR_EQ("a", model.text());
+ EXPECT_EQ(1U, model.cursor_pos());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("", model.GetText());
- EXPECT_EQ(0U, model.GetCursorPosition());
+ EXPECT_STR_EQ("", model.text());
+ EXPECT_EQ(0U, model.cursor_pos());
// Undoing further shouldn't change the text.
EXPECT_FALSE(model.Undo());
- EXPECT_STR_EQ("", model.GetText());
+ EXPECT_STR_EQ("", model.text());
EXPECT_FALSE(model.Undo());
- EXPECT_STR_EQ("", model.GetText());
- EXPECT_EQ(0U, model.GetCursorPosition());
+ EXPECT_STR_EQ("", model.text());
+ EXPECT_EQ(0U, model.cursor_pos());
// Redoing to the latest text.
EXPECT_TRUE(model.Redo());
- EXPECT_STR_EQ("a", model.GetText());
- EXPECT_EQ(1U, model.GetCursorPosition());
+ EXPECT_STR_EQ("a", model.text());
+ EXPECT_EQ(1U, model.cursor_pos());
EXPECT_TRUE(model.Redo());
- EXPECT_STR_EQ("abc", model.GetText());
- EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_STR_EQ("abc", model.text());
+ EXPECT_EQ(3U, model.cursor_pos());
// Backspace ===============================
EXPECT_TRUE(model.Backspace());
- EXPECT_STR_EQ("ab", model.GetText());
+ EXPECT_STR_EQ("ab", model.text());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("abc", model.GetText());
- EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_STR_EQ("abc", model.text());
+ EXPECT_EQ(3U, model.cursor_pos());
EXPECT_TRUE(model.Redo());
- EXPECT_STR_EQ("ab", model.GetText());
- EXPECT_EQ(2U, model.GetCursorPosition());
+ EXPECT_STR_EQ("ab", model.text());
+ EXPECT_EQ(2U, model.cursor_pos());
// Continous backspaces are treated as one edit.
EXPECT_TRUE(model.Backspace());
EXPECT_TRUE(model.Backspace());
- EXPECT_STR_EQ("", model.GetText());
+ EXPECT_STR_EQ("", model.text());
// Extra backspace shouldn't affect the history.
EXPECT_FALSE(model.Backspace());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("ab", model.GetText());
- EXPECT_EQ(2U, model.GetCursorPosition());
+ EXPECT_STR_EQ("ab", model.text());
+ EXPECT_EQ(2U, model.cursor_pos());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("abc", model.GetText());
- EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_STR_EQ("abc", model.text());
+ EXPECT_EQ(3U, model.cursor_pos());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("a", model.GetText());
- EXPECT_EQ(1U, model.GetCursorPosition());
+ EXPECT_STR_EQ("a", model.text());
+ EXPECT_EQ(1U, model.cursor_pos());
// Clear history
model.ClearEditHistory();
EXPECT_FALSE(model.Undo());
EXPECT_FALSE(model.Redo());
- EXPECT_STR_EQ("a", model.GetText());
- EXPECT_EQ(1U, model.GetCursorPosition());
+ EXPECT_STR_EQ("a", model.text());
+ EXPECT_EQ(1U, model.cursor_pos());
// Delete ===============================
model.SetText(ASCIIToUTF16("ABCDE"));
model.ClearEditHistory();
model.MoveCursorTo(2, false);
EXPECT_TRUE(model.Delete());
- EXPECT_STR_EQ("ABDE", model.GetText());
- model.MoveCursorLeft(gfx::LINE_BREAK, false);
+ EXPECT_STR_EQ("ABDE", model.text());
+ model.MoveCursorToHome(false);
EXPECT_TRUE(model.Delete());
- EXPECT_STR_EQ("BDE", model.GetText());
+ EXPECT_STR_EQ("BDE", model.text());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("ABDE", model.GetText());
- EXPECT_EQ(0U, model.GetCursorPosition());
+ EXPECT_STR_EQ("ABDE", model.text());
+ EXPECT_EQ(0U, model.cursor_pos());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("ABCDE", model.GetText());
- EXPECT_EQ(2U, model.GetCursorPosition());
+ EXPECT_STR_EQ("ABCDE", model.text());
+ EXPECT_EQ(2U, model.cursor_pos());
EXPECT_TRUE(model.Redo());
- EXPECT_STR_EQ("ABDE", model.GetText());
- EXPECT_EQ(2U, model.GetCursorPosition());
+ EXPECT_STR_EQ("ABDE", model.text());
+ EXPECT_EQ(2U, model.cursor_pos());
// Continous deletes are treated as one edit.
EXPECT_TRUE(model.Delete());
EXPECT_TRUE(model.Delete());
- EXPECT_STR_EQ("AB", model.GetText());
+ EXPECT_STR_EQ("AB", model.text());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("ABDE", model.GetText());
- EXPECT_EQ(2U, model.GetCursorPosition());
+ EXPECT_STR_EQ("ABDE", model.text());
+ EXPECT_EQ(2U, model.cursor_pos());
EXPECT_TRUE(model.Redo());
- EXPECT_STR_EQ("AB", model.GetText());
- EXPECT_EQ(2U, model.GetCursorPosition());
+ EXPECT_STR_EQ("AB", model.text());
+ EXPECT_EQ(2U, model.cursor_pos());
}
TEST_F(TextfieldViewsModelTest, UndoRedo_SetText) {
// This is to test the undo/redo behavior of omnibox.
TextfieldViewsModel model(NULL);
model.InsertChar('w');
- EXPECT_STR_EQ("w", model.GetText());
- EXPECT_EQ(1U, model.GetCursorPosition());
+ EXPECT_STR_EQ("w", model.text());
+ EXPECT_EQ(1U, model.cursor_pos());
model.SetText(ASCIIToUTF16("www.google.com"));
- EXPECT_EQ(1U, model.GetCursorPosition());
- EXPECT_STR_EQ("www.google.com", model.GetText());
+ EXPECT_EQ(1U, model.cursor_pos());
+ EXPECT_STR_EQ("www.google.com", model.text());
model.SelectRange(ui::Range(14, 1));
model.InsertChar('w');
- EXPECT_STR_EQ("ww", model.GetText());
+ EXPECT_STR_EQ("ww", model.text());
model.SetText(ASCIIToUTF16("www.google.com"));
model.SelectRange(ui::Range(14, 2));
model.InsertChar('w');
- EXPECT_STR_EQ("www", model.GetText());
+ EXPECT_STR_EQ("www", model.text());
model.SetText(ASCIIToUTF16("www.google.com"));
model.SelectRange(ui::Range(14, 3));
model.InsertChar('.');
- EXPECT_STR_EQ("www.", model.GetText());
+ EXPECT_STR_EQ("www.", model.text());
model.SetText(ASCIIToUTF16("www.google.com"));
model.SelectRange(ui::Range(14, 4));
model.InsertChar('y');
- EXPECT_STR_EQ("www.y", model.GetText());
+ EXPECT_STR_EQ("www.y", model.text());
model.SetText(ASCIIToUTF16("www.youtube.com"));
- EXPECT_STR_EQ("www.youtube.com", model.GetText());
- EXPECT_EQ(5U, model.GetCursorPosition());
+ EXPECT_STR_EQ("www.youtube.com", model.text());
+ EXPECT_EQ(5U, model.cursor_pos());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("www.google.com", model.GetText());
- EXPECT_EQ(4U, model.GetCursorPosition());
+ EXPECT_STR_EQ("www.google.com", model.text());
+ EXPECT_EQ(4U, model.cursor_pos());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("www.google.com", model.GetText());
- EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_STR_EQ("www.google.com", model.text());
+ EXPECT_EQ(3U, model.cursor_pos());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("www.google.com", model.GetText());
- EXPECT_EQ(2U, model.GetCursorPosition());
+ EXPECT_STR_EQ("www.google.com", model.text());
+ EXPECT_EQ(2U, model.cursor_pos());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("www.google.com", model.GetText());
- EXPECT_EQ(1U, model.GetCursorPosition());
+ EXPECT_STR_EQ("www.google.com", model.text());
+ EXPECT_EQ(1U, model.cursor_pos());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("", model.GetText());
- EXPECT_EQ(0U, model.GetCursorPosition());
+ EXPECT_STR_EQ("", model.text());
+ EXPECT_EQ(0U, model.cursor_pos());
EXPECT_FALSE(model.Undo());
EXPECT_TRUE(model.Redo());
- EXPECT_STR_EQ("www.google.com", model.GetText());
- EXPECT_EQ(1U, model.GetCursorPosition());
+ EXPECT_STR_EQ("www.google.com", model.text());
+ EXPECT_EQ(1U, model.cursor_pos());
EXPECT_TRUE(model.Redo());
- EXPECT_STR_EQ("www.google.com", model.GetText());
- EXPECT_EQ(2U, model.GetCursorPosition());
+ EXPECT_STR_EQ("www.google.com", model.text());
+ EXPECT_EQ(2U, model.cursor_pos());
EXPECT_TRUE(model.Redo());
- EXPECT_STR_EQ("www.google.com", model.GetText());
- EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_STR_EQ("www.google.com", model.text());
+ EXPECT_EQ(3U, model.cursor_pos());
EXPECT_TRUE(model.Redo());
- EXPECT_STR_EQ("www.google.com", model.GetText());
- EXPECT_EQ(4U, model.GetCursorPosition());
+ EXPECT_STR_EQ("www.google.com", model.text());
+ EXPECT_EQ(4U, model.cursor_pos());
EXPECT_TRUE(model.Redo());
- EXPECT_STR_EQ("www.youtube.com", model.GetText());
- EXPECT_EQ(5U, model.GetCursorPosition());
+ EXPECT_STR_EQ("www.youtube.com", model.text());
+ EXPECT_EQ(5U, model.cursor_pos());
EXPECT_FALSE(model.Redo());
}
@@ -797,153 +837,153 @@ TEST_F(TextfieldViewsModelTest, UndoRedo_CutCopyPasteTest) {
model.MoveCursorTo(1, false);
model.MoveCursorTo(3, true);
model.Cut();
- EXPECT_STR_EQ("ADE", model.GetText());
- EXPECT_EQ(1U, model.GetCursorPosition());
+ EXPECT_STR_EQ("ADE", model.text());
+ EXPECT_EQ(1U, model.cursor_pos());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("ABCDE", model.GetText());
- EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_STR_EQ("ABCDE", model.text());
+ EXPECT_EQ(3U, model.cursor_pos());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("", model.GetText());
- EXPECT_EQ(0U, model.GetCursorPosition());
+ EXPECT_STR_EQ("", model.text());
+ EXPECT_EQ(0U, model.cursor_pos());
EXPECT_FALSE(model.Undo()); // no more undo
- EXPECT_STR_EQ("", model.GetText());
+ EXPECT_STR_EQ("", model.text());
EXPECT_TRUE(model.Redo());
- EXPECT_STR_EQ("ABCDE", model.GetText());
- EXPECT_EQ(0U, model.GetCursorPosition());
+ EXPECT_STR_EQ("ABCDE", model.text());
+ EXPECT_EQ(0U, model.cursor_pos());
EXPECT_TRUE(model.Redo());
- EXPECT_STR_EQ("ADE", model.GetText());
- EXPECT_EQ(1U, model.GetCursorPosition());
+ EXPECT_STR_EQ("ADE", model.text());
+ EXPECT_EQ(1U, model.cursor_pos());
EXPECT_FALSE(model.Redo()); // no more redo
- EXPECT_STR_EQ("ADE", model.GetText());
+ EXPECT_STR_EQ("ADE", model.text());
model.Paste();
model.Paste();
model.Paste();
- EXPECT_STR_EQ("ABCBCBCDE", model.GetText());
- EXPECT_EQ(7U, model.GetCursorPosition());
+ EXPECT_STR_EQ("ABCBCBCDE", model.text());
+ EXPECT_EQ(7U, model.cursor_pos());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("ABCBCDE", model.GetText());
- EXPECT_EQ(5U, model.GetCursorPosition());
+ EXPECT_STR_EQ("ABCBCDE", model.text());
+ EXPECT_EQ(5U, model.cursor_pos());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("ABCDE", model.GetText());
- EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_STR_EQ("ABCDE", model.text());
+ EXPECT_EQ(3U, model.cursor_pos());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("ADE", model.GetText());
- EXPECT_EQ(1U, model.GetCursorPosition());
+ EXPECT_STR_EQ("ADE", model.text());
+ EXPECT_EQ(1U, model.cursor_pos());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("ABCDE", model.GetText());
- EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_STR_EQ("ABCDE", model.text());
+ EXPECT_EQ(3U, model.cursor_pos());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("", model.GetText());
- EXPECT_EQ(0U, model.GetCursorPosition());
+ EXPECT_STR_EQ("", model.text());
+ EXPECT_EQ(0U, model.cursor_pos());
EXPECT_FALSE(model.Undo());
- EXPECT_STR_EQ("", model.GetText());
+ EXPECT_STR_EQ("", model.text());
EXPECT_TRUE(model.Redo());
- EXPECT_STR_EQ("ABCDE", model.GetText()); // Redoing SetText
- EXPECT_EQ(0U, model.GetCursorPosition());
+ EXPECT_STR_EQ("ABCDE", model.text()); // Redoing SetText
+ EXPECT_EQ(0U, model.cursor_pos());
// Redo
EXPECT_TRUE(model.Redo());
- EXPECT_STR_EQ("ADE", model.GetText());
- EXPECT_EQ(1U, model.GetCursorPosition());
+ EXPECT_STR_EQ("ADE", model.text());
+ EXPECT_EQ(1U, model.cursor_pos());
EXPECT_TRUE(model.Redo());
- EXPECT_STR_EQ("ABCDE", model.GetText());
- EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_STR_EQ("ABCDE", model.text());
+ EXPECT_EQ(3U, model.cursor_pos());
EXPECT_TRUE(model.Redo());
- EXPECT_STR_EQ("ABCBCDE", model.GetText());
- EXPECT_EQ(5U, model.GetCursorPosition());
+ EXPECT_STR_EQ("ABCBCDE", model.text());
+ EXPECT_EQ(5U, model.cursor_pos());
EXPECT_TRUE(model.Redo());
- EXPECT_STR_EQ("ABCBCBCDE", model.GetText());
- EXPECT_EQ(7U, model.GetCursorPosition());
+ EXPECT_STR_EQ("ABCBCBCDE", model.text());
+ EXPECT_EQ(7U, model.cursor_pos());
EXPECT_FALSE(model.Redo());
// with SelectRange
model.SelectRange(ui::Range(1, 3));
EXPECT_TRUE(model.Cut());
- EXPECT_STR_EQ("ABCBCDE", model.GetText());
- EXPECT_EQ(1U, model.GetCursorPosition());
+ EXPECT_STR_EQ("ABCBCDE", model.text());
+ EXPECT_EQ(1U, model.cursor_pos());
model.SelectRange(ui::Range(1, 1));
EXPECT_FALSE(model.Cut());
- model.MoveCursorRight(gfx::LINE_BREAK, false);
+ model.MoveCursorToEnd(false);
EXPECT_TRUE(model.Paste());
- EXPECT_STR_EQ("ABCBCDEBC", model.GetText());
- EXPECT_EQ(9U, model.GetCursorPosition());
+ EXPECT_STR_EQ("ABCBCDEBC", model.text());
+ EXPECT_EQ(9U, model.cursor_pos());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("ABCBCDE", model.GetText());
- EXPECT_EQ(7U, model.GetCursorPosition());
+ EXPECT_STR_EQ("ABCBCDE", model.text());
+ EXPECT_EQ(7U, model.cursor_pos());
// empty cut shouldn't create an edit.
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("ABCBCBCDE", model.GetText());
- EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_STR_EQ("ABCBCBCDE", model.text());
+ EXPECT_EQ(3U, model.cursor_pos());
// Copy
ResetModel(&model);
model.SetText(ASCIIToUTF16("12345"));
- EXPECT_STR_EQ("12345", model.GetText());
- EXPECT_EQ(0U, model.GetCursorPosition());
+ EXPECT_STR_EQ("12345", model.text());
+ EXPECT_EQ(0U, model.cursor_pos());
model.MoveCursorTo(1, false);
model.MoveCursorTo(3, true);
model.Copy(); // Copy "23"
- EXPECT_STR_EQ("12345", model.GetText());
- EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_STR_EQ("12345", model.text());
+ EXPECT_EQ(3U, model.cursor_pos());
model.Paste(); // Paste "23" into "23".
- EXPECT_STR_EQ("12345", model.GetText());
- EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_STR_EQ("12345", model.text());
+ EXPECT_EQ(3U, model.cursor_pos());
model.Paste();
- EXPECT_STR_EQ("1232345", model.GetText());
- EXPECT_EQ(5U, model.GetCursorPosition());
+ EXPECT_STR_EQ("1232345", model.text());
+ EXPECT_EQ(5U, model.cursor_pos());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("12345", model.GetText());
- EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_STR_EQ("12345", model.text());
+ EXPECT_EQ(3U, model.cursor_pos());
// TODO(oshima): We need to change the return type from bool to enum.
EXPECT_FALSE(model.Undo()); // No text change.
- EXPECT_STR_EQ("12345", model.GetText());
- EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_STR_EQ("12345", model.text());
+ EXPECT_EQ(3U, model.cursor_pos());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("", model.GetText());
+ EXPECT_STR_EQ("", model.text());
EXPECT_FALSE(model.Undo());
// Redo
EXPECT_TRUE(model.Redo());
- EXPECT_STR_EQ("12345", model.GetText());
- EXPECT_EQ(0U, model.GetCursorPosition());
+ EXPECT_STR_EQ("12345", model.text());
+ EXPECT_EQ(0U, model.cursor_pos());
EXPECT_TRUE(model.Redo());
- EXPECT_STR_EQ("12345", model.GetText()); // For 1st paste
- EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_STR_EQ("12345", model.text()); // For 1st paste
+ EXPECT_EQ(3U, model.cursor_pos());
EXPECT_TRUE(model.Redo());
- EXPECT_STR_EQ("1232345", model.GetText());
- EXPECT_EQ(5U, model.GetCursorPosition());
+ EXPECT_STR_EQ("1232345", model.text());
+ EXPECT_EQ(5U, model.cursor_pos());
EXPECT_FALSE(model.Redo());
- EXPECT_STR_EQ("1232345", model.GetText());
+ EXPECT_STR_EQ("1232345", model.text());
- // Test using SelectRange
+ // with SelectRange
model.SelectRange(ui::Range(1, 3));
model.Copy();
- EXPECT_STR_EQ("1232345", model.GetText());
- model.MoveCursorRight(gfx::LINE_BREAK, false);
+ EXPECT_STR_EQ("1232345", model.text());
+ model.MoveCursorToEnd(false);
EXPECT_TRUE(model.Paste());
- EXPECT_STR_EQ("123234523", model.GetText());
- EXPECT_EQ(9U, model.GetCursorPosition());
+ EXPECT_STR_EQ("123234523", model.text());
+ EXPECT_EQ(9U, model.cursor_pos());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("1232345", model.GetText());
- EXPECT_EQ(7U, model.GetCursorPosition());
+ EXPECT_STR_EQ("1232345", model.text());
+ EXPECT_EQ(7U, model.cursor_pos());
}
TEST_F(TextfieldViewsModelTest, UndoRedo_CursorTest) {
TextfieldViewsModel model(NULL);
model.InsertChar('a');
- model.MoveCursorLeft(gfx::CHARACTER_BREAK, false);
- model.MoveCursorRight(gfx::CHARACTER_BREAK, false);
+ model.MoveCursorLeft(false);
+ model.MoveCursorRight(false);
model.InsertChar('b');
- // Moving the cursor shouldn't create a new edit.
- EXPECT_STR_EQ("ab", model.GetText());
+ // Moving cursor shoudln't create a new edit.
+ EXPECT_STR_EQ("ab", model.text());
EXPECT_FALSE(model.Redo());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("", model.GetText());
+ EXPECT_STR_EQ("", model.text());
EXPECT_FALSE(model.Undo());
- EXPECT_STR_EQ("", model.GetText());
+ EXPECT_STR_EQ("", model.text());
EXPECT_TRUE(model.Redo());
- EXPECT_STR_EQ("ab", model.GetText());
- EXPECT_EQ(2U, model.GetCursorPosition());
+ EXPECT_STR_EQ("ab", model.text());
+ EXPECT_EQ(2U, model.cursor_pos());
EXPECT_FALSE(model.Redo());
}
@@ -955,21 +995,21 @@ void RunInsertReplaceTest(TextfieldViewsModel& model) {
model.InsertChar('1');
model.InsertChar('2');
model.InsertChar('3');
- EXPECT_STR_EQ("a123d", model.GetText());
- EXPECT_EQ(4U, model.GetCursorPosition());
+ EXPECT_STR_EQ("a123d", model.text());
+ EXPECT_EQ(4U, model.cursor_pos());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("abcd", model.GetText());
- EXPECT_EQ(reverse ? 1U : 3U, model.GetCursorPosition());
+ EXPECT_STR_EQ("abcd", model.text());
+ EXPECT_EQ(reverse ? 1U : 3U, model.cursor_pos());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("", model.GetText());
- EXPECT_EQ(0U, model.GetCursorPosition());
+ EXPECT_STR_EQ("", model.text());
+ EXPECT_EQ(0U, model.cursor_pos());
EXPECT_FALSE(model.Undo());
EXPECT_TRUE(model.Redo());
- EXPECT_STR_EQ("abcd", model.GetText());
- EXPECT_EQ(0U, model.GetCursorPosition()); // By SetText
+ EXPECT_STR_EQ("abcd", model.text());
+ EXPECT_EQ(0U, model.cursor_pos()); // By SetText
EXPECT_TRUE(model.Redo());
- EXPECT_STR_EQ("a123d", model.GetText());
- EXPECT_EQ(4U, model.GetCursorPosition());
+ EXPECT_STR_EQ("a123d", model.text());
+ EXPECT_EQ(4U, model.cursor_pos());
EXPECT_FALSE(model.Redo());
}
@@ -982,21 +1022,21 @@ void RunOverwriteReplaceTest(TextfieldViewsModel& model) {
model.ReplaceChar('2');
model.ReplaceChar('3');
model.ReplaceChar('4');
- EXPECT_STR_EQ("a1234", model.GetText());
- EXPECT_EQ(5U, model.GetCursorPosition());
+ EXPECT_STR_EQ("a1234", model.text());
+ EXPECT_EQ(5U, model.cursor_pos());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("abcd", model.GetText());
- EXPECT_EQ(reverse ? 1U : 3U, model.GetCursorPosition());
+ EXPECT_STR_EQ("abcd", model.text());
+ EXPECT_EQ(reverse ? 1U : 3U, model.cursor_pos());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("", model.GetText());
- EXPECT_EQ(0U, model.GetCursorPosition());
+ EXPECT_STR_EQ("", model.text());
+ EXPECT_EQ(0U, model.cursor_pos());
EXPECT_FALSE(model.Undo());
EXPECT_TRUE(model.Redo());
- EXPECT_STR_EQ("abcd", model.GetText());
- EXPECT_EQ(0U, model.GetCursorPosition());
+ EXPECT_STR_EQ("abcd", model.text());
+ EXPECT_EQ(0U, model.cursor_pos());
EXPECT_TRUE(model.Redo());
- EXPECT_STR_EQ("a1234", model.GetText());
- EXPECT_EQ(5U, model.GetCursorPosition());
+ EXPECT_STR_EQ("a1234", model.text());
+ EXPECT_EQ(5U, model.cursor_pos());
EXPECT_FALSE(model.Redo());
}
@@ -1074,72 +1114,282 @@ TEST_F(TextfieldViewsModelTest, UndoRedo_CompositionText) {
composition.selection = ui::Range(2, 3);
model.SetText(ASCIIToUTF16("ABCDE"));
- model.MoveCursorRight(gfx::LINE_BREAK, false);
+ model.MoveCursorToEnd(false);
model.InsertChar('x');
- EXPECT_STR_EQ("ABCDEx", model.GetText());
+ EXPECT_STR_EQ("ABCDEx", model.text());
EXPECT_TRUE(model.Undo()); // set composition should forget undone edit.
model.SetCompositionText(composition);
EXPECT_TRUE(model.HasCompositionText());
EXPECT_TRUE(model.HasSelection());
- EXPECT_STR_EQ("ABCDEabc", model.GetText());
+ EXPECT_STR_EQ("ABCDEabc", model.text());
// Accepting composition
model.ConfirmCompositionText();
- EXPECT_STR_EQ("ABCDEabc", model.GetText());
+ EXPECT_STR_EQ("ABCDEabc", model.text());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("ABCDE", model.GetText());
+ EXPECT_STR_EQ("ABCDE", model.text());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("", model.GetText());
+ EXPECT_STR_EQ("", model.text());
EXPECT_TRUE(model.Redo());
- EXPECT_STR_EQ("ABCDE", model.GetText());
+ EXPECT_STR_EQ("ABCDE", model.text());
EXPECT_TRUE(model.Redo());
- EXPECT_STR_EQ("ABCDEabc", model.GetText());
+ EXPECT_STR_EQ("ABCDEabc", model.text());
EXPECT_FALSE(model.Redo());
// Canceling composition
- model.MoveCursorLeft(gfx::LINE_BREAK, false);
+ model.MoveCursorToHome(false);
model.SetCompositionText(composition);
- EXPECT_STR_EQ("abcABCDEabc", model.GetText());
+ EXPECT_STR_EQ("abcABCDEabc", model.text());
model.CancelCompositionText();
- EXPECT_STR_EQ("ABCDEabc", model.GetText());
+ EXPECT_STR_EQ("ABCDEabc", model.text());
EXPECT_FALSE(model.Redo());
- EXPECT_STR_EQ("ABCDEabc", model.GetText());
+ EXPECT_STR_EQ("ABCDEabc", model.text());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("ABCDE", model.GetText());
+ EXPECT_STR_EQ("ABCDE", model.text());
EXPECT_TRUE(model.Redo());
- EXPECT_STR_EQ("ABCDEabc", model.GetText());
+ EXPECT_STR_EQ("ABCDEabc", model.text());
EXPECT_FALSE(model.Redo());
// SetText with the same text as the result.
ResetModel(&model);
model.SetText(ASCIIToUTF16("ABCDE"));
- model.MoveCursorRight(gfx::LINE_BREAK, false);
+ model.MoveCursorToEnd(false);
model.SetCompositionText(composition);
- EXPECT_STR_EQ("ABCDEabc", model.GetText());
+ EXPECT_STR_EQ("ABCDEabc", model.text());
model.SetText(ASCIIToUTF16("ABCDEabc"));
- EXPECT_STR_EQ("ABCDEabc", model.GetText());
+ EXPECT_STR_EQ("ABCDEabc", model.text());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("ABCDE", model.GetText());
+ EXPECT_STR_EQ("ABCDE", model.text());
EXPECT_TRUE(model.Redo());
- EXPECT_STR_EQ("ABCDEabc", model.GetText());
+ EXPECT_STR_EQ("ABCDEabc", model.text());
EXPECT_FALSE(model.Redo());
// SetText with the different text than the result should not
// remember composition text.
ResetModel(&model);
model.SetText(ASCIIToUTF16("ABCDE"));
- model.MoveCursorRight(gfx::LINE_BREAK, false);
+ model.MoveCursorToEnd(false);
model.SetCompositionText(composition);
- EXPECT_STR_EQ("ABCDEabc", model.GetText());
+ EXPECT_STR_EQ("ABCDEabc", model.text());
model.SetText(ASCIIToUTF16("1234"));
- EXPECT_STR_EQ("1234", model.GetText());
+ EXPECT_STR_EQ("1234", model.text());
EXPECT_TRUE(model.Undo());
- EXPECT_STR_EQ("ABCDE", model.GetText());
+ EXPECT_STR_EQ("ABCDE", model.text());
EXPECT_TRUE(model.Redo());
- EXPECT_STR_EQ("1234", model.GetText());
+ EXPECT_STR_EQ("1234", model.text());
EXPECT_FALSE(model.Redo());
// TODO(oshima): We need MockInputMethod to test the behavior with IME.
}
+TEST_F(TextfieldViewsModelTest, TextStyleTest) {
+ const SkColor black = 0xFF000000; // black is default text color.
+ const SkColor white = 0xFFFFFFFF;
+ TextfieldViewsModel model(NULL);
+ TextStyle* color = model.CreateTextStyle();
+ color->set_foreground(white);
+ TextStyle* underline = model.CreateTextStyle();
+ underline->set_underline(true);
+ underline->set_foreground(white);
+ TextStyle* strike = model.CreateTextStyle();
+ strike->set_strike(true);
+ strike->set_foreground(white);
+
+ // Case 1: No overlaps
+ model.ApplyTextStyle(color, ui::Range(1, 3));
+ model.ApplyTextStyle(underline, ui::Range(5, 6));
+
+ TextfieldViewsModel::TextFragments fragments;
+ model.GetFragments(&fragments);
+ // Styles with empty string simply returns an empty fragments.
+ EXPECT_EQ(0U, fragments.size());
+
+ // 1st style only.
+ model.SetText(ASCIIToUTF16("01234")); // SetText doesn't change styles.
+ model.GetFragments(&fragments);
+ EXPECT_EQ(3U, fragments.size());
+ EXPECT_EQ(0U, fragments[0].range.start());
+ EXPECT_EQ(1U, fragments[0].range.end());
+ EXPECT_EQ(black, fragments[0].style->foreground());
+
+ EXPECT_EQ(1U, fragments[1].range.start());
+ EXPECT_EQ(3U, fragments[1].range.end());
+ EXPECT_EQ(color, fragments[1].style);
+
+ EXPECT_EQ(3U, fragments[2].range.start());
+ EXPECT_EQ(5U, fragments[2].range.end());
+ EXPECT_EQ(black, fragments[2].style->foreground());
+
+ // Clear styles
+ model.ClearAllTextStyles();
+ model.GetFragments(&fragments);
+ EXPECT_EQ(1U, fragments.size());
+ EXPECT_EQ(0U, fragments[0].range.start());
+ EXPECT_EQ(5U, fragments[0].range.end());
+ EXPECT_EQ(black, fragments[0].style->foreground());
+
+ // Case 2: Overlaps on left and on right
+ model.ApplyTextStyle(color, ui::Range(1, 3));
+ model.ApplyTextStyle(strike, ui::Range(6, 8));
+ model.ApplyTextStyle(underline, ui::Range(2, 7));
+
+ // With short string
+ model.SetText(ASCIIToUTF16("0"));
+ model.GetFragments(&fragments);
+ EXPECT_EQ(1U, fragments.size());
+ EXPECT_EQ(0U, fragments[0].range.start());
+ EXPECT_EQ(1U, fragments[0].range.end());
+ EXPECT_EQ(black, fragments[0].style->foreground());
+
+ // With mid-length string
+ model.SetText(ASCIIToUTF16("0123"));
+ model.GetFragments(&fragments);
+ EXPECT_EQ(3U, fragments.size());
+ EXPECT_EQ(0U, fragments[0].range.start());
+ EXPECT_EQ(1U, fragments[0].range.end());
+ EXPECT_EQ(black, fragments[0].style->foreground());
+
+ EXPECT_EQ(1U, fragments[1].range.start());
+ EXPECT_EQ(2U, fragments[1].range.end());
+ EXPECT_EQ(color, fragments[1].style);
+
+ EXPECT_EQ(2U, fragments[2].range.start());
+ EXPECT_EQ(4U, fragments[2].range.end());
+ EXPECT_EQ(underline, fragments[2].style);
+
+ // With long (longer than styles) string
+ model.SetText(ASCIIToUTF16("0123456789"));
+ model.GetFragments(&fragments);
+ EXPECT_EQ(5U, fragments.size());
+ EXPECT_EQ(0U, fragments[0].range.start());
+ EXPECT_EQ(1U, fragments[0].range.end());
+ EXPECT_EQ(black, fragments[0].style->foreground());
+
+ EXPECT_EQ(1U, fragments[1].range.start());
+ EXPECT_EQ(2U, fragments[1].range.end());
+ EXPECT_EQ(color, fragments[1].style);
+
+ EXPECT_EQ(2U, fragments[2].range.start());
+ EXPECT_EQ(7U, fragments[2].range.end());
+ EXPECT_EQ(underline, fragments[2].style);
+
+ EXPECT_EQ(7U, fragments[3].range.start());
+ EXPECT_EQ(8U, fragments[3].range.end());
+ EXPECT_EQ(strike, fragments[3].style);
+
+ EXPECT_EQ(8U, fragments[4].range.start());
+ EXPECT_EQ(10U, fragments[4].range.end());
+ EXPECT_EQ(black, fragments[4].style->foreground());
+
+ model.ClearAllTextStyles();
+
+ // Case 3: The underline style splits the color style underneath.
+ model.ApplyTextStyle(color, ui::Range(0, 15));
+ model.ApplyTextStyle(underline, ui::Range(5, 6));
+ model.GetFragments(&fragments);
+ EXPECT_EQ(3U, fragments.size());
+ EXPECT_EQ(0U, fragments[0].range.start());
+ EXPECT_EQ(5U, fragments[0].range.end());
+ EXPECT_EQ(color, fragments[0].style);
+
+ EXPECT_EQ(5U, fragments[1].range.start());
+ EXPECT_EQ(6U, fragments[1].range.end());
+ EXPECT_EQ(underline, fragments[1].style);
+
+ EXPECT_EQ(6U, fragments[2].range.start());
+ EXPECT_EQ(10U, fragments[2].range.end());
+ EXPECT_EQ(color, fragments[2].style);
+
+ model.ClearAllTextStyles();
+
+ // Case 4: The underline style moves the color style underneath.
+ model.ApplyTextStyle(color, ui::Range(0, 15));
+ model.ApplyTextStyle(underline, ui::Range(0, 6));
+ model.GetFragments(&fragments);
+ EXPECT_EQ(2U, fragments.size());
+ EXPECT_EQ(0U, fragments[0].range.start());
+ EXPECT_EQ(6U, fragments[0].range.end());
+ EXPECT_EQ(underline, fragments[0].style);
+
+ EXPECT_EQ(6U, fragments[1].range.start());
+ EXPECT_EQ(10U, fragments[1].range.end());
+ EXPECT_EQ(color, fragments[1].style);
+
+ model.ClearAllTextStyles();
+
+ model.ApplyTextStyle(color, ui::Range(0, 10));
+ model.ApplyTextStyle(underline, ui::Range(6, 10));
+ model.GetFragments(&fragments);
+ EXPECT_EQ(2U, fragments.size());
+ EXPECT_EQ(0U, fragments[0].range.start());
+ EXPECT_EQ(6U, fragments[0].range.end());
+ EXPECT_EQ(color, fragments[0].style);
+
+ EXPECT_EQ(6U, fragments[1].range.start());
+ EXPECT_EQ(10U, fragments[1].range.end());
+ EXPECT_EQ(underline, fragments[1].style);
+
+ model.ClearAllTextStyles();
+ // Case 5: The strike style hides the unerline style underneath.
+ model.ApplyTextStyle(color, ui::Range(0, 15));
+ model.ApplyTextStyle(underline, ui::Range(0, 6));
+ model.ApplyTextStyle(strike, ui::Range(4, 7));
+ model.GetFragments(&fragments);
+ EXPECT_EQ(3U, fragments.size());
+ EXPECT_EQ(0U, fragments[0].range.start());
+ EXPECT_EQ(4U, fragments[0].range.end());
+ EXPECT_EQ(underline, fragments[0].style);
+
+ EXPECT_EQ(4U, fragments[1].range.start());
+ EXPECT_EQ(7U, fragments[1].range.end());
+ EXPECT_EQ(strike, fragments[1].style);
+
+ EXPECT_EQ(7U, fragments[2].range.start());
+ EXPECT_EQ(10U, fragments[2].range.end());
+ EXPECT_EQ(color, fragments[2].style);
+
+ // Case 6: Reversed range.
+ model.ClearAllTextStyles();
+ model.ApplyTextStyle(color, ui::Range(3, 1));
+ model.ApplyTextStyle(underline, ui::Range(6, 4));
+ model.ApplyTextStyle(strike, ui::Range(5, 2));
+ model.GetFragments(&fragments);
+ EXPECT_EQ(5U, fragments.size());
+
+ EXPECT_EQ(0U, fragments[0].range.start());
+ EXPECT_EQ(1U, fragments[0].range.end());
+ EXPECT_EQ(black, fragments[0].style->foreground());
+
+ EXPECT_EQ(1U, fragments[1].range.start());
+ EXPECT_EQ(2U, fragments[1].range.end());
+ EXPECT_EQ(color, fragments[1].style);
+
+ EXPECT_EQ(2U, fragments[2].range.start());
+ EXPECT_EQ(5U, fragments[2].range.end());
+ EXPECT_EQ(strike, fragments[2].style);
+
+ EXPECT_EQ(5U, fragments[3].range.start());
+ EXPECT_EQ(6U, fragments[3].range.end());
+ EXPECT_EQ(underline, fragments[3].style);
+
+ EXPECT_EQ(6U, fragments[4].range.start());
+ EXPECT_EQ(10U, fragments[4].range.end());
+ EXPECT_EQ(black, fragments[4].style->foreground());
+
+ // Case 7: empty / invald range
+ model.ClearAllTextStyles();
+ model.ApplyTextStyle(color, ui::Range(0, 0));
+ model.ApplyTextStyle(underline, ui::Range(4, 4));
+ ui::Range invalid = ui::Range(0, 2).Intersect(ui::Range(3, 4));
+ ASSERT_FALSE(invalid.IsValid());
+
+ model.ApplyTextStyle(strike, invalid);
+ model.GetFragments(&fragments);
+ EXPECT_EQ(1U, fragments.size());
+
+ EXPECT_EQ(0U, fragments[0].range.start());
+ EXPECT_EQ(10U, fragments[0].range.end());
+ EXPECT_EQ(black, fragments[0].style->foreground());
+}
+
} // namespace views
diff --git a/views/examples/textfield_example.cc b/views/examples/textfield_example.cc
index bb1ab24..f3553fe 100644
--- a/views/examples/textfield_example.cc
+++ b/views/examples/textfield_example.cc
@@ -6,8 +6,8 @@
#include "base/utf_string_conversions.h"
#include "ui/base/range/range.h"
-#include "ui/gfx/render_text.h"
#include "views/controls/label.h"
+#include "views/controls/textfield/text_style.h"
#include "views/controls/textfield/textfield.h"
#include "views/layout/grid_layout.h"
#include "views/view.h"
@@ -15,7 +15,10 @@
namespace examples {
TextfieldExample::TextfieldExample(ExamplesMain* main)
- : ExampleBase(main) {
+ : ExampleBase(main),
+ underline_(NULL),
+ strike_(NULL),
+ color_(NULL) {
}
TextfieldExample::~TextfieldExample() {
@@ -90,22 +93,19 @@ void TextfieldExample::ButtonPressed(views::Button* sender,
} else if (sender == set_) {
name_->SetText(WideToUTF16(L"[set]"));
} else if (sender == set_style_) {
- gfx::StyleRange color;
- color.foreground = SK_ColorYELLOW;
- color.range = ui::Range(0, 11);
- name_->ApplyStyleRange(color);
-
- gfx::StyleRange underline;
- underline.underline = true;
- underline.foreground = SK_ColorBLUE;
- underline.range = ui::Range(1, 7);
- name_->ApplyStyleRange(underline);
-
- gfx::StyleRange strike;
- strike.strike = true;
- strike.foreground = SK_ColorRED;
- strike.range = ui::Range(6, 9);
- name_->ApplyStyleRange(strike);
+ if (!underline_) {
+ color_ = name_->CreateTextStyle();
+ color_->set_foreground(SK_ColorYELLOW);
+ underline_ = name_->CreateTextStyle();
+ underline_->set_underline(true);
+ underline_->set_foreground(SK_ColorBLUE);
+ strike_ = name_->CreateTextStyle();
+ strike_->set_strike(true);
+ strike_->set_foreground(SK_ColorRED);
+ name_->ApplyTextStyle(color_, ui::Range(0, 11));
+ name_->ApplyTextStyle(underline_, ui::Range(1, 7));
+ name_->ApplyTextStyle(strike_, ui::Range(6, 9));
+ }
}
}
diff --git a/views/examples/textfield_example.h b/views/examples/textfield_example.h
index 608a3e7..faa9f46 100644
--- a/views/examples/textfield_example.h
+++ b/views/examples/textfield_example.h
@@ -15,6 +15,10 @@
#include "views/controls/textfield/textfield_controller.h"
#include "views/examples/example_base.h"
+namespace views {
+class TextStyle;
+}
+
namespace examples {
// TextfieldExample mimics login screen.
@@ -51,6 +55,11 @@ class TextfieldExample : public ExampleBase,
views::TextButton* set_;
views::TextButton* set_style_;
+ // Text Styles
+ views::TextStyle* underline_;
+ views::TextStyle* strike_;
+ views::TextStyle* color_;
+
DISALLOW_COPY_AND_ASSIGN(TextfieldExample);
};
diff --git a/views/views.gyp b/views/views.gyp
index 1771c85..39593fc 100644
--- a/views/views.gyp
+++ b/views/views.gyp
@@ -206,6 +206,8 @@
'controls/textfield/gtk_views_entry.h',
'controls/textfield/gtk_views_textview.cc',
'controls/textfield/gtk_views_textview.h',
+ 'controls/textfield/text_style.cc',
+ 'controls/textfield/text_style.h',
'controls/textfield/textfield.cc',
'controls/textfield/textfield.h',
'controls/textfield/textfield_controller.h',