Chromium Code Reviews| 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 PASSWORD_REPLACEMENT_CHAR = '*'; | |
|
msw
2011/12/03 00:22:40
Is this the correct style for a const char identif
benrg
2011/12/06 17:21:30
Done. (I misremembered the coding guidelines.)
| |
| 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::GetCensoredText() const { | |
| 146 string16 txt = text(); | |
| 147 if (password_) { | |
| 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? | |
| 152 std::fill(txt.begin(), txt.end(), PASSWORD_REPLACEMENT_CHAR); | |
| 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::SetIsPassword(bool password) { | |
| 163 password_ = password; | |
| 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 = GetCensoredText(); |
| 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 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 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() { | 378 int RenderText::GetStringWidth() { |
| 345 return default_style_.font.GetStringWidth(text()); | 379 return default_style_.font.GetStringWidth(text()); |
|
msw
2011/12/03 00:22:40
Shouldn't this use GetCensoredText()?
xji
2011/12/06 01:24:04
guess since it could be pure virtual, it does not
benrg
2011/12/06 17:21:30
This code is dead, see below.
| |
| 346 } | 380 } |
| 347 | 381 |
| 348 void RenderText::Draw(Canvas* canvas) { | 382 void RenderText::Draw(Canvas* canvas) { |
| 349 EnsureLayout(); | 383 EnsureLayout(); |
| 350 | 384 |
| 351 if (!text().empty()) { | 385 if (!text().empty()) { |
| 352 DrawSelection(canvas); | 386 DrawSelection(canvas); |
| 353 DrawVisualText(canvas); | 387 DrawVisualText(canvas); |
| 354 } | 388 } |
| 355 DrawCursor(canvas); | 389 DrawCursor(canvas); |
| 356 } | 390 } |
| 357 | 391 |
| 358 SelectionModel RenderText::FindCursorPosition(const Point& point) { | 392 SelectionModel RenderText::FindCursorPosition(const Point& point) { |
| 359 const Font& font = default_style_.font; | 393 const Font& font = default_style_.font; |
| 360 int left = 0; | 394 int left = 0; |
| 361 int left_pos = 0; | 395 int left_pos = 0; |
| 362 int right = font.GetStringWidth(text()); | 396 int right = font.GetStringWidth(text()); |
|
msw
2011/12/03 00:22:40
Shouldn't this use GetCensoredText()?
benrg
2011/12/06 17:21:30
This code is dead and seems unlikely to ever be re
| |
| 363 int right_pos = text().length(); | 397 int right_pos = text().length(); |
| 364 | 398 |
| 365 int x = point.x() - (display_rect_.x() + GetUpdatedDisplayOffset().x()); | 399 int x = point.x() - (display_rect_.x() + GetUpdatedDisplayOffset().x()); |
| 366 if (x <= left) return SelectionModel(left_pos); | 400 if (x <= left) return SelectionModel(left_pos); |
| 367 if (x >= right) return SelectionModel(right_pos); | 401 if (x >= right) return SelectionModel(right_pos); |
| 368 // binary searching the cursor position. | 402 // binary searching the cursor position. |
| 369 // TODO(oshima): use the center of character instead of edge. | 403 // TODO(oshima): use the center of character instead of edge. |
| 370 // Binary search may not work for language like Arabic. | 404 // Binary search may not work for language like Arabic. |
| 371 while (std::abs(static_cast<long>(right_pos - left_pos)) > 1) { | 405 while (std::abs(static_cast<long>(right_pos - left_pos)) > 1) { |
| 372 int pivot_pos = left_pos + (right_pos - left_pos) / 2; | 406 int pivot_pos = left_pos + (right_pos - left_pos) / 2; |
| 373 int pivot = font.GetStringWidth(text().substr(0, pivot_pos)); | 407 int pivot = font.GetStringWidth(text().substr(0, pivot_pos)); |
|
msw
2011/12/03 00:22:40
Shouldn't this use GetCensoredText()?
| |
| 374 if (pivot < x) { | 408 if (pivot < x) { |
| 375 left = pivot; | 409 left = pivot; |
| 376 left_pos = pivot_pos; | 410 left_pos = pivot_pos; |
| 377 } else if (pivot == x) { | 411 } else if (pivot == x) { |
| 378 return SelectionModel(pivot_pos); | 412 return SelectionModel(pivot_pos); |
| 379 } else { | 413 } else { |
| 380 right = pivot; | 414 right = pivot; |
| 381 right_pos = pivot_pos; | 415 right_pos = pivot_pos; |
| 382 } | 416 } |
| 383 } | 417 } |
| (...skipping 25 matching lines...) Expand all Loading... | |
| 409 | 443 |
| 410 RenderText::RenderText() | 444 RenderText::RenderText() |
| 411 : text_(), | 445 : text_(), |
| 412 selection_model_(), | 446 selection_model_(), |
| 413 cursor_bounds_(), | 447 cursor_bounds_(), |
| 414 cursor_visible_(false), | 448 cursor_visible_(false), |
| 415 insert_mode_(true), | 449 insert_mode_(true), |
| 416 composition_range_(ui::Range::InvalidRange()), | 450 composition_range_(ui::Range::InvalidRange()), |
| 417 style_ranges_(), | 451 style_ranges_(), |
| 418 default_style_(), | 452 default_style_(), |
| 453 password_(false), | |
| 419 display_rect_(), | 454 display_rect_(), |
| 420 display_offset_(), | 455 display_offset_(), |
| 421 cached_bounds_and_offset_valid_(false) { | 456 cached_bounds_and_offset_valid_(false) { |
| 422 } | 457 } |
| 423 | 458 |
| 424 const Point& RenderText::GetUpdatedDisplayOffset() { | 459 const Point& RenderText::GetUpdatedDisplayOffset() { |
| 425 UpdateCachedBoundsAndOffset(); | 460 UpdateCachedBoundsAndOffset(); |
| 426 return display_offset_; | 461 return display_offset_; |
| 427 } | 462 } |
| 428 | 463 |
| 429 SelectionModel RenderText::GetLeftSelectionModel(const SelectionModel& current, | 464 SelectionModel RenderText::GetLeftSelectionModel(const SelectionModel& current, |
| 430 BreakType break_type) { | 465 BreakType break_type) { |
| 431 if (break_type == LINE_BREAK) | 466 if (break_type == LINE_BREAK) |
| 432 return LeftEndSelectionModel(); | 467 return LeftEndSelectionModel(); |
| 433 size_t pos = std::max(static_cast<long>(current.selection_end() - 1), | 468 size_t pos = std::max(static_cast<long>(current.selection_end() - 1), |
| 434 static_cast<long>(0)); | 469 static_cast<long>(0)); |
| 435 if (break_type == CHARACTER_BREAK) | 470 if (break_type == CHARACTER_BREAK) |
| 436 return SelectionModel(pos, pos, SelectionModel::LEADING); | 471 return SelectionModel(pos, pos, SelectionModel::LEADING); |
| 437 | 472 |
| 438 // Notes: We always iterate words from the beginning. | 473 // Notes: We always iterate words from the beginning. |
| 439 // This is probably fast enough for our usage, but we may | 474 // This is probably fast enough for our usage, but we may |
| 440 // want to modify WordIterator so that it can start from the | 475 // want to modify WordIterator so that it can start from the |
| 441 // middle of string and advance backwards. | 476 // middle of string and advance backwards. |
| 442 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD); | 477 string16 txt = GetCensoredText(); |
| 478 base::i18n::BreakIterator iter(txt, base::i18n::BreakIterator::BREAK_WORD); | |
| 443 bool success = iter.Init(); | 479 bool success = iter.Init(); |
| 444 DCHECK(success); | 480 DCHECK(success); |
| 445 if (!success) | 481 if (!success) |
| 446 return current; | 482 return current; |
| 447 while (iter.Advance()) { | 483 while (iter.Advance()) { |
| 448 if (iter.IsWord()) { | 484 if (iter.IsWord()) { |
| 449 size_t begin = iter.pos() - iter.GetString().length(); | 485 size_t begin = iter.pos() - iter.GetString().length(); |
| 450 if (begin == current.selection_end()) { | 486 if (begin == current.selection_end()) { |
| 451 // The cursor is at the beginning of a word. | 487 // The cursor is at the beginning of a word. |
| 452 // Move to previous word. | 488 // Move to previous word. |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 468 SelectionModel RenderText::GetRightSelectionModel(const SelectionModel& current, | 504 SelectionModel RenderText::GetRightSelectionModel(const SelectionModel& current, |
| 469 BreakType break_type) { | 505 BreakType break_type) { |
| 470 if (text_.empty()) | 506 if (text_.empty()) |
| 471 return SelectionModel(0, 0, SelectionModel::LEADING); | 507 return SelectionModel(0, 0, SelectionModel::LEADING); |
| 472 if (break_type == LINE_BREAK) | 508 if (break_type == LINE_BREAK) |
| 473 return RightEndSelectionModel(); | 509 return RightEndSelectionModel(); |
| 474 size_t pos = std::min(current.selection_end() + 1, text().length()); | 510 size_t pos = std::min(current.selection_end() + 1, text().length()); |
| 475 if (break_type == CHARACTER_BREAK) | 511 if (break_type == CHARACTER_BREAK) |
| 476 return SelectionModel(pos, pos, SelectionModel::LEADING); | 512 return SelectionModel(pos, pos, SelectionModel::LEADING); |
| 477 | 513 |
| 478 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD); | 514 string16 txt = GetCensoredText(); |
| 515 base::i18n::BreakIterator iter(txt, base::i18n::BreakIterator::BREAK_WORD); | |
| 479 bool success = iter.Init(); | 516 bool success = iter.Init(); |
| 480 DCHECK(success); | 517 DCHECK(success); |
| 481 if (!success) | 518 if (!success) |
| 482 return current; | 519 return current; |
| 483 while (iter.Advance()) { | 520 while (iter.Advance()) { |
| 484 pos = iter.pos(); | 521 pos = iter.pos(); |
| 485 if (iter.IsWord() && pos > current.selection_end()) | 522 if (iter.IsWord() && pos > current.selection_end()) |
| 486 break; | 523 break; |
| 487 } | 524 } |
| 488 return SelectionModel(pos, pos, SelectionModel::LEADING); | 525 return SelectionModel(pos, pos, SelectionModel::LEADING); |
| (...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 611 void RenderText::DrawCursor(Canvas* canvas) { | 648 void RenderText::DrawCursor(Canvas* canvas) { |
| 612 // Paint cursor. Replace cursor is drawn as rectangle for now. | 649 // Paint cursor. Replace cursor is drawn as rectangle for now. |
| 613 // TODO(msw): Draw a better cursor with a better indication of association. | 650 // TODO(msw): Draw a better cursor with a better indication of association. |
| 614 if (cursor_visible() && focused()) { | 651 if (cursor_visible() && focused()) { |
| 615 Rect r(GetUpdatedCursorBounds()); | 652 Rect r(GetUpdatedCursorBounds()); |
| 616 canvas->DrawRectInt(kCursorColor, r.x(), r.y(), r.width(), r.height()); | 653 canvas->DrawRectInt(kCursorColor, r.x(), r.y(), r.width(), r.height()); |
| 617 } | 654 } |
| 618 } | 655 } |
| 619 | 656 |
| 620 } // namespace gfx | 657 } // namespace gfx |
| OLD | NEW |