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 |