diff options
-rw-r--r-- | ui/gfx/render_text.cc | 142 | ||||
-rw-r--r-- | ui/gfx/render_text.h | 52 | ||||
-rw-r--r-- | ui/gfx/render_text_win.cc | 628 | ||||
-rw-r--r-- | ui/gfx/render_text_win.h | 104 | ||||
-rw-r--r-- | views/controls/textfield/native_textfield_views.cc | 11 | ||||
-rw-r--r-- | views/controls/textfield/textfield_views_model.cc | 13 | ||||
-rw-r--r-- | views/controls/textfield/textfield_views_model.h | 3 | ||||
-rw-r--r-- | views/examples/textfield_example.cc | 33 |
8 files changed, 892 insertions, 94 deletions
diff --git a/ui/gfx/render_text.cc b/ui/gfx/render_text.cc index 498bb3b..26518d1 100644 --- a/ui/gfx/render_text.cc +++ b/ui/gfx/render_text.cc @@ -164,14 +164,8 @@ void RenderText::SetText(const string16& text) { cached_bounds_and_offset_valid_ = false; } -void RenderText::SetSelectionModel(const SelectionModel& sel) { - size_t start = sel.selection_start(); - size_t end = sel.selection_end(); - selection_model_.set_selection_start(std::min(start, text().length())); - selection_model_.set_selection_end(std::min(end, text().length())); - selection_model_.set_caret_pos(std::min(sel.caret_pos(), text().length())); - selection_model_.set_caret_placement(sel.caret_placement()); - +void RenderText::ToggleInsertMode() { + insert_mode_ = !insert_mode_; cached_bounds_and_offset_valid_ = false; } @@ -184,13 +178,8 @@ size_t RenderText::GetCursorPosition() const { return selection_model_.selection_end(); } -void RenderText::SetCursorPosition(const size_t position) { - SelectionModel sel(selection_model()); - sel.set_selection_start(position); - sel.set_selection_end(position); - sel.set_caret_pos(GetIndexOfPreviousGrapheme(position)); - sel.set_caret_placement(SelectionModel::TRAILING); - SetSelectionModel(sel); +void RenderText::SetCursorPosition(size_t position) { + MoveCursorTo(position, false); } void RenderText::MoveCursorLeft(BreakType break_type, bool select) { @@ -237,9 +226,26 @@ void RenderText::MoveCursorRight(BreakType break_type, bool select) { MoveCursorTo(position); } -bool RenderText::MoveCursorTo(const SelectionModel& selection) { - bool changed = !selection.Equals(selection_model_); - SetSelectionModel(selection); +bool RenderText::MoveCursorTo(const SelectionModel& selection_model) { + SelectionModel sel(selection_model); + size_t text_length = text().length(); + // Enforce valid selection model components. + if (sel.selection_start() > text_length) + sel.set_selection_start(text_length); + if (sel.selection_end() > text_length) + sel.set_selection_end(text_length); + // The current model only supports caret positions at valid character indices. + if (text_length == 0) { + sel.set_caret_pos(0); + sel.set_caret_placement(SelectionModel::LEADING); + } else if (sel.caret_pos() >= text_length) { + SelectionModel end = GetTextDirection() == base::i18n::RIGHT_TO_LEFT ? + LeftEndSelectionModel() : RightEndSelectionModel(); + sel.set_caret_pos(end.caret_pos()); + sel.set_caret_placement(end.caret_placement()); + } + bool changed = !sel.Equals(selection_model_); + SetSelectionModel(sel); return changed; } @@ -251,6 +257,8 @@ bool RenderText::MoveCursorTo(const Point& point, bool select) { } bool RenderText::IsPointInSelection(const Point& point) { + if (EmptySelection()) + return false; // TODO(xji): should this check whether the point is inside the visual // selection bounds? In case of "abcFED", if "ED" is selected, |point| points // to the right half of 'c', is the point in selection? @@ -265,8 +273,8 @@ void RenderText::ClearSelection() { } void RenderText::SelectAll() { - SelectionModel sel(0, text().length(), - text().length(), SelectionModel::LEADING); + SelectionModel sel(RightEndSelectionModel()); + sel.set_selection_start(LeftEndSelectionModel().selection_start()); SetSelectionModel(sel); } @@ -308,12 +316,8 @@ void RenderText::SelectWord() { break; } - SelectionModel sel(selection_model()); - sel.set_selection_start(selection_start); - sel.set_selection_end(cursor_position); - sel.set_caret_pos(GetIndexOfPreviousGrapheme(cursor_position)); - sel.set_caret_placement(SelectionModel::TRAILING); - SetSelectionModel(sel); + MoveCursorTo(selection_start, false); + MoveCursorTo(cursor_position, true); } const ui::Range& RenderText::GetCompositionRange() const { @@ -350,8 +354,8 @@ void RenderText::ApplyDefaultStyle() { } 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). + if (base::i18n::IsRTL()) + return base::i18n::RIGHT_TO_LEFT; return base::i18n::LEFT_TO_RIGHT; } @@ -443,20 +447,6 @@ SelectionModel RenderText::FindCursorPosition(const Point& point) { return SelectionModel(left_pos); } -std::vector<Rect> RenderText::GetSubstringBounds(size_t from, size_t to) { - size_t start = std::min(from, to); - size_t end = std::max(from, to); - const Font& font = default_style_.font; - int start_x = font.GetStringWidth(text().substr(0, start)); - int end_x = font.GetStringWidth(text().substr(0, end)); - Rect rect(start_x, 0, end_x - start_x, font.GetHeight()); - rect.Offset(display_rect_.origin()); - rect.Offset(GetUpdatedDisplayOffset()); - // Center the rect vertically in |display_rect_|. - rect.Offset(Point(0, (display_rect_.height() - rect.height()) / 2)); - return std::vector<Rect>(1, rect); -} - Rect RenderText::GetCursorBounds(const SelectionModel& selection, bool insert_mode) { size_t from = selection.selection_end(); @@ -549,9 +539,35 @@ SelectionModel RenderText::GetRightSelectionModel(const SelectionModel& current, return SelectionModel(pos, pos, SelectionModel::LEADING); } +SelectionModel RenderText::LeftEndSelectionModel() { + return SelectionModel(0, 0, SelectionModel::LEADING); +} + +SelectionModel RenderText::RightEndSelectionModel() { + size_t cursor = text().length(); + size_t caret_pos = GetIndexOfPreviousGrapheme(cursor); + SelectionModel::CaretPlacement placement = (caret_pos == cursor) ? + SelectionModel::LEADING : SelectionModel::TRAILING; + return SelectionModel(cursor, caret_pos, placement); +} + size_t RenderText::GetIndexOfPreviousGrapheme(size_t position) { // TODO(msw): Handle complex script. - return std::max(static_cast<int>(position - 1), static_cast<int>(0)); + return std::max(static_cast<long>(position - 1), static_cast<long>(0)); +} + +std::vector<Rect> RenderText::GetSubstringBounds(size_t from, size_t to) { + size_t start = std::min(from, to); + size_t end = std::max(from, to); + const Font& font = default_style_.font; + int start_x = font.GetStringWidth(text().substr(0, start)); + int end_x = font.GetStringWidth(text().substr(0, end)); + Rect rect(start_x, 0, end_x - start_x, font.GetHeight()); + rect.Offset(display_rect_.origin()); + rect.Offset(GetUpdatedDisplayOffset()); + // Center the rect vertically in |display_rect_|. + rect.Offset(Point(0, (display_rect_.height() - rect.height()) / 2)); + return std::vector<Rect>(1, rect); } void RenderText::ApplyCompositionAndSelectionStyles( @@ -576,6 +592,45 @@ void RenderText::ApplyCompositionAndSelectionStyles( } } +Point RenderText::ToTextPoint(const Point& point) { + Point p(point.Subtract(display_rect().origin())); + p = p.Subtract(GetUpdatedDisplayOffset()); + if (base::i18n::IsRTL()) + p.Offset(GetStringWidth() - display_rect().width() + 1, 0); + return p; +} + +Point RenderText::ToViewPoint(const Point& point) { + Point p(point.Add(display_rect().origin())); + p = p.Add(GetUpdatedDisplayOffset()); + if (base::i18n::IsRTL()) + p.Offset(display_rect().width() - GetStringWidth() - 1, 0); + return p; +} + +void RenderText::SetSelectionModel(const SelectionModel& selection_model) { + DCHECK_LE(selection_model.selection_start(), text().length()); + selection_model_.set_selection_start(selection_model.selection_start()); + DCHECK_LE(selection_model.selection_end(), text().length()); + selection_model_.set_selection_end(selection_model.selection_end()); + DCHECK_LT(selection_model.caret_pos(), + std::max(text().length(), static_cast<size_t>(1))); + selection_model_.set_caret_pos(selection_model.caret_pos()); + selection_model_.set_caret_placement(selection_model.caret_placement()); + + cached_bounds_and_offset_valid_ = false; +} + +void RenderText::MoveCursorTo(size_t position, bool select) { + size_t cursor = std::min(position, text().length()); + size_t caret_pos = GetIndexOfPreviousGrapheme(cursor); + SelectionModel::CaretPlacement placement = (caret_pos == cursor) ? + SelectionModel::LEADING : SelectionModel::TRAILING; + size_t selection_start = select ? GetSelectionStart() : cursor; + SelectionModel sel(selection_start, cursor, caret_pos, placement); + SetSelectionModel(sel); +} + bool RenderText::IsPositionAtWordSelectionBoundary(size_t pos) { return pos == 0 || (u_isalnum(text()[pos - 1]) && !u_isalnum(text()[pos])) || (!u_isalnum(text()[pos - 1]) && u_isalnum(text()[pos])); @@ -589,7 +644,6 @@ void RenderText::UpdateCachedBoundsAndOffset() { // function will set |cursor_bounds_| and |display_offset_| to correct values. cached_bounds_and_offset_valid_ = true; cursor_bounds_ = GetCursorBounds(selection_model_, insert_mode_); - cursor_bounds_.set_width(std::max(cursor_bounds_.width(), 1)); // Update |display_offset_| to ensure the current cursor is visible. int display_width = display_rect_.width(); int string_width = GetStringWidth(); diff --git a/ui/gfx/render_text.h b/ui/gfx/render_text.h index cc4f4e5..e13d8e4 100644 --- a/ui/gfx/render_text.h +++ b/ui/gfx/render_text.h @@ -154,13 +154,12 @@ class UI_EXPORT RenderText { virtual void SetText(const string16& text); const SelectionModel& selection_model() const { return selection_model_; } - void SetSelectionModel(const SelectionModel& sel); 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_; } + void ToggleInsertMode(); bool focused() const { return focused_; } void set_focused(bool focused) { focused_ = focused; } @@ -176,7 +175,7 @@ class UI_EXPORT RenderText { // edits take place, and doesn't necessarily correspond to // SelectionModel::caret_pos. size_t GetCursorPosition() const; - void SetCursorPosition(const size_t position); + void SetCursorPosition(size_t position); void SetCaretPlacement(SelectionModel::CaretPlacement placement) { selection_model_.set_caret_placement(placement); @@ -184,16 +183,18 @@ class UI_EXPORT RenderText { // 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 |select| is false, the selection start is moved to the same position. void MoveCursorLeft(BreakType break_type, bool select); void MoveCursorRight(BreakType break_type, bool select); // Set the selection_model_ to the value of |selection|. + // The selection model components are modified if invalid. // Returns true if the cursor position or selection range changed. - bool MoveCursorTo(const SelectionModel& selection); + bool MoveCursorTo(const SelectionModel& selection_model); // Move the cursor to the position associated with the clicked point. - // If |select| is false, the selection range is emptied at the new position. + // If |select| is false, the selection start is moved to the same position. + // Returns true if the cursor position or selection range changed. bool MoveCursorTo(const Point& point, bool select); size_t GetSelectionStart() const { @@ -236,13 +237,6 @@ class UI_EXPORT RenderText { // Gets the SelectionModel from a visual point in local coordinates. virtual SelectionModel FindCursorPosition(const Point& point); - // Get the visual bounds containing the logical substring within |from| to - // |to|. These bounds could be visually discontinuous if the substring is - // split by a LTR/RTL level change. These bounds are in local coordinates, but - // may be outside the visible region if the text is longer than the textfield. - // Subsequent text, cursor, or bounds changes may invalidate returned values. - virtual std::vector<Rect> GetSubstringBounds(size_t from, size_t to); - // Get the visual bounds of a cursor at |selection|. These bounds typically // represent a vertical line, but if |insert_mode| is true they contain the // bounds of the associated glyph. These bounds are in local coordinates, but @@ -274,13 +268,31 @@ class UI_EXPORT RenderText { virtual SelectionModel GetRightSelectionModel(const SelectionModel& current, BreakType break_type); + // Get the SelectionModels corresponding to visual text ends. + // The returned value represents a cursor/caret position without a selection. + virtual SelectionModel LeftEndSelectionModel(); + virtual SelectionModel RightEndSelectionModel(); + // Get the logical index of the grapheme preceeding the argument |position|. virtual size_t GetIndexOfPreviousGrapheme(size_t position); + // Get the visual bounds containing the logical substring within |from| to + // |to|. These bounds could be visually discontinuous if the substring is + // split by a LTR/RTL level change. These bounds are in local coordinates, but + // may be outside the visible region if the text is longer than the textfield. + // Subsequent text, cursor, or bounds changes may invalidate returned values. + // TODO(msw) Re-evaluate this function's necessity and signature. + virtual std::vector<Rect> GetSubstringBounds(size_t from, size_t to); + // Apply composition style (underline) to composition range and selection // style (foreground) to selection range. void ApplyCompositionAndSelectionStyles(StyleRanges* style_ranges) const; + // Convert points from the text space to the view space and back. + // Handles the display area, display offset, and the application LTR/RTL mode. + Point ToTextPoint(const Point& point); + Point ToViewPoint(const Point& point); + private: friend class RenderTextTest; @@ -289,8 +301,13 @@ class UI_EXPORT RenderText { FRIEND_TEST_ALL_PREFIXES(RenderTextTest, ApplyStyleRange); FRIEND_TEST_ALL_PREFIXES(RenderTextTest, StyleRangesAdjust); - // Clear out |style_ranges_|. - void ClearStyleRanges(); + // Sets the selection model, the argument is assumed to be valid. + void SetSelectionModel(const SelectionModel& selection_model); + + // Set the cursor to |position|, with the caret trailing the previous + // grapheme, or if there is no previous grapheme, leading the cursor position. + // If |select| is false, the selection start is moved to the same position. + void MoveCursorTo(size_t position, bool select); bool IsPositionAtWordSelectionBoundary(size_t pos); @@ -328,9 +345,8 @@ class UI_EXPORT RenderText { // Get this point with GetUpdatedDisplayOffset (or risk using a stale value). Point display_offset_; - // The cached bounds and offset are invalidated by operations such as - // SetCursorPosition, SetSelectionModel, Font related style change, and other - // operations that adjust the visible text bounds. + // The cached bounds and offset are invalidated by changes to the cursor, + // selection, font, and other operations that adjust the visible text bounds. bool cached_bounds_and_offset_valid_; DISALLOW_COPY_AND_ASSIGN(RenderText); diff --git a/ui/gfx/render_text_win.cc b/ui/gfx/render_text_win.cc index 9f47946..3195093 100644 --- a/ui/gfx/render_text_win.cc +++ b/ui/gfx/render_text_win.cc @@ -4,13 +4,639 @@ #include "ui/gfx/render_text_win.h" +#include "base/i18n/break_iterator.h" +#include "base/logging.h" +#include "base/stl_util.h" +#include "base/string_util.h" +#include "skia/ext/skia_utils_win.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/canvas_skia.h" +#include "third_party/skia/include/core/SkTypeface.h" + +namespace { + +// The maximum supported number of Uniscribe runs; a SCRIPT_ITEM is 8 bytes. +// TODO(msw): Review memory use/failure? Max string length? Alternate approach? +const int kGuessItems = 100; +const int kMaxItems = 10000; + +// The maximum supported number of Uniscribe glyphs; a glyph is 1 word. +// TODO(msw): Review memory use/failure? Max string length? Alternate approach? +const int kMaxGlyphs = 100000; + +// TODO(msw): Solve gfx/Uniscribe/Skia text size unit conversion issues. +const float kSkiaFontScale = 1.375; + +} // namespace + namespace gfx { +namespace internal { + +TextRun::TextRun() + : strike(false), + width(0), + preceding_run_widths(0), + glyph_count(0) { +} + +} // namespace internal + RenderTextWin::RenderTextWin() - : RenderText() { + : RenderText(), + script_control_(), + script_state_(), + script_cache_(NULL), + string_width_(0) { + // Omitting default constructors for script_* would leave POD uninitialized. + HRESULT hr = 0; + + // TODO(msw): Call ScriptRecordDigitSubstitution on WM_SETTINGCHANGE message. + // TODO(msw): Use Chrome/profile locale/language settings? + hr = ScriptRecordDigitSubstitution(LOCALE_USER_DEFAULT, &digit_substitute_); + DCHECK(SUCCEEDED(hr)); + + hr = ScriptApplyDigitSubstitution(&digit_substitute_, + &script_control_, + &script_state_); + DCHECK(SUCCEEDED(hr)); + script_control_.fMergeNeutralItems = true; + + MoveCursorTo(LeftEndSelectionModel()); } RenderTextWin::~RenderTextWin() { + ScriptFreeCache(&script_cache_); + STLDeleteContainerPointers(runs_.begin(), runs_.end()); +} + +void RenderTextWin::SetText(const string16& text) { + // TODO(msw): Skip complex processing if ScriptIsComplex returns false. + RenderText::SetText(text); + ItemizeLogicalText(); + LayoutVisualText(CreateCompatibleDC(NULL)); +} + +void RenderTextWin::SetDisplayRect(const Rect& r) { + RenderText::SetDisplayRect(r); + ItemizeLogicalText(); + LayoutVisualText(CreateCompatibleDC(NULL)); +} + +void RenderTextWin::ApplyStyleRange(StyleRange style_range) { + RenderText::ApplyStyleRange(style_range); + ItemizeLogicalText(); + LayoutVisualText(CreateCompatibleDC(NULL)); +} + +void RenderTextWin::ApplyDefaultStyle() { + RenderText::ApplyDefaultStyle(); + ItemizeLogicalText(); + LayoutVisualText(CreateCompatibleDC(NULL)); +} + +int RenderTextWin::GetStringWidth() { + return string_width_; +} + +void RenderTextWin::Draw(Canvas* canvas) { + skia::ScopedPlatformPaint scoped_platform_paint(canvas->AsCanvasSkia()); + HDC hdc = scoped_platform_paint.GetPlatformSurface(); + int saved_dc = SaveDC(hdc); + DrawSelection(canvas); + DrawVisualText(canvas); + DrawCursor(canvas); + RestoreDC(hdc, saved_dc); +} + +SelectionModel RenderTextWin::FindCursorPosition(const Point& point) { + if (text().empty()) + return SelectionModel(); + + // Find the run that contains the point and adjust the argument location. + Point p(ToTextPoint(point)); + size_t run_index = GetRunContainingPoint(p); + if (run_index == runs_.size()) + return (p.x() < 0) ? LeftEndSelectionModel() : RightEndSelectionModel(); + internal::TextRun* run = runs_[run_index]; + + int position = 0, trailing = 0; + HRESULT hr = ScriptXtoCP(p.x() - run->preceding_run_widths, + run->range.length(), + run->glyph_count, + run->logical_clusters.get(), + run->visible_attributes.get(), + run->advance_widths.get(), + &(run->script_analysis), + &position, + &trailing); + DCHECK(SUCCEEDED(hr)); + position += run->range.start(); + + size_t cursor = position + trailing; + DCHECK_GE(cursor, 0U); + DCHECK_LE(cursor, text().length()); + return SelectionModel(cursor, position, + (trailing > 0) ? SelectionModel::TRAILING : SelectionModel::LEADING); +} + +Rect RenderTextWin::GetCursorBounds(const SelectionModel& selection, + bool insert_mode) { + // Highlight the logical cursor (selection end) when not in insert mode. + size_t pos = insert_mode ? selection.caret_pos() : selection.selection_end(); + size_t run_index = GetRunContainingPosition(pos); + internal::TextRun* run = run_index == runs_.size() ? NULL : runs_[run_index]; + + int start_x = 0, end_x = 0; + if (run) { + HRESULT hr = 0; + hr = ScriptCPtoX(pos - run->range.start(), + false, + run->range.length(), + run->glyph_count, + run->logical_clusters.get(), + run->visible_attributes.get(), + run->advance_widths.get(), + &(run->script_analysis), + &start_x); + DCHECK(SUCCEEDED(hr)); + hr = ScriptCPtoX(pos - run->range.start(), + true, + run->range.length(), + run->glyph_count, + run->logical_clusters.get(), + run->visible_attributes.get(), + run->advance_widths.get(), + &(run->script_analysis), + &end_x); + DCHECK(SUCCEEDED(hr)); + } + // TODO(msw): Use the last visual run's font instead of the default font? + int height = run ? run->font.GetHeight() : default_style().font.GetHeight(); + Rect rect(std::min(start_x, end_x), 0, std::abs(end_x - start_x), height); + // Offset to the run start or the right/left end for an out of bounds index. + // Also center the rect vertically in the display area. + int text_end_offset = base::i18n::IsRTL() ? 0 : GetStringWidth(); + rect.Offset((run ? run->preceding_run_widths : text_end_offset), + (display_rect().height() - rect.height()) / 2); + // Adjust for leading/trailing in insert mode. + if (insert_mode && run) { + bool leading = selection.caret_placement() == SelectionModel::LEADING; + // Adjust the x value for right-side placement. + if (run->script_analysis.fRTL == leading) + rect.set_x(rect.right()); + rect.set_width(0); + } + rect.set_origin(ToViewPoint(rect.origin())); + return rect; +} + +SelectionModel RenderTextWin::GetLeftSelectionModel( + const SelectionModel& selection, + BreakType break_type) { + if (break_type == LINE_BREAK || text().empty()) + return LeftEndSelectionModel(); + if (break_type == CHARACTER_BREAK) + return LeftSelectionModel(selection); + // TODO(msw): Implement word breaking. + return RenderText::GetLeftSelectionModel(selection, break_type); +} + +SelectionModel RenderTextWin::GetRightSelectionModel( + const SelectionModel& selection, + BreakType break_type) { + if (break_type == LINE_BREAK || text().empty()) + return RightEndSelectionModel(); + if (break_type == CHARACTER_BREAK) + return RightSelectionModel(selection); + // TODO(msw): Implement word breaking. + return RenderText::GetRightSelectionModel(selection, break_type); +} + +SelectionModel RenderTextWin::LeftEndSelectionModel() { + if (text().empty()) + return SelectionModel(0, 0, SelectionModel::LEADING); + size_t cursor = base::i18n::IsRTL() ? text().length() : 0; + internal::TextRun* run = runs_[visual_to_logical_[0]]; + bool rtl = run->script_analysis.fRTL; + size_t caret = rtl ? run->range.end() - 1 : run->range.start(); + SelectionModel::CaretPlacement placement = + rtl ? SelectionModel::TRAILING : SelectionModel::LEADING; + return SelectionModel(cursor, caret, placement); +} + +SelectionModel RenderTextWin::RightEndSelectionModel() { + if (text().empty()) + return SelectionModel(0, 0, SelectionModel::LEADING); + size_t cursor = base::i18n::IsRTL() ? 0 : text().length(); + internal::TextRun* run = runs_[visual_to_logical_[runs_.size() - 1]]; + bool rtl = run->script_analysis.fRTL; + size_t caret = rtl ? run->range.start() : run->range.end() - 1; + SelectionModel::CaretPlacement placement = + rtl ? SelectionModel::LEADING : SelectionModel::TRAILING; + return SelectionModel(cursor, caret, placement); +} + +size_t RenderTextWin::GetIndexOfPreviousGrapheme(size_t position) { + return IndexOfAdjacentGrapheme(position, false); +} + +std::vector<Rect> RenderTextWin::GetSubstringBounds(size_t from, size_t to) { + ui::Range range(from, to); + DCHECK(ui::Range(0, text().length()).Contains(range)); + Point display_offset(GetUpdatedDisplayOffset()); + std::vector<Rect> bounds; + HRESULT hr = 0; + + // Add a Rect for each run/selection intersection. + // TODO(msw): The bounds should probably not always be leading the range ends. + for (size_t i = 0; i < runs_.size(); ++i) { + internal::TextRun* run = runs_[visual_to_logical_[i]]; + ui::Range intersection = run->range.Intersect(range); + if (intersection.IsValid()) { + DCHECK(!intersection.is_reversed()); + int start_offset = 0; + hr = ScriptCPtoX(intersection.start() - run->range.start(), + false, + run->range.length(), + run->glyph_count, + run->logical_clusters.get(), + run->visible_attributes.get(), + run->advance_widths.get(), + &(run->script_analysis), + &start_offset); + DCHECK(SUCCEEDED(hr)); + int end_offset = 0; + hr = ScriptCPtoX(intersection.end() - run->range.start(), + false, + run->range.length(), + run->glyph_count, + run->logical_clusters.get(), + run->visible_attributes.get(), + run->advance_widths.get(), + &(run->script_analysis), + &end_offset); + DCHECK(SUCCEEDED(hr)); + if (start_offset > end_offset) + std::swap(start_offset, end_offset); + Rect rect(run->preceding_run_widths + start_offset, 0, + end_offset - start_offset, run->font.GetHeight()); + // Center the rect vertically in the display area. + rect.Offset(0, (display_rect().height() - rect.height()) / 2); + rect.set_origin(ToViewPoint(rect.origin())); + // Union this with the last rect if they're adjacent. + if (!bounds.empty() && rect.SharesEdgeWith(bounds.back())) { + rect = rect.Union(bounds.back()); + bounds.pop_back(); + } + bounds.push_back(rect); + } + } + return bounds; +} + +void RenderTextWin::ItemizeLogicalText() { + text_is_dirty_ = false; + STLDeleteContainerPointers(runs_.begin(), runs_.end()); + runs_.clear(); + if (text().empty()) + return; + + const wchar_t* raw_text = text().c_str(); + const int text_length = text().length(); + + HRESULT hr = E_OUTOFMEMORY; + int script_items_count = 0; + scoped_array<SCRIPT_ITEM> script_items; + for (size_t n = kGuessItems; hr == E_OUTOFMEMORY && n < kMaxItems; n *= 2) { + // Derive the array of Uniscribe script items from the logical text. + // ScriptItemize always adds a terminal array item so that the length of the + // last item can be derived from the terminal SCRIPT_ITEM::iCharPos. + script_items.reset(new SCRIPT_ITEM[n]); + hr = ScriptItemize(raw_text, + text_length, + n - 1, + &script_control_, + &script_state_, + script_items.get(), + &script_items_count); + } + DCHECK(SUCCEEDED(hr)); + + if (script_items_count <= 0) + return; + + // Build the list of runs, merge font/underline styles. + // TODO(msw): Only break for font changes, not color etc. See TextRun comment. + // TODO(msw): Apply the overriding selection and composition styles. + StyleRanges::const_iterator style = style_ranges().begin(); + SCRIPT_ITEM* script_item = script_items.get(); + for (int run_break = 0; run_break < text_length;) { + internal::TextRun* run = new internal::TextRun(); + run->range.set_start(run_break); + run->font = !style->underline ? style->font : + style->font.DeriveFont(0, style->font.GetStyle() | Font::UNDERLINED); + run->foreground = style->foreground; + run->strike = style->strike; + run->script_analysis = script_item->a; + + // Find the range end and advance the structures as needed. + int script_item_end = (script_item + 1)->iCharPos; + int style_range_end = style->range.end(); + run_break = std::min(script_item_end, style_range_end); + if (script_item_end <= style_range_end) + script_item++; + if (script_item_end >= style_range_end) + style++; + run->range.set_end(run_break); + runs_.push_back(run); + } +} + +void RenderTextWin::LayoutVisualText(HDC hdc) { + HRESULT hr = 0; + std::vector<internal::TextRun*>::const_iterator run_iter; + for (run_iter = runs_.begin(); run_iter < runs_.end(); ++run_iter) { + internal::TextRun* run = *run_iter; + int run_length = run->range.length(); + string16 run_string(text().substr(run->range.start(), run_length)); + const wchar_t* run_text = run_string.c_str(); + // Select the font desired for glyph generation + SelectObject(hdc, run->font.GetNativeFont()); + + run->logical_clusters.reset(new WORD[run_length]); + run->glyph_count = 0; + hr = E_OUTOFMEMORY; + // kGuess comes from: http://msdn.microsoft.com/en-us/library/dd368564.aspx + const int kGuess = static_cast<int>(1.5 * run_length + 16); + for (size_t n = kGuess; hr == E_OUTOFMEMORY && n < kMaxGlyphs; n *= 2) { + run->glyphs.reset(new WORD[n]); + run->visible_attributes.reset(new SCRIPT_VISATTR[n]); + hr = ScriptShape(hdc, + &script_cache_, + run_text, + run_length, + n, + &(run->script_analysis), + run->glyphs.get(), + run->logical_clusters.get(), + run->visible_attributes.get(), + &(run->glyph_count)); + } + DCHECK(SUCCEEDED(hr)); + + if (run->glyph_count > 0) { + run->advance_widths.reset(new int[run->glyph_count]); + run->offsets.reset(new GOFFSET[run->glyph_count]); + hr = ScriptPlace(hdc, + &script_cache_, + run->glyphs.get(), + run->glyph_count, + run->visible_attributes.get(), + &(run->script_analysis), + run->advance_widths.get(), + run->offsets.get(), + &(run->abc_widths)); + DCHECK(SUCCEEDED(hr)); + } + } + + if (runs_.size() > 0) { + // Build the array of bidirectional embedding levels. + scoped_array<BYTE> levels(new BYTE[runs_.size()]); + for (size_t i = 0; i < runs_.size(); ++i) + levels[i] = runs_[i]->script_analysis.s.uBidiLevel; + + // Get the maps between visual and logical run indices. + visual_to_logical_.reset(new int[runs_.size()]); + logical_to_visual_.reset(new int[runs_.size()]); + hr = ScriptLayout(runs_.size(), + levels.get(), + visual_to_logical_.get(), + logical_to_visual_.get()); + DCHECK(SUCCEEDED(hr)); + } + + // Precalculate run width information. + size_t preceding_run_widths = 0; + for (size_t i = 0; i < runs_.size(); ++i) { + internal::TextRun* run = runs_[visual_to_logical_[i]]; + run->preceding_run_widths = preceding_run_widths; + const ABC& abc = run->abc_widths; + run->width = abc.abcA + abc.abcB + abc.abcC; + preceding_run_widths += run->width; + } + string_width_ = preceding_run_widths; +} + +size_t RenderTextWin::GetRunContainingPosition(size_t position) const { + // Find the text run containing the argument position. + size_t run = 0; + for (; run < runs_.size(); ++run) + if (runs_[run]->range.start() <= position && + runs_[run]->range.end() > position) + break; + return run; +} + +size_t RenderTextWin::GetRunContainingPoint(const Point& point) const { + // Find the text run containing the argument point (assumed already offset). + size_t run = 0; + for (; run < runs_.size(); ++run) + if (runs_[run]->preceding_run_widths <= point.x() && + runs_[run]->preceding_run_widths + runs_[run]->width > point.x()) + break; + return run; +} + +size_t RenderTextWin::IndexOfAdjacentGrapheme(size_t index, bool next) const { + size_t run_index = GetRunContainingPosition(index); + internal::TextRun* run = run_index < runs_.size() ? runs_[run_index] : NULL; + long start = run ? run->range.start() : 0; + long length = run ? run->range.length() : text().length(); + long ch = index - start; + WORD cluster = run ? run->logical_clusters[ch] : 0; + + if (!next) { + do { + ch--; + } while (ch >= 0 && run && run->logical_clusters[ch] == cluster); + } else { + while (ch < length && run && run->logical_clusters[ch] == cluster) + ch++; + } + return std::max(static_cast<long>(std::min(ch, length) + start), 0L); +} + +SelectionModel RenderTextWin::FirstSelectionModelInsideRun( + internal::TextRun* run) const { + size_t caret = run->range.start(); + size_t cursor = IndexOfAdjacentGrapheme(caret, true); + return SelectionModel(cursor, caret, SelectionModel::TRAILING); +} + +SelectionModel RenderTextWin::LastSelectionModelInsideRun( + internal::TextRun* run) const { + size_t caret = IndexOfAdjacentGrapheme(run->range.end(), false); + return SelectionModel(caret, caret, SelectionModel::LEADING); +} + +SelectionModel RenderTextWin::LeftSelectionModel( + const SelectionModel& selection) { + size_t caret = selection.caret_pos(); + SelectionModel::CaretPlacement caret_placement = selection.caret_placement(); + size_t run_index = GetRunContainingPosition(caret); + DCHECK(run_index < runs_.size()); + internal::TextRun* run = runs_[run_index]; + + // If the caret's associated character is in a LTR run. + if (!run->script_analysis.fRTL) { + if (caret_placement == SelectionModel::TRAILING) + return SelectionModel(caret, caret, SelectionModel::LEADING); + else if (caret > run->range.start()) { + caret = IndexOfAdjacentGrapheme(caret, false); + return SelectionModel(caret, caret, SelectionModel::LEADING); + } + } else { // The caret's associated character is in a RTL run. + if (caret_placement == SelectionModel::LEADING) { + size_t cursor = IndexOfAdjacentGrapheme(caret, true); + return SelectionModel(cursor, caret, SelectionModel::TRAILING); + } else if (selection.selection_end() < run->range.end()) { + caret = IndexOfAdjacentGrapheme(caret, true); + size_t cursor = IndexOfAdjacentGrapheme(caret, true); + return SelectionModel(cursor, caret, SelectionModel::TRAILING); + } + } + + // The character is at the begin of its run; go to the previous visual run. + size_t visual_index = logical_to_visual_[run_index]; + if (visual_index == 0) + return LeftEndSelectionModel(); + internal::TextRun* prev = runs_[visual_to_logical_[visual_index - 1]]; + return prev->script_analysis.fRTL ? FirstSelectionModelInsideRun(prev) : + LastSelectionModelInsideRun(prev); +} + +SelectionModel RenderTextWin::RightSelectionModel( + const SelectionModel& selection) { + size_t caret = selection.caret_pos(); + SelectionModel::CaretPlacement caret_placement = selection.caret_placement(); + size_t run_index = GetRunContainingPosition(caret); + DCHECK(run_index < runs_.size()); + internal::TextRun* run = runs_[run_index]; + + // If the caret's associated character is in a LTR run. + if (!run->script_analysis.fRTL) { + if (caret_placement == SelectionModel::LEADING) { + size_t cursor = IndexOfAdjacentGrapheme(caret, true); + return SelectionModel(cursor, caret, SelectionModel::TRAILING); + } else if (selection.selection_end() < run->range.end()) { + caret = IndexOfAdjacentGrapheme(caret, true); + size_t cursor = IndexOfAdjacentGrapheme(caret, true); + return SelectionModel(cursor, caret, SelectionModel::TRAILING); + } + } else { // The caret's associated character is in a RTL run. + if (caret_placement == SelectionModel::TRAILING) + return SelectionModel(caret, caret, SelectionModel::LEADING); + else if (caret > run->range.start()) { + caret = IndexOfAdjacentGrapheme(caret, false); + return SelectionModel(caret, caret, SelectionModel::LEADING); + } + } + + // The character is at the end of its run; go to the next visual run. + size_t visual_index = logical_to_visual_[run_index]; + if (visual_index == runs_.size() - 1) + return RightEndSelectionModel(); + internal::TextRun* next = runs_[visual_to_logical_[visual_index + 1]]; + return next->script_analysis.fRTL ? LastSelectionModelInsideRun(next) : + FirstSelectionModelInsideRun(next); +} + +void RenderTextWin::DrawSelection(Canvas* canvas) { + std::vector<Rect> sel( + GetSubstringBounds(GetSelectionStart(), GetCursorPosition())); + SkColor color = focused() ? kFocusedSelectionColor : kUnfocusedSelectionColor; + for (std::vector<Rect>::const_iterator i = sel.begin(); i < sel.end(); ++i) + canvas->FillRectInt(color, i->x(), i->y(), i->width(), i->height()); +} + +void RenderTextWin::DrawVisualText(Canvas* canvas) { + if (text().empty()) + return; + + CanvasSkia* canvas_skia = canvas->AsCanvasSkia(); + skia::ScopedPlatformPaint scoped_platform_paint(canvas_skia); + + Point offset(ToViewPoint(Point())); + // TODO(msw): Establish a vertical baseline for strings of mixed font heights. + size_t height = default_style().font.GetHeight(); + // Center the text vertically in the display area. + offset.Offset(0, (display_rect().height() - height) / 2); + + SkPaint paint; + paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + paint.setStyle(SkPaint::kFill_Style); + paint.setAntiAlias(true); + paint.setSubpixelText(true); + paint.setLCDRenderText(true); + SkPoint point(SkPoint::Make(SkIntToScalar(offset.x()), + SkIntToScalar(display_rect().height() - offset.y()))); + RECT rect = display_rect().ToRECT(); + scoped_array<SkPoint> pos; + for (size_t i = 0; i < runs_.size(); ++i) { + // Get the run specified by the visual-to-logical map. + internal::TextRun* run = runs_[visual_to_logical_[i]]; + + // TODO(msw): Font default/fallback and style integration. + std::string font(UTF16ToASCII(run->font.GetFontName())); + SkTypeface::Style style = SkTypeface::kNormal; + SkTypeface* typeface = SkTypeface::CreateFromName(font.c_str(), style); + if (typeface) { + paint.setTypeface(typeface); + // |paint| adds its own ref. Release the ref from CreateFromName. + typeface->unref(); + } + // TODO(msw): Skia font size units? Set OmniboxViewViews gfx::Font size? + int font_size = run->font.GetFontSize(); + paint.setTextSize(SkFloatToScalar(font_size * kSkiaFontScale)); + paint.setColor(run->foreground); + + // Based on WebCore::skiaDrawText. + pos.reset(new SkPoint[run->glyph_count]); + for (int glyph = 0; glyph < run->glyph_count; glyph++) { + pos[glyph].set(point.x() + run->offsets[glyph].du, + point.y() + run->offsets[glyph].dv); + point.offset(SkIntToScalar(run->advance_widths[glyph]), 0); + } + size_t byte_length = run->glyph_count * sizeof(WORD); + canvas_skia->drawPosText(run->glyphs.get(), byte_length, pos.get(), paint); + + // Draw the strikethrough. + if (run->strike) { + Rect bounds(offset, Size(run->width, run->font.GetHeight())); + SkPaint strike; + strike.setAntiAlias(true); + strike.setStyle(SkPaint::kFill_Style); + strike.setColor(run->foreground); + strike.setStrokeWidth(kStrikeWidth); + canvas->AsCanvasSkia()->drawLine(SkIntToScalar(bounds.x()), + SkIntToScalar(bounds.bottom()), + SkIntToScalar(bounds.right()), + SkIntToScalar(bounds.y()), + strike); + } + offset.Offset(run->width, 0); + } +} + +void RenderTextWin::DrawCursor(Canvas* canvas) { + // Paint cursor. Replace cursor is drawn as rectangle for now. + // TODO(msw): Draw a better cursor with a better indication of association. + if (cursor_visible() && focused()) { + Rect r(GetUpdatedCursorBounds()); + canvas->DrawRectInt(kCursorColor, r.x(), r.y(), r.width(), r.height()); + } } RenderText* RenderText::CreateRenderText() { diff --git a/ui/gfx/render_text_win.h b/ui/gfx/render_text_win.h index 829888b..d812f50 100644 --- a/ui/gfx/render_text_win.h +++ b/ui/gfx/render_text_win.h @@ -6,17 +6,121 @@ #define UI_GFX_RENDER_TEXT_WIN_H_ #pragma once +#include <usp10.h> + +#include "base/memory/scoped_ptr.h" #include "ui/gfx/render_text.h" namespace gfx { +namespace internal { + +struct TextRun { + TextRun(); + + ui::Range range; + Font font; + // TODO(msw): Disambiguate color, strike, etc. from TextRuns. + // Otherwise, this breaks the glyph shaping process. + // See the example at: http://www.catch22.net/tuts/neatpad/12. + SkColor foreground; + bool strike; + + int width; + // The cumulative widths of preceding runs. + int preceding_run_widths; + + SCRIPT_ANALYSIS script_analysis; + + scoped_array<WORD> glyphs; + scoped_array<WORD> logical_clusters; + scoped_array<SCRIPT_VISATTR> visible_attributes; + int glyph_count; + + scoped_array<int> advance_widths; + scoped_array<GOFFSET> offsets; + ABC abc_widths; + + private: + DISALLOW_COPY_AND_ASSIGN(TextRun); +}; + +} // namespace internal + // RenderTextWin is the Windows implementation of RenderText using Uniscribe. class RenderTextWin : public RenderText { public: RenderTextWin(); virtual ~RenderTextWin(); + // Overridden from RenderText: + virtual void SetText(const string16& text) OVERRIDE; + virtual void SetDisplayRect(const Rect& r) OVERRIDE; + virtual void ApplyStyleRange(StyleRange style_range) OVERRIDE; + virtual void ApplyDefaultStyle() OVERRIDE; + virtual int GetStringWidth() OVERRIDE; + virtual void Draw(Canvas* canvas) OVERRIDE; + virtual SelectionModel FindCursorPosition(const Point& point) OVERRIDE; + virtual Rect GetCursorBounds(const SelectionModel& selection, + bool insert_mode) OVERRIDE; + + protected: + // Overridden from RenderText: + virtual SelectionModel GetLeftSelectionModel(const SelectionModel& current, + BreakType break_type) OVERRIDE; + virtual SelectionModel GetRightSelectionModel(const SelectionModel& current, + BreakType break_type) OVERRIDE; + virtual SelectionModel LeftEndSelectionModel() OVERRIDE; + virtual SelectionModel RightEndSelectionModel() OVERRIDE; + virtual size_t GetIndexOfPreviousGrapheme(size_t position) OVERRIDE; + virtual std::vector<Rect> GetSubstringBounds(size_t from, size_t to) OVERRIDE; + private: + void ItemizeLogicalText(); + void LayoutVisualText(HDC hdc); + + // Return the run index that contains the argument; or the length of the + // |runs_| vector if argument exceeds the text length or width. + size_t GetRunContainingPosition(size_t position) const; + size_t GetRunContainingPoint(const Point& point) const; + + // Return an index belonging to the |next| or previous logical grapheme. + // The return value is bounded by 0 and the text length, inclusive. + size_t IndexOfAdjacentGrapheme(size_t index, bool next) const; + + // Given a |run|, returns the SelectionModel that contains the logical first + // or last caret position inside (not at a boundary of) the run. + // The returned value represents a cursor/caret position without a selection. + SelectionModel FirstSelectionModelInsideRun(internal::TextRun*) const; + SelectionModel LastSelectionModelInsideRun(internal::TextRun*) const; + + // Get the selection model visually left/right of |selection| by one grapheme. + // The returned value represents a cursor/caret position without a selection. + SelectionModel LeftSelectionModel(const SelectionModel& selection); + SelectionModel RightSelectionModel(const SelectionModel& selection); + + // Draw the text, cursor, and selection. + void DrawSelection(Canvas* canvas); + void DrawVisualText(Canvas* canvas); + void DrawCursor(Canvas* canvas); + + bool text_is_dirty_; + bool style_is_dirty_; + + // National Language Support native digit and digit substitution settings. + SCRIPT_DIGITSUBSTITUTE digit_substitute_; + + SCRIPT_CONTROL script_control_; + SCRIPT_STATE script_state_; + + SCRIPT_CACHE script_cache_; + + std::vector<internal::TextRun*> runs_; + int string_width_; + + scoped_array<int> visual_to_logical_; + scoped_array<int> logical_to_visual_; + DISALLOW_COPY_AND_ASSIGN(RenderTextWin); }; diff --git a/views/controls/textfield/native_textfield_views.cc b/views/controls/textfield/native_textfield_views.cc index 5b43215..e8fd03d 100644 --- a/views/controls/textfield/native_textfield_views.cc +++ b/views/controls/textfield/native_textfield_views.cc @@ -874,11 +874,12 @@ bool NativeTextfieldViews::HandleKeyEvent(const KeyEvent& key_event) { cursor_changed = true; break; case ui::VKEY_END: - model_->MoveCursorRight(gfx::LINE_BREAK, selection); - cursor_changed = true; - break; case ui::VKEY_HOME: - model_->MoveCursorLeft(gfx::LINE_BREAK, selection); + if ((key_code == ui::VKEY_HOME) == + (GetRenderText()->GetTextDirection() == base::i18n::RIGHT_TO_LEFT)) + model_->MoveCursorRight(gfx::LINE_BREAK, selection); + else + model_->MoveCursorLeft(gfx::LINE_BREAK, selection); cursor_changed = true; break; case ui::VKEY_BACK: @@ -921,7 +922,7 @@ bool NativeTextfieldViews::HandleKeyEvent(const KeyEvent& key_event) { cursor_changed = text_changed = model_->Delete(); break; case ui::VKEY_INSERT: - GetRenderText()->toggle_insert_mode(); + GetRenderText()->ToggleInsertMode(); cursor_changed = true; break; default: diff --git a/views/controls/textfield/textfield_views_model.cc b/views/controls/textfield/textfield_views_model.cc index 3f927dd..85e2179 100644 --- a/views/controls/textfield/textfield_views_model.cc +++ b/views/controls/textfield/textfield_views_model.cc @@ -398,11 +398,6 @@ bool TextfieldViewsModel::MoveCursorTo(const gfx::Point& point, bool select) { return render_text_->MoveCursorTo(point, select); } -std::vector<gfx::Rect> TextfieldViewsModel::GetSelectionBounds() const { - return render_text_->GetSubstringBounds(render_text_->GetSelectionStart(), - render_text_->GetCursorPosition()); -} - string16 TextfieldViewsModel::GetSelectedText() const { return GetText().substr(render_text_->MinOfSelection(), (render_text_->MaxOfSelection() - render_text_->MinOfSelection())); @@ -421,7 +416,7 @@ void TextfieldViewsModel::SelectRange(const ui::Range& range) { void TextfieldViewsModel::SelectSelectionModel(const gfx::SelectionModel& sel) { if (HasCompositionText()) ConfirmCompositionText(); - render_text_->SetSelectionModel(sel); + render_text_->MoveCursorTo(sel); } void TextfieldViewsModel::SelectAll() { @@ -509,7 +504,7 @@ bool TextfieldViewsModel::Cut() { render_text_->GetSelectionStart(), render_text_->GetSelectionStart(), gfx::SelectionModel::LEADING); - render_text_->SetSelectionModel(sel); + render_text_->MoveCursorTo(sel); DeleteSelection(); return true; } @@ -589,7 +584,7 @@ void TextfieldViewsModel::SetCompositionText( size_t end = std::min(range.start() + composition.selection.end(), range.end()); gfx::SelectionModel sel(start, end); - render_text_->SetSelectionModel(sel); + render_text_->MoveCursorTo(sel); } else { render_text_->SetCursorPosition(range.end()); } @@ -662,7 +657,7 @@ void TextfieldViewsModel::ReplaceTextInternal(const string16& text, size_t cursor = GetCursorPosition(); gfx::SelectionModel sel(render_text_->selection_model()); sel.set_selection_start(cursor + text.length()); - render_text_->SetSelectionModel(sel); + render_text_->MoveCursorTo(sel); } // Edit history is recorded in InsertText. InsertTextInternal(text, mergeable); diff --git a/views/controls/textfield/textfield_views_model.h b/views/controls/textfield/textfield_views_model.h index 616255f..abd1e13 100644 --- a/views/controls/textfield/textfield_views_model.h +++ b/views/controls/textfield/textfield_views_model.h @@ -143,9 +143,6 @@ class VIEWS_EXPORT TextfieldViewsModel { // 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; - // Selection related method // Returns the selected text. diff --git a/views/examples/textfield_example.cc b/views/examples/textfield_example.cc index bb1ab24..af0f2b9 100644 --- a/views/examples/textfield_example.cc +++ b/views/examples/textfield_example.cc @@ -90,22 +90,27 @@ 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); + if (!name_->text().empty()) { + gfx::StyleRange color; + color.foreground = SK_ColorYELLOW; + color.range = ui::Range(0, name_->text().length()); + name_->ApplyStyleRange(color); - gfx::StyleRange underline; - underline.underline = true; - underline.foreground = SK_ColorBLUE; - underline.range = ui::Range(1, 7); - name_->ApplyStyleRange(underline); + if (name_->text().length() >= 5) { + size_t fifth = name_->text().length() / 5; + gfx::StyleRange underline; + underline.underline = true; + underline.foreground = SK_ColorBLUE; + underline.range = ui::Range(1 * fifth, 4 * fifth); + name_->ApplyStyleRange(underline); - gfx::StyleRange strike; - strike.strike = true; - strike.foreground = SK_ColorRED; - strike.range = ui::Range(6, 9); - name_->ApplyStyleRange(strike); + gfx::StyleRange strike; + strike.strike = true; + strike.foreground = SK_ColorRED; + strike.range = ui::Range(2 * fifth, 3 * fifth); + name_->ApplyStyleRange(strike); + } + } } } |