OLD | NEW |
---|---|
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "ui/gfx/render_text.h" | 5 #include "ui/gfx/render_text.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 | 8 |
9 #include "base/i18n/break_iterator.h" | 9 #include "base/i18n/break_iterator.h" |
10 #include "base/logging.h" | 10 #include "base/logging.h" |
11 #include "base/stl_util.h" | 11 #include "base/stl_util.h" |
12 #include "ui/gfx/canvas.h" | 12 #include "ui/gfx/canvas.h" |
13 #include "ui/gfx/canvas_skia.h" | 13 #include "ui/gfx/canvas_skia.h" |
14 #include "unicode/uchar.h" | 14 #include "unicode/uchar.h" |
15 | 15 |
16 namespace { | 16 namespace { |
17 | 17 |
18 // All chars are replaced by this char when the password style is set. | |
19 // TODO(benrg): GTK uses the first of U+25CF, U+2022, U+2731, U+273A, '*' | |
20 // that's available in the font (find_invisible_char() in gtkentry.c). | |
21 const char16 kPasswordReplacementChar = '*'; | |
22 | |
23 // Color settings for text, backgrounds and cursor. | |
24 // These are tentative, and should be derived from theme, system | |
25 // settings and current settings. | |
26 // TODO(oshima): Change this to match the standard chrome | |
27 // before dogfooding textfield views. | |
28 const SkColor kSelectedTextColor = SK_ColorWHITE; | |
29 const SkColor kFocusedSelectionColor = SkColorSetRGB(30, 144, 255); | |
30 const SkColor kUnfocusedSelectionColor = SK_ColorLTGRAY; | |
31 const SkColor kCursorColor = SK_ColorBLACK; | |
32 | |
18 #ifndef NDEBUG | 33 #ifndef NDEBUG |
19 // Check StyleRanges invariant conditions: sorted and non-overlapping ranges. | 34 // Check StyleRanges invariant conditions: sorted and non-overlapping ranges. |
20 void CheckStyleRanges(const gfx::StyleRanges& style_ranges, size_t length) { | 35 void CheckStyleRanges(const gfx::StyleRanges& style_ranges, size_t length) { |
21 if (length == 0) { | 36 if (length == 0) { |
22 DCHECK(style_ranges.empty()) << "Style ranges exist for empty text."; | 37 DCHECK(style_ranges.empty()) << "Style ranges exist for empty text."; |
23 return; | 38 return; |
24 } | 39 } |
25 for (gfx::StyleRanges::size_type i = 0; i < style_ranges.size() - 1; i++) { | 40 for (gfx::StyleRanges::size_type i = 0; i < style_ranges.size() - 1; i++) { |
26 const ui::Range& former = style_ranges[i].range; | 41 const ui::Range& former = style_ranges[i].range; |
27 const ui::Range& latter = style_ranges[i + 1].range; | 42 const ui::Range& latter = style_ranges[i + 1].range; |
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
120 #endif | 135 #endif |
121 cached_bounds_and_offset_valid_ = false; | 136 cached_bounds_and_offset_valid_ = false; |
122 | 137 |
123 // Reset selection model. SetText should always followed by SetSelectionModel | 138 // Reset selection model. SetText should always followed by SetSelectionModel |
124 // or SetCursorPosition in upper layer. | 139 // or SetCursorPosition in upper layer. |
125 SetSelectionModel(SelectionModel(0, 0, SelectionModel::LEADING)); | 140 SetSelectionModel(SelectionModel(0, 0, SelectionModel::LEADING)); |
126 | 141 |
127 UpdateLayout(); | 142 UpdateLayout(); |
128 } | 143 } |
129 | 144 |
145 string16 RenderText::GetObscuredText() const { | |
146 string16 txt = text(); | |
147 if (obscured_) { | |
148 // TODO(benrg): There should probably be one bullet/asterisk per | |
149 // cursorable character, not one per UTF-16 word. However, I'm not sure | |
150 // it's worth the effort. GTK appears to use one per code point. Do people | |
151 // use non-BMP code points and combining diacritics in their passwords? | |
xji
2011/12/22 01:11:20
Ben and I just talked about how to map obscured te
benrg
2011/12/22 02:14:21
xji wrote:
| |
152 std::fill(txt.begin(), txt.end(), kPasswordReplacementChar); | |
153 } | |
154 return txt; | |
155 } | |
156 | |
130 void RenderText::ToggleInsertMode() { | 157 void RenderText::ToggleInsertMode() { |
131 insert_mode_ = !insert_mode_; | 158 insert_mode_ = !insert_mode_; |
132 cached_bounds_and_offset_valid_ = false; | 159 cached_bounds_and_offset_valid_ = false; |
133 } | 160 } |
134 | 161 |
162 void RenderText::SetObscured(bool obscured) { | |
163 obscured_ = obscured; | |
164 cached_bounds_and_offset_valid_ = false; | |
165 UpdateLayout(); | |
166 } | |
167 | |
135 void RenderText::SetDisplayRect(const Rect& r) { | 168 void RenderText::SetDisplayRect(const Rect& r) { |
136 display_rect_ = r; | 169 display_rect_ = r; |
137 cached_bounds_and_offset_valid_ = false; | 170 cached_bounds_and_offset_valid_ = false; |
138 UpdateLayout(); | 171 UpdateLayout(); |
139 } | 172 } |
140 | 173 |
141 size_t RenderText::GetCursorPosition() const { | 174 size_t RenderText::GetCursorPosition() const { |
142 return selection_model_.selection_end(); | 175 return selection_model_.selection_end(); |
143 } | 176 } |
144 | 177 |
(...skipping 121 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
266 | 299 |
267 void RenderText::SelectAll() { | 300 void RenderText::SelectAll() { |
268 SelectionModel sel(RightEndSelectionModel()); | 301 SelectionModel sel(RightEndSelectionModel()); |
269 sel.set_selection_start(LeftEndSelectionModel().selection_start()); | 302 sel.set_selection_start(LeftEndSelectionModel().selection_start()); |
270 SetSelectionModel(sel); | 303 SetSelectionModel(sel); |
271 } | 304 } |
272 | 305 |
273 void RenderText::SelectWord() { | 306 void RenderText::SelectWord() { |
274 size_t cursor_position = GetCursorPosition(); | 307 size_t cursor_position = GetCursorPosition(); |
275 | 308 |
276 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD); | 309 string16 txt = GetObscuredText(); |
310 base::i18n::BreakIterator iter(txt, base::i18n::BreakIterator::BREAK_WORD); | |
277 bool success = iter.Init(); | 311 bool success = iter.Init(); |
278 DCHECK(success); | 312 DCHECK(success); |
279 if (!success) | 313 if (!success) |
280 return; | 314 return; |
281 | 315 |
282 size_t selection_start = cursor_position; | 316 size_t selection_start = cursor_position; |
283 for (; selection_start != 0; --selection_start) { | 317 for (; selection_start != 0; --selection_start) { |
284 if (iter.IsStartOfWord(selection_start) || | 318 if (iter.IsStartOfWord(selection_start) || |
285 iter.IsEndOfWord(selection_start)) | 319 iter.IsEndOfWord(selection_start)) |
286 break; | 320 break; |
287 } | 321 } |
288 | 322 |
289 if (selection_start == cursor_position) | 323 if (selection_start == cursor_position) |
290 ++cursor_position; | 324 ++cursor_position; |
291 | 325 |
292 for (; cursor_position < text().length(); ++cursor_position) { | 326 for (; cursor_position < txt.length(); ++cursor_position) { |
293 if (iter.IsEndOfWord(cursor_position) || | 327 if (iter.IsEndOfWord(cursor_position) || |
294 iter.IsStartOfWord(cursor_position)) | 328 iter.IsStartOfWord(cursor_position)) |
295 break; | 329 break; |
296 } | 330 } |
297 | 331 |
298 MoveCursorTo(selection_start, false); | 332 MoveCursorTo(selection_start, false); |
299 MoveCursorTo(cursor_position, true); | 333 MoveCursorTo(cursor_position, true); |
300 } | 334 } |
301 | 335 |
302 const ui::Range& RenderText::GetCompositionRange() const { | 336 const ui::Range& RenderText::GetCompositionRange() const { |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
334 cached_bounds_and_offset_valid_ = false; | 368 cached_bounds_and_offset_valid_ = false; |
335 UpdateLayout(); | 369 UpdateLayout(); |
336 } | 370 } |
337 | 371 |
338 base::i18n::TextDirection RenderText::GetTextDirection() { | 372 base::i18n::TextDirection RenderText::GetTextDirection() { |
339 if (base::i18n::IsRTL()) | 373 if (base::i18n::IsRTL()) |
340 return base::i18n::RIGHT_TO_LEFT; | 374 return base::i18n::RIGHT_TO_LEFT; |
341 return base::i18n::LEFT_TO_RIGHT; | 375 return base::i18n::LEFT_TO_RIGHT; |
342 } | 376 } |
343 | 377 |
344 int RenderText::GetStringWidth() { | |
345 return default_style_.font.GetStringWidth(text()); | |
346 } | |
347 | |
348 void RenderText::Draw(Canvas* canvas) { | 378 void RenderText::Draw(Canvas* canvas) { |
349 EnsureLayout(); | 379 EnsureLayout(); |
350 | 380 |
351 if (!text().empty()) { | 381 if (!text().empty()) { |
352 DrawSelection(canvas); | 382 DrawSelection(canvas); |
353 DrawVisualText(canvas); | 383 DrawVisualText(canvas); |
354 } | 384 } |
355 DrawCursor(canvas); | 385 DrawCursor(canvas); |
356 } | 386 } |
357 | 387 |
358 SelectionModel RenderText::FindCursorPosition(const Point& point) { | |
359 const Font& font = default_style_.font; | |
360 int left = 0; | |
361 int left_pos = 0; | |
362 int right = font.GetStringWidth(text()); | |
363 int right_pos = text().length(); | |
364 | |
365 int x = point.x() - (display_rect_.x() + GetUpdatedDisplayOffset().x()); | |
366 if (x <= left) return SelectionModel(left_pos); | |
367 if (x >= right) return SelectionModel(right_pos); | |
368 // binary searching the cursor position. | |
369 // TODO(oshima): use the center of character instead of edge. | |
370 // Binary search may not work for language like Arabic. | |
371 while (std::abs(static_cast<long>(right_pos - left_pos)) > 1) { | |
372 int pivot_pos = left_pos + (right_pos - left_pos) / 2; | |
373 int pivot = font.GetStringWidth(text().substr(0, pivot_pos)); | |
374 if (pivot < x) { | |
375 left = pivot; | |
376 left_pos = pivot_pos; | |
377 } else if (pivot == x) { | |
378 return SelectionModel(pivot_pos); | |
379 } else { | |
380 right = pivot; | |
381 right_pos = pivot_pos; | |
382 } | |
383 } | |
384 return SelectionModel(left_pos); | |
385 } | |
386 | |
387 const Rect& RenderText::GetUpdatedCursorBounds() { | 388 const Rect& RenderText::GetUpdatedCursorBounds() { |
388 UpdateCachedBoundsAndOffset(); | 389 UpdateCachedBoundsAndOffset(); |
389 return cursor_bounds_; | 390 return cursor_bounds_; |
390 } | 391 } |
391 | 392 |
392 size_t RenderText::GetIndexOfNextGrapheme(size_t position) { | 393 size_t RenderText::GetIndexOfNextGrapheme(size_t position) { |
393 return IndexOfAdjacentGrapheme(position, true); | 394 return IndexOfAdjacentGrapheme(position, true); |
394 } | 395 } |
395 | 396 |
396 SelectionModel RenderText::GetSelectionModelForSelectionStart() { | 397 SelectionModel RenderText::GetSelectionModelForSelectionStart() { |
(...skipping 12 matching lines...) Expand all Loading... | |
409 | 410 |
410 RenderText::RenderText() | 411 RenderText::RenderText() |
411 : text_(), | 412 : text_(), |
412 selection_model_(), | 413 selection_model_(), |
413 cursor_bounds_(), | 414 cursor_bounds_(), |
414 cursor_visible_(false), | 415 cursor_visible_(false), |
415 insert_mode_(true), | 416 insert_mode_(true), |
416 composition_range_(ui::Range::InvalidRange()), | 417 composition_range_(ui::Range::InvalidRange()), |
417 style_ranges_(), | 418 style_ranges_(), |
418 default_style_(), | 419 default_style_(), |
420 obscured_(false), | |
419 display_rect_(), | 421 display_rect_(), |
420 display_offset_(), | 422 display_offset_(), |
421 cached_bounds_and_offset_valid_(false) { | 423 cached_bounds_and_offset_valid_(false) { |
422 } | 424 } |
423 | 425 |
424 const Point& RenderText::GetUpdatedDisplayOffset() { | 426 const Point& RenderText::GetUpdatedDisplayOffset() { |
425 UpdateCachedBoundsAndOffset(); | 427 UpdateCachedBoundsAndOffset(); |
426 return display_offset_; | 428 return display_offset_; |
427 } | 429 } |
428 | 430 |
429 SelectionModel RenderText::GetLeftSelectionModel(const SelectionModel& current, | |
430 BreakType break_type) { | |
431 if (break_type == LINE_BREAK) | |
432 return LeftEndSelectionModel(); | |
433 size_t pos = std::max(static_cast<long>(current.selection_end() - 1), | |
434 static_cast<long>(0)); | |
435 if (break_type == CHARACTER_BREAK) | |
436 return SelectionModel(pos, pos, SelectionModel::LEADING); | |
437 | |
438 // Notes: We always iterate words from the beginning. | |
439 // This is probably fast enough for our usage, but we may | |
440 // want to modify WordIterator so that it can start from the | |
441 // middle of string and advance backwards. | |
442 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD); | |
443 bool success = iter.Init(); | |
444 DCHECK(success); | |
445 if (!success) | |
446 return current; | |
447 while (iter.Advance()) { | |
448 if (iter.IsWord()) { | |
449 size_t begin = iter.pos() - iter.GetString().length(); | |
450 if (begin == current.selection_end()) { | |
451 // The cursor is at the beginning of a word. | |
452 // Move to previous word. | |
453 break; | |
454 } else if (iter.pos() >= current.selection_end()) { | |
455 // The cursor is in the middle or at the end of a word. | |
456 // Move to the top of current word. | |
457 pos = begin; | |
458 break; | |
459 } else { | |
460 pos = iter.pos() - iter.GetString().length(); | |
461 } | |
462 } | |
463 } | |
464 | |
465 return SelectionModel(pos, pos, SelectionModel::LEADING); | |
466 } | |
467 | |
468 SelectionModel RenderText::GetRightSelectionModel(const SelectionModel& current, | |
469 BreakType break_type) { | |
470 if (text_.empty()) | |
471 return SelectionModel(0, 0, SelectionModel::LEADING); | |
472 if (break_type == LINE_BREAK) | |
473 return RightEndSelectionModel(); | |
474 size_t pos = std::min(current.selection_end() + 1, text().length()); | |
475 if (break_type == CHARACTER_BREAK) | |
476 return SelectionModel(pos, pos, SelectionModel::LEADING); | |
477 | |
478 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD); | |
479 bool success = iter.Init(); | |
480 DCHECK(success); | |
481 if (!success) | |
482 return current; | |
483 while (iter.Advance()) { | |
484 pos = iter.pos(); | |
485 if (iter.IsWord() && pos > current.selection_end()) | |
486 break; | |
487 } | |
488 return SelectionModel(pos, pos, SelectionModel::LEADING); | |
489 } | |
490 | |
491 SelectionModel RenderText::LeftEndSelectionModel() { | |
492 return SelectionModel(0, 0, SelectionModel::LEADING); | |
493 } | |
494 | |
495 SelectionModel RenderText::RightEndSelectionModel() { | |
496 size_t cursor = text().length(); | |
497 size_t caret_pos = GetIndexOfPreviousGrapheme(cursor); | |
498 SelectionModel::CaretPlacement placement = (caret_pos == cursor) ? | |
499 SelectionModel::LEADING : SelectionModel::TRAILING; | |
500 return SelectionModel(cursor, caret_pos, placement); | |
501 } | |
502 | |
503 void RenderText::SetSelectionModel(const SelectionModel& model) { | 431 void RenderText::SetSelectionModel(const SelectionModel& model) { |
504 DCHECK_LE(model.selection_start(), text().length()); | 432 DCHECK_LE(model.selection_start(), text().length()); |
505 selection_model_.set_selection_start(model.selection_start()); | 433 selection_model_.set_selection_start(model.selection_start()); |
506 DCHECK_LE(model.selection_end(), text().length()); | 434 DCHECK_LE(model.selection_end(), text().length()); |
507 selection_model_.set_selection_end(model.selection_end()); | 435 selection_model_.set_selection_end(model.selection_end()); |
508 DCHECK_LT(model.caret_pos(), | 436 DCHECK_LT(model.caret_pos(), |
509 std::max(text().length(), static_cast<size_t>(1))); | 437 std::max(text().length(), static_cast<size_t>(1))); |
510 selection_model_.set_caret_pos(model.caret_pos()); | 438 selection_model_.set_caret_pos(model.caret_pos()); |
511 selection_model_.set_caret_placement(model.caret_placement()); | 439 selection_model_.set_caret_placement(model.caret_placement()); |
512 | 440 |
(...skipping 99 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
612 void RenderText::DrawCursor(Canvas* canvas) { | 540 void RenderText::DrawCursor(Canvas* canvas) { |
613 // Paint cursor. Replace cursor is drawn as rectangle for now. | 541 // Paint cursor. Replace cursor is drawn as rectangle for now. |
614 // TODO(msw): Draw a better cursor with a better indication of association. | 542 // TODO(msw): Draw a better cursor with a better indication of association. |
615 if (cursor_visible() && focused()) { | 543 if (cursor_visible() && focused()) { |
616 Rect r(GetUpdatedCursorBounds()); | 544 Rect r(GetUpdatedCursorBounds()); |
617 canvas->DrawRectInt(kCursorColor, r.x(), r.y(), r.width(), r.height()); | 545 canvas->DrawRectInt(kCursorColor, r.x(), r.y(), r.width(), r.height()); |
618 } | 546 } |
619 } | 547 } |
620 | 548 |
621 } // namespace gfx | 549 } // namespace gfx |
OLD | NEW |