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 |