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" |
(...skipping 229 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
240 // The current model only supports caret positions at valid character indices. | 240 // The current model only supports caret positions at valid character indices. |
241 if (text_length == 0) { | 241 if (text_length == 0) { |
242 sel.set_caret_pos(0); | 242 sel.set_caret_pos(0); |
243 sel.set_caret_placement(SelectionModel::LEADING); | 243 sel.set_caret_placement(SelectionModel::LEADING); |
244 } else if (sel.caret_pos() >= text_length) { | 244 } else if (sel.caret_pos() >= text_length) { |
245 SelectionModel end = GetTextDirection() == base::i18n::RIGHT_TO_LEFT ? | 245 SelectionModel end = GetTextDirection() == base::i18n::RIGHT_TO_LEFT ? |
246 LeftEndSelectionModel() : RightEndSelectionModel(); | 246 LeftEndSelectionModel() : RightEndSelectionModel(); |
247 sel.set_caret_pos(end.caret_pos()); | 247 sel.set_caret_pos(end.caret_pos()); |
248 sel.set_caret_placement(end.caret_placement()); | 248 sel.set_caret_placement(end.caret_placement()); |
249 } | 249 } |
250 | |
251 if (!IsCursorablePosition(sel.selection_start()) || | |
252 !IsCursorablePosition(sel.selection_end()) || | |
253 !IsCursorablePosition(sel.caret_pos())) | |
254 return false; | |
255 | |
250 bool changed = !sel.Equals(selection_model_); | 256 bool changed = !sel.Equals(selection_model_); |
251 SetSelectionModel(sel); | 257 SetSelectionModel(sel); |
252 return changed; | 258 return changed; |
253 } | 259 } |
254 | 260 |
255 bool RenderText::MoveCursorTo(const Point& point, bool select) { | 261 bool RenderText::MoveCursorTo(const Point& point, bool select) { |
256 SelectionModel selection = FindCursorPosition(point); | 262 SelectionModel selection = FindCursorPosition(point); |
257 if (select) | 263 if (select) |
258 selection.set_selection_start(GetSelectionStart()); | 264 selection.set_selection_start(GetSelectionStart()); |
259 return MoveCursorTo(selection); | 265 return MoveCursorTo(selection); |
(...skipping 14 matching lines...) Expand all Loading... | |
274 sel.set_selection_start(GetCursorPosition()); | 280 sel.set_selection_start(GetCursorPosition()); |
275 SetSelectionModel(sel); | 281 SetSelectionModel(sel); |
276 } | 282 } |
277 | 283 |
278 void RenderText::SelectAll() { | 284 void RenderText::SelectAll() { |
279 SelectionModel sel(RightEndSelectionModel()); | 285 SelectionModel sel(RightEndSelectionModel()); |
280 sel.set_selection_start(LeftEndSelectionModel().selection_start()); | 286 sel.set_selection_start(LeftEndSelectionModel().selection_start()); |
281 SetSelectionModel(sel); | 287 SetSelectionModel(sel); |
282 } | 288 } |
283 | 289 |
284 // TODO(xji): it does not work for languages do not use space as word breaker, | |
285 // such as Chinese. Should use BreakIterator. | |
286 void RenderText::SelectWord() { | 290 void RenderText::SelectWord() { |
287 size_t selection_start = GetSelectionStart(); | |
288 size_t cursor_position = GetCursorPosition(); | 291 size_t cursor_position = GetCursorPosition(); |
289 // First we setup selection_start_ and selection_end_. There are so many cases | |
290 // because we try to emulate what select-word looks like in a gtk textfield. | |
291 // See associated testcase for different cases. | |
292 if (cursor_position > 0 && cursor_position < text().length()) { | |
293 if (u_isalnum(text()[cursor_position])) { | |
294 selection_start = cursor_position; | |
295 cursor_position++; | |
296 } else | |
297 selection_start = cursor_position - 1; | |
298 } else if (cursor_position == 0) { | |
299 selection_start = cursor_position; | |
300 if (text().length() > 0) | |
301 cursor_position++; | |
302 } else { | |
303 selection_start = cursor_position - 1; | |
304 } | |
305 | 292 |
306 // Now we move selection_start_ to beginning of selection. Selection boundary | 293 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD); |
307 // is defined as the position where we have alpha-num character on one side | 294 bool success = iter.Init(); |
308 // and non-alpha-num char on the other side. | 295 DCHECK(success); |
309 for (; selection_start > 0; selection_start--) { | 296 if (!success) |
310 if (IsPositionAtWordSelectionBoundary(selection_start)) | 297 return; |
298 | |
299 size_t selection_start = cursor_position; | |
300 for (; selection_start != 0; --selection_start) { | |
301 if (iter.IsStartOfWord(selection_start) || | |
302 iter.IsEndOfWord(selection_start)) | |
311 break; | 303 break; |
312 } | 304 } |
313 | 305 |
314 // Now we move selection_end_ to end of selection. Selection boundary | 306 if (selection_start == cursor_position) |
315 // is defined as the position where we have alpha-num character on one side | 307 ++cursor_position; |
316 // and non-alpha-num char on the other side. | 308 |
317 for (; cursor_position < text().length(); cursor_position++) { | 309 for (; cursor_position < text().length(); ++cursor_position) { |
318 if (IsPositionAtWordSelectionBoundary(cursor_position)) | 310 if (iter.IsEndOfWord(cursor_position) || |
311 iter.IsStartOfWord(cursor_position)) | |
319 break; | 312 break; |
320 } | 313 } |
321 | 314 |
322 MoveCursorTo(selection_start, false); | 315 MoveCursorTo(selection_start, false); |
323 MoveCursorTo(cursor_position, true); | 316 MoveCursorTo(cursor_position, true); |
324 } | 317 } |
325 | 318 |
326 const ui::Range& RenderText::GetCompositionRange() const { | 319 const ui::Range& RenderText::GetCompositionRange() const { |
327 return composition_range_; | 320 return composition_range_; |
328 } | 321 } |
(...skipping 126 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
455 size_t from = selection.selection_end(); | 448 size_t from = selection.selection_end(); |
456 size_t to = insert_mode ? from : std::min(text_.length(), from + 1); | 449 size_t to = insert_mode ? from : std::min(text_.length(), from + 1); |
457 return GetSubstringBounds(from, to)[0]; | 450 return GetSubstringBounds(from, to)[0]; |
458 } | 451 } |
459 | 452 |
460 const Rect& RenderText::GetUpdatedCursorBounds() { | 453 const Rect& RenderText::GetUpdatedCursorBounds() { |
461 UpdateCachedBoundsAndOffset(); | 454 UpdateCachedBoundsAndOffset(); |
462 return cursor_bounds_; | 455 return cursor_bounds_; |
463 } | 456 } |
464 | 457 |
458 size_t RenderText::GetIndexOfNextGrapheme(size_t position) { | |
459 // TODO(xji): Handle complex script. | |
460 return std::min(static_cast<long>(position + 1), | |
msw
2011/09/14 02:43:48
Both of these static casts have lint errors:
"Use
xji
2011/09/15 23:38:13
Merged win/linux impls.
| |
461 static_cast<long>(text_.length())); | |
462 } | |
463 | |
465 RenderText::RenderText() | 464 RenderText::RenderText() |
466 : text_(), | 465 : text_(), |
467 selection_model_(), | 466 selection_model_(), |
468 cursor_bounds_(), | 467 cursor_bounds_(), |
469 cursor_visible_(false), | 468 cursor_visible_(false), |
470 insert_mode_(true), | 469 insert_mode_(true), |
471 composition_range_(ui::Range::InvalidRange()), | 470 composition_range_(ui::Range::InvalidRange()), |
472 style_ranges_(), | 471 style_ranges_(), |
473 default_style_(), | 472 default_style_(), |
474 display_rect_(), | 473 display_rect_(), |
(...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
568 int start_x = font.GetStringWidth(text().substr(0, start)); | 567 int start_x = font.GetStringWidth(text().substr(0, start)); |
569 int end_x = font.GetStringWidth(text().substr(0, end)); | 568 int end_x = font.GetStringWidth(text().substr(0, end)); |
570 Rect rect(start_x, 0, end_x - start_x, font.GetHeight()); | 569 Rect rect(start_x, 0, end_x - start_x, font.GetHeight()); |
571 rect.Offset(display_rect_.origin()); | 570 rect.Offset(display_rect_.origin()); |
572 rect.Offset(GetUpdatedDisplayOffset()); | 571 rect.Offset(GetUpdatedDisplayOffset()); |
573 // Center the rect vertically in |display_rect_|. | 572 // Center the rect vertically in |display_rect_|. |
574 rect.Offset(Point(0, (display_rect_.height() - rect.height()) / 2)); | 573 rect.Offset(Point(0, (display_rect_.height() - rect.height()) / 2)); |
575 return std::vector<Rect>(1, rect); | 574 return std::vector<Rect>(1, rect); |
576 } | 575 } |
577 | 576 |
577 bool RenderText::IsCursorablePosition(size_t position) { | |
578 return true; | |
msw
2011/09/14 02:43:48
I think this can potentially be pure virtual, and
xji
2011/09/15 23:38:13
Done.
| |
579 } | |
580 | |
578 void RenderText::ApplyCompositionAndSelectionStyles( | 581 void RenderText::ApplyCompositionAndSelectionStyles( |
579 StyleRanges* style_ranges) const { | 582 StyleRanges* style_ranges) const { |
580 // TODO(msw): This pattern ought to be reconsidered; what about composition | 583 // TODO(msw): This pattern ought to be reconsidered; what about composition |
581 // and selection overlaps, retain existing local style features? | 584 // and selection overlaps, retain existing local style features? |
582 // Apply a composition style override to a copy of the style ranges. | 585 // Apply a composition style override to a copy of the style ranges. |
583 if (composition_range_.IsValid() && !composition_range_.is_empty()) { | 586 if (composition_range_.IsValid() && !composition_range_.is_empty()) { |
584 StyleRange composition_style(default_style_); | 587 StyleRange composition_style(default_style_); |
585 composition_style.underline = true; | 588 composition_style.underline = true; |
586 composition_style.range.set_start(composition_range_.start()); | 589 composition_style.range.set_start(composition_range_.start()); |
587 composition_style.range.set_end(composition_range_.end()); | 590 composition_style.range.set_end(composition_range_.end()); |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
625 | 628 |
626 cached_bounds_and_offset_valid_ = false; | 629 cached_bounds_and_offset_valid_ = false; |
627 } | 630 } |
628 | 631 |
629 void RenderText::MoveCursorTo(size_t position, bool select) { | 632 void RenderText::MoveCursorTo(size_t position, bool select) { |
630 size_t cursor = std::min(position, text().length()); | 633 size_t cursor = std::min(position, text().length()); |
631 size_t caret_pos = GetIndexOfPreviousGrapheme(cursor); | 634 size_t caret_pos = GetIndexOfPreviousGrapheme(cursor); |
632 SelectionModel::CaretPlacement placement = (caret_pos == cursor) ? | 635 SelectionModel::CaretPlacement placement = (caret_pos == cursor) ? |
633 SelectionModel::LEADING : SelectionModel::TRAILING; | 636 SelectionModel::LEADING : SelectionModel::TRAILING; |
634 size_t selection_start = select ? GetSelectionStart() : cursor; | 637 size_t selection_start = select ? GetSelectionStart() : cursor; |
635 SelectionModel sel(selection_start, cursor, caret_pos, placement); | 638 if (IsCursorablePosition(cursor)) { |
636 SetSelectionModel(sel); | 639 SelectionModel sel(selection_start, cursor, caret_pos, placement); |
637 } | 640 SetSelectionModel(sel); |
638 | 641 } |
639 bool RenderText::IsPositionAtWordSelectionBoundary(size_t pos) { | |
640 return pos == 0 || (u_isalnum(text()[pos - 1]) && !u_isalnum(text()[pos])) || | |
641 (!u_isalnum(text()[pos - 1]) && u_isalnum(text()[pos])); | |
642 } | 642 } |
643 | 643 |
644 void RenderText::UpdateCachedBoundsAndOffset() { | 644 void RenderText::UpdateCachedBoundsAndOffset() { |
645 if (cached_bounds_and_offset_valid_) | 645 if (cached_bounds_and_offset_valid_) |
646 return; | 646 return; |
647 // First, set the valid flag true to calculate the current cursor bounds using | 647 // First, set the valid flag true to calculate the current cursor bounds using |
648 // the stale |display_offset_|. Applying |delta_offset| at the end of this | 648 // the stale |display_offset_|. Applying |delta_offset| at the end of this |
649 // function will set |cursor_bounds_| and |display_offset_| to correct values. | 649 // function will set |cursor_bounds_| and |display_offset_| to correct values. |
650 cached_bounds_and_offset_valid_ = true; | 650 cached_bounds_and_offset_valid_ = true; |
651 cursor_bounds_ = GetCursorBounds(selection_model_, insert_mode_); | 651 cursor_bounds_ = GetCursorBounds(selection_model_, insert_mode_); |
(...skipping 30 matching lines...) Expand all Loading... | |
682 selection_start, | 682 selection_start, |
683 SelectionModel::LEADING); | 683 SelectionModel::LEADING); |
684 else if (selection_start > selection_end) | 684 else if (selection_start > selection_end) |
685 return SelectionModel(selection_start, | 685 return SelectionModel(selection_start, |
686 GetIndexOfPreviousGrapheme(selection_start), | 686 GetIndexOfPreviousGrapheme(selection_start), |
687 SelectionModel::TRAILING); | 687 SelectionModel::TRAILING); |
688 return selection_model_; | 688 return selection_model_; |
689 } | 689 } |
690 | 690 |
691 } // namespace gfx | 691 } // namespace gfx |
OLD | NEW |