Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "ui/gfx/render_text.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 | |
| 9 #include "base/logging.h" | |
| 10 #include "base/stl_util-inl.h" | |
| 11 | |
| 12 namespace { | |
| 13 | |
| 14 #ifndef NDEBUG | |
| 15 // Check StyleRanges invariant conditions: sorted and non-overlapping ranges. | |
| 16 void CheckStyleRanges(const gfx::StyleRanges& style_ranges, size_t length) { | |
| 17 if (length == 0) { | |
| 18 DCHECK(style_ranges.empty()) << "Style ranges exist for empty text."; | |
| 19 return; | |
| 20 } | |
| 21 for (gfx::StyleRanges::size_type i = 0; i < style_ranges.size() - 1; i++) { | |
| 22 ui::Range& former = style_ranges[i]->range; | |
| 23 ui::Range& latter = style_ranges[i + 1]->range; | |
| 24 DCHECK(!former.is_empty()) << "Empty range at " << i << ":" << former; | |
| 25 DCHECK(former.IsValid()) << "Invalid range at " << i << ":" << former; | |
| 26 DCHECK(!former.is_reversed()) << "Reversed range at " << i << ":" << former; | |
| 27 DCHECK(former.end() == latter.start()) << "Ranges gap/overlap/unsorted." << | |
| 28 "former:" << former << ", latter:" << latter; | |
| 29 } | |
| 30 const gfx::StyleRange* end_style = *style_ranges.rbegin(); | |
| 31 DCHECK(!end_style->range.is_empty()) << "Empty range at end."; | |
| 32 DCHECK(end_style->range.IsValid()) << "Invalid range at end."; | |
| 33 DCHECK(!end_style->range.is_reversed()) << "Reversed range at end."; | |
| 34 DCHECK(end_style->range.end() == length) << "Style and text length mismatch."; | |
| 35 } | |
| 36 #endif | |
| 37 | |
| 38 } // namespace | |
| 39 | |
| 40 namespace gfx { | |
| 41 | |
| 42 void RenderText::SetText(const string16& text) { | |
| 43 // TODO(msw): Allow text and styles to get out of sync? Repair on draw? | |
| 44 // Update the style ranges as needed. | |
| 45 if (text.empty()) { | |
| 46 style_ranges_.clear(); | |
| 47 } else if (style_ranges_.empty()) { | |
| 48 StyleRange* style = new StyleRange(); | |
| 49 style->font = default_font_; | |
| 50 style->foreground = default_color_; | |
| 51 style->range.set_end(text.length()); | |
| 52 style_ranges_.push_back(style); | |
| 53 } else if (text.length() > text_.length()) { | |
| 54 style_ranges_.back()->range.set_end(text.length()); | |
| 55 } else if (text.length() < text_.length()) { | |
| 56 StyleRanges::const_iterator i; | |
| 57 for (i = style_ranges_.begin(); i != style_ranges_.end(); i++) { | |
| 58 StyleRange* style = *i; | |
| 59 if (style->range.start() > text.length()) { | |
| 60 style_ranges_.erase(i); | |
| 61 delete style; | |
| 62 } else if (style->range.end() > text.length()) { | |
| 63 style->range.set_end(text.length()); | |
| 64 } | |
| 65 } | |
| 66 } | |
| 67 #ifndef NDEBUG | |
| 68 CheckStyleRanges(style_ranges_, text.length()); | |
| 69 #endif | |
| 70 | |
| 71 text_ = text; | |
| 72 // TODO(msw): Mark dirty text flag. | |
| 73 } | |
| 74 | |
| 75 size_t RenderText::GetCursor() const { | |
| 76 return selection_range_.end(); | |
| 77 } | |
| 78 | |
| 79 void RenderText::SetCursor(const size_t position) { | |
| 80 selection_range_.set_end(position); | |
| 81 selection_range_.set_start(position); | |
| 82 } | |
| 83 | |
| 84 void RenderText::MoveCursorLeft(bool select, bool move_by_word) { | |
| 85 size_t position = selection_range_.end(); | |
| 86 // Cancelling a selection moves to the edge of the selection. | |
| 87 if (!selection_range_.is_empty() && !select) { | |
| 88 // Use the selection start if it is left of the selection end. | |
| 89 if (GetCursorBounds(selection_range_.start(), false).x() < | |
| 90 GetCursorBounds(position, false).x()) | |
| 91 position = selection_range_.start(); | |
| 92 // if |move_by_word|, use the nearest word boundary left of the selection. | |
| 93 if (move_by_word) | |
| 94 position = GetLeftCursorPosition(position, true); | |
| 95 } else { | |
| 96 position = GetLeftCursorPosition(position, move_by_word); | |
| 97 } | |
| 98 MoveCursorTo(position, select); | |
| 99 } | |
| 100 | |
| 101 void RenderText::MoveCursorRight(bool select, bool move_by_word) { | |
| 102 size_t position = selection_range_.end(); | |
| 103 // Cancelling a selection moves to the edge of the selection. | |
| 104 if (!selection_range_.is_empty() && !select) { | |
| 105 // Use the selection start if it is right of the selection end. | |
| 106 if (GetCursorBounds(selection_range_.start(), false).x() > | |
| 107 GetCursorBounds(position, false).x()) | |
| 108 position = selection_range_.start(); | |
| 109 // if |move_by_word|, use the nearest word boundary right of the selection. | |
| 110 if (move_by_word) | |
| 111 position = GetRightCursorPosition(position, true); | |
| 112 } else { | |
| 113 position = GetRightCursorPosition(position, move_by_word); | |
| 114 } | |
| 115 MoveCursorTo(position, select); | |
| 116 } | |
| 117 | |
| 118 void RenderText::MoveCursorToLeftEnd(bool select) { | |
| 119 // TODO(msw) Bidi. | |
| 120 MoveCursorTo(0, select); | |
| 121 } | |
| 122 | |
| 123 void RenderText::MoveCursorToRightEnd(bool select) { | |
| 124 // TODO(msw) Bidi. | |
| 125 MoveCursorTo(text().length(), select); | |
| 126 } | |
| 127 | |
| 128 void RenderText::MoveCursorTo(size_t position, bool select) { | |
| 129 selection_range_.set_end(position); | |
| 130 if (!select) | |
| 131 selection_range_.set_start(position); | |
| 132 } | |
| 133 | |
| 134 const ui::Range& RenderText::GetSelection() const { | |
| 135 return selection_range_; | |
| 136 } | |
| 137 | |
| 138 void RenderText::SetSelection(const ui::Range& selection_range) { | |
| 139 selection_range_.set_end(selection_range.end()); | |
| 140 selection_range_.set_start(selection_range.start()); | |
| 141 } | |
| 142 | |
| 143 void RenderText::ClearSelection() { | |
| 144 selection_range_.set_start(GetCursor()); | |
| 145 } | |
| 146 | |
| 147 void RenderText::SelectAll() { | |
| 148 SetSelection(ui::Range(0, text().length())); | |
| 149 } | |
| 150 | |
| 151 void RenderText::SelectWord() { | |
| 152 // TODO(msw): Bidi impl? | |
| 153 size_t selection_start = GetSelection().start(); | |
| 154 size_t cursor_position = GetCursor(); | |
| 155 // First we setup selection_start_ and cursor_pos_. There are so many cases | |
| 156 // because we try to emulate what select-word looks like in a gtk textfield. | |
| 157 // See associated testcase for different cases. | |
| 158 if (cursor_position > 0 && cursor_position < text().length()) { | |
| 159 if (isalnum(text()[cursor_position])) { | |
| 160 selection_start = cursor_position; | |
| 161 cursor_position++; | |
| 162 } else | |
| 163 selection_start = cursor_position - 1; | |
| 164 } else if (cursor_position == 0) { | |
| 165 selection_start = cursor_position; | |
| 166 if (text().length() > 0) | |
| 167 cursor_position++; | |
| 168 } else { | |
| 169 selection_start = cursor_position - 1; | |
| 170 } | |
| 171 | |
| 172 // Now we move selection_start_ to beginning of selection. Selection boundary | |
| 173 // is defined as the position where we have alpha-num character on one side | |
| 174 // and non-alpha-num char on the other side. | |
| 175 for (; selection_start > 0; selection_start--) { | |
| 176 if (IsPositionAtWordSelectionBoundary(selection_start)) | |
| 177 break; | |
| 178 } | |
| 179 | |
| 180 // Now we move cursor_pos_ to end of selection. Selection boundary | |
| 181 // is defined as the position where we have alpha-num character on one side | |
| 182 // and non-alpha-num char on the other side. | |
| 183 for (; cursor_position < text().length(); cursor_position++) { | |
| 184 if (IsPositionAtWordSelectionBoundary(cursor_position)) | |
| 185 break; | |
| 186 } | |
| 187 | |
| 188 SetSelection(ui::Range(selection_start, cursor_position)); | |
| 189 } | |
| 190 | |
| 191 const ui::Range& RenderText::GetComposition() const { | |
| 192 return composition_range_; | |
| 193 } | |
| 194 | |
| 195 void RenderText::SetComposition(const ui::Range& composition_range) | |
| 196 { | |
| 197 composition_range_.set_end(composition_range.end()); | |
| 198 composition_range_.set_start(composition_range.start()); | |
| 199 } | |
| 200 | |
| 201 const StyleRanges& RenderText::GetStyleRanges() const { | |
| 202 return style_ranges_; | |
| 203 } | |
| 204 | |
| 205 // TODO(msw): Enforce style ranges to exactly cover the text? | |
| 206 // Allow 'default' style? Mismatching length from text? | |
| 207 void RenderText::ApplyStyleRange(StyleRange* style_range) { | |
| 208 const ui::Range& new_range = style_range->range; | |
| 209 CHECK(new_range.IsValid()); | |
| 210 CHECK(!new_range.is_empty()); | |
| 211 CHECK(!new_range.is_reversed()); | |
| 212 | |
| 213 // Follow StyleRanges invariant conditions: sorted and non-overlapping ranges. | |
| 214 StyleRanges::const_iterator i; | |
| 215 for (i = style_ranges_.begin(); i != style_ranges_.end(); i++) { | |
| 216 StyleRange* style = *i; | |
| 217 if (style->range.start() >= new_range.end()) | |
| 218 break; | |
| 219 if (new_range.Contains(style->range)) { | |
| 220 style_ranges_.erase(i); | |
| 221 delete style; | |
| 222 } else if (style->range.start() < new_range.start() && | |
| 223 style->range.end() > new_range.end()) { | |
| 224 // Split the current style into two styles. | |
| 225 StyleRange* split_style = new StyleRange(*style); | |
| 226 split_style->range.set_end(new_range.start()); | |
| 227 style_ranges_.insert(i, split_style); | |
| 228 style->range.set_start(new_range.end()); | |
| 229 break; | |
| 230 } else if (style->range.start() < new_range.start()) { | |
| 231 style->range.set_end(std::min(style->range.end(), new_range.start())); | |
| 232 } else { | |
| 233 style->range.set_start(new_range.end()); | |
| 234 break; | |
| 235 } | |
| 236 } | |
| 237 // Add the new range in its sorted location. | |
| 238 style_ranges_.insert(i, style_range); | |
| 239 #ifndef NDEBUG | |
| 240 CheckStyleRanges(style_ranges_, text_.length()); | |
| 241 #endif | |
| 242 } | |
| 243 | |
| 244 RenderText::RenderText() | |
| 245 : text_(), | |
| 246 selection_range_(), | |
| 247 is_cursor_visible_(false), | |
| 248 composition_range_(), | |
| 249 style_ranges_(), | |
| 250 display_rect_(), | |
| 251 render_offset_(), | |
| 252 default_font_(), | |
| 253 default_color_(), | |
| 254 // TODO(msw): Needed? | |
| 255 //cursor_bounds_(), | |
| 256 //selection_bounds_(), | |
| 257 flags_() { | |
| 258 // TODO(msw): default font... | |
| 259 //style_ranges_.push_back(new StyleRange(font, ui::Range())); | |
| 260 } | |
| 261 | |
| 262 RenderText::RenderText(const string16& text, | |
| 263 const gfx::Font& font, | |
| 264 const SkColor& color, | |
| 265 const gfx::Rect& display_rect, | |
| 266 int flags) | |
| 267 : text_(text), | |
| 268 selection_range_(), | |
| 269 is_cursor_visible_(false), | |
| 270 composition_range_(), | |
| 271 style_ranges_(), | |
| 272 display_rect_(display_rect), | |
| 273 render_offset_(), | |
| 274 default_font_(font), | |
| 275 default_color_(color), | |
| 276 // TODO(msw): Needed? | |
| 277 //cursor_bounds_(), | |
| 278 //selection_bounds_(), | |
| 279 flags_(flags) { | |
| 280 // TODO(msw): StyleRange ctor? | |
| 281 //style_ranges_.push_back(new StyleRange(font, ui::Range(0, text_.length()))); | |
| 282 } | |
| 283 | |
| 284 RenderText::~RenderText() { | |
| 285 STLDeleteContainerPointers(style_ranges_.begin(), style_ranges_.end()); | |
| 286 } | |
| 287 | |
| 288 bool RenderText::IsPositionAtWordSelectionBoundary(size_t pos) { | |
| 289 // 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.
| |
| 290 return (isalnum(text()[pos - 1]) && !isalnum(text()[pos])) || | |
| 291 (!isalnum(text()[pos - 1]) && isalnum(text()[pos])); | |
| 292 } | |
| 293 | |
| 294 } // namespace gfx | |
| OLD | NEW |