diff options
author | msw@chromium.org <msw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-07-25 05:45:36 +0000 |
---|---|---|
committer | msw@chromium.org <msw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-07-25 05:45:36 +0000 |
commit | 1a048518abc4766f489068a3a833963d6a98f5c3 (patch) | |
tree | 3bd2acf2d73b46c7f3935119ca3451e19ff1e224 | |
parent | f077210dfebac7d8355038dc5c2dbeb81f692017 (diff) | |
download | chromium_src-1a048518abc4766f489068a3a833963d6a98f5c3.zip chromium_src-1a048518abc4766f489068a3a833963d6a98f5c3.tar.gz chromium_src-1a048518abc4766f489068a3a833963d6a98f5c3.tar.bz2 |
Revert 93840 - Add gfx::RenderText and support code.
RenderText is NativeTextFieldViews' interface to platform-specific text rendering engines.
This change doesn't hook in any new Pango or Uniscribe functionality, it will just setup the necessary API.
Review URL: http://codereview.chromium.org/7265011
TBR=msw@chromium.org
Review URL: http://codereview.chromium.org/7492041
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@93842 0039d316-1c4b-4281-b951-d872f2087c98
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_ @@ -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', |