Chromium Code Reviews| Index: ui/gfx/render_text.cc |
| diff --git a/ui/gfx/render_text.cc b/ui/gfx/render_text.cc |
| new file mode 100755 |
| index 0000000000000000000000000000000000000000..594ee75d8a57640fa46286e985d62949e2c9f99f |
| --- /dev/null |
| +++ b/ui/gfx/render_text.cc |
| @@ -0,0 +1,294 @@ |
| +// 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/logging.h" |
| +#include "base/stl_util-inl.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++) { |
| + ui::Range& former = style_ranges[i]->range; |
| + 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 |
| + |
| +} // namespace |
| + |
| +namespace gfx { |
| + |
| +void RenderText::SetText(const string16& text) { |
| + // TODO(msw): Allow text and styles to get out of sync? Repair on draw? |
| + // Update the style ranges as needed. |
| + if (text.empty()) { |
| + style_ranges_.clear(); |
| + } else if (style_ranges_.empty()) { |
| + StyleRange* style = new StyleRange(); |
| + style->font = default_font_; |
| + style->foreground = default_color_; |
| + style->range.set_end(text.length()); |
| + style_ranges_.push_back(style); |
| + } else if (text.length() > text_.length()) { |
| + style_ranges_.back()->range.set_end(text.length()); |
| + } else if (text.length() < text_.length()) { |
| + StyleRanges::const_iterator i; |
| + for (i = style_ranges_.begin(); i != style_ranges_.end(); i++) { |
| + StyleRange* style = *i; |
| + if (style->range.start() > text.length()) { |
| + style_ranges_.erase(i); |
| + delete style; |
| + } else if (style->range.end() > text.length()) { |
| + style->range.set_end(text.length()); |
| + } |
| + } |
| + } |
| +#ifndef NDEBUG |
| + CheckStyleRanges(style_ranges_, text.length()); |
| +#endif |
| + |
| + text_ = text; |
| + // TODO(msw): Mark dirty text flag. |
| +} |
| + |
| +size_t RenderText::GetCursor() const { |
| + return selection_range_.end(); |
| +} |
| + |
| +void RenderText::SetCursor(const size_t position) { |
| + selection_range_.set_end(position); |
| + selection_range_.set_start(position); |
| +} |
| + |
| +void RenderText::MoveCursorLeft(bool select, bool move_by_word) { |
| + size_t position = selection_range_.end(); |
| + // Cancelling a selection moves to the edge of the selection. |
| + if (!selection_range_.is_empty() && !select) { |
| + // Use the selection start if it is left of the selection end. |
| + if (GetCursorBounds(selection_range_.start(), false).x() < |
| + GetCursorBounds(position, false).x()) |
| + position = selection_range_.start(); |
| + // if |move_by_word|, use the nearest word boundary left of the selection. |
| + if (move_by_word) |
| + position = GetLeftCursorPosition(position, true); |
| + } else { |
| + position = GetLeftCursorPosition(position, move_by_word); |
| + } |
| + MoveCursorTo(position, select); |
| +} |
| + |
| +void RenderText::MoveCursorRight(bool select, bool move_by_word) { |
| + size_t position = selection_range_.end(); |
| + // Cancelling a selection moves to the edge of the selection. |
| + if (!selection_range_.is_empty() && !select) { |
| + // Use the selection start if it is right of the selection end. |
| + if (GetCursorBounds(selection_range_.start(), false).x() > |
| + GetCursorBounds(position, false).x()) |
| + position = selection_range_.start(); |
| + // if |move_by_word|, use the nearest word boundary right of the selection. |
| + if (move_by_word) |
| + position = GetRightCursorPosition(position, true); |
| + } else { |
| + position = GetRightCursorPosition(position, move_by_word); |
| + } |
| + MoveCursorTo(position, select); |
| +} |
| + |
| +void RenderText::MoveCursorToLeftEnd(bool select) { |
| + // TODO(msw) Bidi. |
| + MoveCursorTo(0, select); |
| +} |
| + |
| +void RenderText::MoveCursorToRightEnd(bool select) { |
| + // TODO(msw) Bidi. |
| + MoveCursorTo(text().length(), select); |
| +} |
| + |
| +void RenderText::MoveCursorTo(size_t position, bool select) { |
| + selection_range_.set_end(position); |
| + if (!select) |
| + selection_range_.set_start(position); |
| +} |
| + |
| +const ui::Range& RenderText::GetSelection() const { |
| + return selection_range_; |
| +} |
| + |
| +void RenderText::SetSelection(const ui::Range& selection_range) { |
| + selection_range_.set_end(selection_range.end()); |
| + selection_range_.set_start(selection_range.start()); |
| +} |
| + |
| +void RenderText::ClearSelection() { |
| + selection_range_.set_start(GetCursor()); |
| +} |
| + |
| +void RenderText::SelectAll() { |
| + SetSelection(ui::Range(0, text().length())); |
| +} |
| + |
| +void RenderText::SelectWord() { |
| + // TODO(msw): Bidi impl? |
| + size_t selection_start = GetSelection().start(); |
| + size_t cursor_position = GetCursor(); |
| + // 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::GetComposition() const { |
| + return composition_range_; |
| +} |
| + |
| +void RenderText::SetComposition(const ui::Range& composition_range) |
| +{ |
| + composition_range_.set_end(composition_range.end()); |
| + composition_range_.set_start(composition_range.start()); |
| +} |
| + |
| +const StyleRanges& RenderText::GetStyleRanges() const { |
| + return style_ranges_; |
| +} |
| + |
| +// TODO(msw): Enforce style ranges to exactly cover the text? |
| +// Allow 'default' style? Mismatching length from text? |
| +void RenderText::ApplyStyleRange(StyleRange* style_range) { |
| + const ui::Range& new_range = style_range->range; |
| + CHECK(new_range.IsValid()); |
| + CHECK(!new_range.is_empty()); |
| + CHECK(!new_range.is_reversed()); |
| + |
| + // Follow StyleRanges invariant conditions: sorted and non-overlapping ranges. |
| + StyleRanges::const_iterator i; |
| + for (i = style_ranges_.begin(); i != style_ranges_.end(); i++) { |
| + StyleRange* style = *i; |
| + if (style->range.start() >= new_range.end()) |
| + break; |
| + if (new_range.Contains(style->range)) { |
| + style_ranges_.erase(i); |
| + delete style; |
| + } else if (style->range.start() < new_range.start() && |
| + style->range.end() > new_range.end()) { |
| + // Split the current style into two styles. |
| + StyleRange* split_style = new StyleRange(*style); |
| + split_style->range.set_end(new_range.start()); |
| + style_ranges_.insert(i, split_style); |
| + style->range.set_start(new_range.end()); |
| + break; |
| + } else if (style->range.start() < new_range.start()) { |
| + style->range.set_end(std::min(style->range.end(), new_range.start())); |
| + } else { |
| + style->range.set_start(new_range.end()); |
| + break; |
| + } |
| + } |
| + // Add the new range in its sorted location. |
| + style_ranges_.insert(i, style_range); |
| +#ifndef NDEBUG |
| + CheckStyleRanges(style_ranges_, text_.length()); |
| +#endif |
| +} |
| + |
| +RenderText::RenderText() |
| + : text_(), |
| + selection_range_(), |
| + is_cursor_visible_(false), |
| + composition_range_(), |
| + style_ranges_(), |
| + display_rect_(), |
| + render_offset_(), |
| + default_font_(), |
| + default_color_(), |
| + // TODO(msw): Needed? |
| + //cursor_bounds_(), |
| + //selection_bounds_(), |
| + flags_() { |
| + // TODO(msw): default font... |
| + //style_ranges_.push_back(new StyleRange(font, ui::Range())); |
| +} |
| + |
| +RenderText::RenderText(const string16& text, |
| + const gfx::Font& font, |
| + const SkColor& color, |
| + const gfx::Rect& display_rect, |
| + int flags) |
| + : text_(text), |
| + selection_range_(), |
| + is_cursor_visible_(false), |
| + composition_range_(), |
| + style_ranges_(), |
| + display_rect_(display_rect), |
| + render_offset_(), |
| + default_font_(font), |
| + default_color_(color), |
| + // TODO(msw): Needed? |
| + //cursor_bounds_(), |
| + //selection_bounds_(), |
| + flags_(flags) { |
| + // TODO(msw): StyleRange ctor? |
| + //style_ranges_.push_back(new StyleRange(font, ui::Range(0, text_.length()))); |
| +} |
| + |
| +RenderText::~RenderText() { |
| + STLDeleteContainerPointers(style_ranges_.begin(), style_ranges_.end()); |
| +} |
| + |
| +bool RenderText::IsPositionAtWordSelectionBoundary(size_t pos) { |
| + // TODO(msw): Doesn't this crash with pos == 0? |
|
xji
2011/07/01 18:29:44
even if it does not crash, it definitely access ou
msw
2011/07/01 21:52:15
Done.
|
| + return (isalnum(text()[pos - 1]) && !isalnum(text()[pos])) || |
| + (!isalnum(text()[pos - 1]) && isalnum(text()[pos])); |
| +} |
| + |
| +} // namespace gfx |