Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(404)

Side by Side Diff: views/controls/textfield/textfield_views_model.cc

Issue 7265011: RenderText API Outline. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Fix permissions, export RenderText and StyleRange via UI_API. Created 9 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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 "views/controls/textfield/textfield_views_model.h" 5 #include "views/controls/textfield/textfield_views_model.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 "base/utf_string_conversions.h" 12 #include "base/utf_string_conversions.h"
13 #include "ui/base/clipboard/clipboard.h" 13 #include "ui/base/clipboard/clipboard.h"
14 #include "ui/base/clipboard/scoped_clipboard_writer.h" 14 #include "ui/base/clipboard/scoped_clipboard_writer.h"
15 #include "ui/base/range/range.h" 15 #include "ui/base/range/range.h"
16 #include "ui/gfx/canvas.h"
16 #include "ui/gfx/font.h" 17 #include "ui/gfx/font.h"
17 #include "views/controls/textfield/text_style.h" 18 #include "ui/gfx/render_text.h"
18 #include "views/controls/textfield/textfield.h" 19 #include "views/controls/textfield/textfield.h"
19 #include "views/views_delegate.h" 20 #include "views/views_delegate.h"
20 21
21 namespace views { 22 namespace views {
22 23
23 namespace internal { 24 namespace internal {
24 25
25 // An edit object holds enough information/state to undo/redo the 26 // An edit object holds enough information/state to undo/redo the
26 // change. Two edits are merged when possible, for example, when 27 // change. Two edits are merged when possible, for example, when
27 // you type new characters in sequence. |Commit()| can be used to 28 // you type new characters in sequence. |Commit()| can be used to
(...skipping 221 matching lines...) Expand 10 before | Expand all | Expand 10 after
249 // delete can be merged only with delete at the same 250 // delete can be merged only with delete at the same
250 // position. 251 // position.
251 if (edit->delete_backward_ || old_text_start_ != edit->old_text_start_) 252 if (edit->delete_backward_ || old_text_start_ != edit->old_text_start_)
252 return false; 253 return false;
253 old_text_ += edit->old_text_; 254 old_text_ += edit->old_text_;
254 } 255 }
255 return true; 256 return true;
256 } 257 }
257 }; 258 };
258 259
259 struct TextStyleRange {
260 TextStyleRange(const TextStyle* s, size_t start, size_t end)
261 : style(s),
262 range(start, end) {
263 }
264 TextStyleRange(const TextStyle* s, const ui::Range& r)
265 : style(s),
266 range(r) {
267 }
268 const TextStyle *style;
269 ui::Range range;
270 };
271
272 } // namespace internal 260 } // namespace internal
273 261
274 namespace {
275
276 using views::internal::TextStyleRange;
277
278 static bool TextStyleRangeComparator(const TextStyleRange* i,
279 const TextStyleRange* j) {
280 return i->range.start() < j->range.start();
281 }
282
283 #ifndef NDEBUG
284 // A test function to check TextStyleRanges' invariant condition:
285 // "no overlapping range".
286 bool CheckInvariant(const TextStyleRanges* style_ranges) {
287 TextStyleRanges copy = *style_ranges;
288 std::sort(copy.begin(), copy.end(), TextStyleRangeComparator);
289
290 for (TextStyleRanges::size_type i = 0; i < copy.size() - 1; i++) {
291 ui::Range& former = copy[i]->range;
292 ui::Range& latter = copy[i + 1]->range;
293 if (former.is_empty()) {
294 LOG(WARNING) << "Empty range at " << i << " :" << former;
295 return false;
296 }
297 if (!former.IsValid()) {
298 LOG(WARNING) << "Invalid range at " << i << " :" << former;
299 return false;
300 }
301 if (former.GetMax() > latter.GetMin()) {
302 LOG(WARNING) <<
303 "Sorting error. former:" << former << " latter:" << latter;
304 return false;
305 }
306 if (former.Intersects(latter)) {
307 LOG(ERROR) << "overlapping style range found: former=" << former
308 << ", latter=" << latter;
309 return false;
310 }
311 }
312 if ((*copy.rbegin())->range.is_empty()) {
313 LOG(WARNING) << "Empty range at end";
314 return false;
315 }
316 if (!(*copy.rbegin())->range.IsValid()) {
317 LOG(WARNING) << "Invalid range at end";
318 return false;
319 }
320 return true;
321 }
322 #endif
323
324 void InsertStyle(TextStyleRanges* style_ranges,
325 TextStyleRange* text_style_range) {
326 const ui::Range& range = text_style_range->range;
327 if (range.is_empty() || !range.IsValid()) {
328 delete text_style_range;
329 return;
330 }
331 CHECK(!range.is_reversed());
332
333 // Invariant condition: all items in the range has no overlaps.
334 TextStyleRanges::size_type index = 0;
335 while (index < style_ranges->size()) {
336 TextStyleRange* current = (*style_ranges)[index];
337 if (range.Contains(current->range)) {
338 style_ranges->erase(style_ranges->begin() + index);
339 delete current;
340 continue;
341 } else if (current->range.Contains(range) &&
342 range.start() != current->range.start() &&
343 range.end() != current->range.end()) {
344 // Split current style into two styles.
345 style_ranges->push_back(
346 new TextStyleRange(current->style,
347 range.GetMax(), current->range.GetMax()));
348 current->range.set_end(range.GetMin());
349 } else if (range.Intersects(current->range)) {
350 if (current->range.GetMax() <= range.GetMax()) {
351 current->range.set_end(range.GetMin());
352 } else {
353 current->range.set_start(range.GetMax());
354 }
355 } else {
356 // No change needed. Pass it through.
357 }
358 index ++;
359 }
360 // Add the new range at the end.
361 style_ranges->push_back(text_style_range);
362 #ifndef NDEBUG
363 DCHECK(CheckInvariant(style_ranges));
364 #endif
365 }
366
367 } // namespace
368
369 using internal::Edit; 262 using internal::Edit;
370 using internal::DeleteEdit; 263 using internal::DeleteEdit;
371 using internal::InsertEdit; 264 using internal::InsertEdit;
372 using internal::ReplaceEdit; 265 using internal::ReplaceEdit;
373 using internal::MergeType; 266 using internal::MergeType;
374 using internal::DO_NOT_MERGE; 267 using internal::DO_NOT_MERGE;
375 using internal::MERGE_WITH_PREVIOUS; 268 using internal::MERGE_WITH_PREVIOUS;
376 using internal::MERGEABLE; 269 using internal::MERGEABLE;
377 270
378 ///////////////////////////////////////////////////////////////// 271 /////////////////////////////////////////////////////////////////
379 // TextfieldViewsModel: public 272 // TextfieldViewsModel: public
380 273
381 TextfieldViewsModel::Delegate::~Delegate() { 274 TextfieldViewsModel::Delegate::~Delegate() {
382 } 275 }
383 276
384 TextfieldViewsModel::TextfieldViewsModel(Delegate* delegate) 277 TextfieldViewsModel::TextfieldViewsModel(Delegate* delegate)
385 : delegate_(delegate), 278 : delegate_(delegate),
386 cursor_pos_(0), 279 render_text_(gfx::RenderText::CreateRenderText()),
387 selection_start_(0),
388 composition_start_(0),
389 composition_end_(0),
390 is_password_(false), 280 is_password_(false),
391 current_edit_(edit_history_.end()), 281 current_edit_(edit_history_.end()) {
392 sort_style_ranges_(false) {
393 } 282 }
394 283
395 TextfieldViewsModel::~TextfieldViewsModel() { 284 TextfieldViewsModel::~TextfieldViewsModel() {
396 ClearEditHistory(); 285 ClearEditHistory();
397 ClearComposition(); 286 ClearComposition();
398 ClearAllTextStyles();
399 TextStyles::iterator begin = text_styles_.begin();
400 TextStyles::iterator end = text_styles_.end();
401 while (begin != end) {
402 TextStyles::iterator temp = begin;
403 ++begin;
404 delete *temp;
405 }
406 } 287 }
407 288
408 void TextfieldViewsModel::GetFragments(TextFragments* fragments) { 289 const string16& TextfieldViewsModel::GetText() const {
409 static const TextStyle* kNormalStyle = new TextStyle(); 290 return render_text_->text();
410
411 if (sort_style_ranges_) {
412 sort_style_ranges_ = false;
413 std::sort(style_ranges_.begin(), style_ranges_.end(),
414 TextStyleRangeComparator);
415 }
416
417 // If a user is compositing text, use composition's style.
418 // TODO(oshima): ask suzhe for expected behavior.
419 const TextStyleRanges& ranges = composition_style_ranges_.size() > 0 ?
420 composition_style_ranges_ : style_ranges_;
421 TextStyleRanges::const_iterator next_ = ranges.begin();
422
423 DCHECK(fragments);
424 fragments->clear();
425 size_t current = 0;
426 size_t end = text_.length();
427 while(next_ != ranges.end()) {
428 const TextStyleRange* text_style_range = *next_++;
429 const ui::Range& range = text_style_range->range;
430 const TextStyle* style = text_style_range->style;
431
432 DCHECK(!range.is_empty());
433 DCHECK(range.IsValid());
434 if (range.is_empty() || !range.IsValid())
435 continue;
436
437 size_t start = std::min(range.start(), end);
438
439 if (start == end) // Exit loop if it reached the end.
440 break;
441 else if (current < start) // Fill the gap to next style with normal text.
442 fragments->push_back(TextFragment(current, start, kNormalStyle));
443
444 current = std::min(range.end(), end);
445 fragments->push_back(TextFragment(start, current, style));
446 }
447 // If there is any text left add it as normal text.
448 if (current != end)
449 fragments->push_back(TextFragment(current, end, kNormalStyle));
450 } 291 }
451 292
452 bool TextfieldViewsModel::SetText(const string16& text) { 293 bool TextfieldViewsModel::SetText(const string16& text) {
453 bool changed = false; 294 bool changed = false;
454 if (HasCompositionText()) { 295 if (HasCompositionText()) {
455 ConfirmCompositionText(); 296 ConfirmCompositionText();
456 changed = true; 297 changed = true;
457 } 298 }
458 if (text_ != text) { 299 if (GetText() != text) {
459 if (changed) // No need to remember composition. 300 if (changed) // No need to remember composition.
460 Undo(); 301 Undo();
461 size_t old_cursor = cursor_pos_; 302 size_t old_cursor = GetCursorPosition();
462 size_t new_cursor = old_cursor > text.length() ? text.length() : old_cursor; 303 size_t new_cursor = old_cursor > text.length() ? text.length() : old_cursor;
463 SelectAll(); 304 SelectAll();
464 // If there is a composition text, don't merge with previous edit. 305 // If there is a composition text, don't merge with previous edit.
465 // Otherwise, force merge the edits. 306 // Otherwise, force merge the edits.
466 ExecuteAndRecordReplace( 307 ExecuteAndRecordReplace(
467 changed ? DO_NOT_MERGE : MERGE_WITH_PREVIOUS, 308 changed ? DO_NOT_MERGE : MERGE_WITH_PREVIOUS,
468 old_cursor, 309 old_cursor,
469 new_cursor, 310 new_cursor,
470 text, 311 text,
471 0U); 312 0U);
472 cursor_pos_ = new_cursor; 313 render_text_->SetCursorPosition(new_cursor);
473 } 314 }
474 ClearSelection(); 315 ClearSelection();
475 return changed; 316 return changed;
476 } 317 }
477 318
478 void TextfieldViewsModel::Append(const string16& text) { 319 void TextfieldViewsModel::Append(const string16& text) {
479 if (HasCompositionText()) 320 if (HasCompositionText())
480 ConfirmCompositionText(); 321 ConfirmCompositionText();
481 size_t save = cursor_pos_; 322 size_t save = GetCursorPosition();
482 MoveCursorToEnd(false); 323 MoveCursorRight(gfx::LINE_BREAK, false);
483 InsertText(text); 324 InsertText(text);
484 cursor_pos_ = save; 325 render_text_->SetCursorPosition(save);
485 ClearSelection(); 326 ClearSelection();
486 } 327 }
487 328
488 bool TextfieldViewsModel::Delete() { 329 bool TextfieldViewsModel::Delete() {
489 if (HasCompositionText()) { 330 if (HasCompositionText()) {
490 // No undo/redo for composition text. 331 // No undo/redo for composition text.
491 CancelCompositionText(); 332 CancelCompositionText();
492 return true; 333 return true;
493 } 334 }
494 if (HasSelection()) { 335 if (HasSelection()) {
495 DeleteSelection(); 336 DeleteSelection();
496 return true; 337 return true;
497 } 338 }
498 if (text_.length() > cursor_pos_) { 339 if (GetText().length() > GetCursorPosition()) {
499 ExecuteAndRecordDelete(cursor_pos_, cursor_pos_ + 1, true); 340 size_t cursor_position = GetCursorPosition();
341 ExecuteAndRecordDelete(cursor_position, cursor_position + 1, true);
500 return true; 342 return true;
501 } 343 }
502 return false; 344 return false;
503 } 345 }
504 346
505 bool TextfieldViewsModel::Backspace() { 347 bool TextfieldViewsModel::Backspace() {
506 if (HasCompositionText()) { 348 if (HasCompositionText()) {
507 // No undo/redo for composition text. 349 // No undo/redo for composition text.
508 CancelCompositionText(); 350 CancelCompositionText();
509 return true; 351 return true;
510 } 352 }
511 if (HasSelection()) { 353 if (HasSelection()) {
512 DeleteSelection(); 354 DeleteSelection();
513 return true; 355 return true;
514 } 356 }
515 if (cursor_pos_ > 0) { 357 if (GetCursorPosition() > 0) {
516 ExecuteAndRecordDelete(cursor_pos_, cursor_pos_ - 1, true); 358 size_t cursor_position = GetCursorPosition();
359 ExecuteAndRecordDelete(cursor_position, cursor_position - 1, true);
517 return true; 360 return true;
518 } 361 }
519 return false; 362 return false;
520 } 363 }
521 364
522 void TextfieldViewsModel::MoveCursorLeft(bool select) { 365 size_t TextfieldViewsModel::GetCursorPosition() const {
366 return render_text_->GetCursorPosition();
367 }
368
369 void TextfieldViewsModel::MoveCursorLeft(gfx::BreakType break_type,
370 bool select) {
523 if (HasCompositionText()) 371 if (HasCompositionText())
524 ConfirmCompositionText(); 372 ConfirmCompositionText();
525 // TODO(oshima): support BIDI 373 render_text_->MoveCursorLeft(break_type, select);
526 if (select) {
527 if (cursor_pos_ > 0)
528 cursor_pos_--;
529 } else {
530 if (HasSelection())
531 cursor_pos_ = std::min(cursor_pos_, selection_start_);
532 else if (cursor_pos_ > 0)
533 cursor_pos_--;
534 ClearSelection();
535 }
536 } 374 }
537 375
538 void TextfieldViewsModel::MoveCursorRight(bool select) { 376 void TextfieldViewsModel::MoveCursorRight(gfx::BreakType break_type,
377 bool select) {
539 if (HasCompositionText()) 378 if (HasCompositionText())
540 ConfirmCompositionText(); 379 ConfirmCompositionText();
541 // TODO(oshima): support BIDI 380 render_text_->MoveCursorRight(break_type, select);
542 if (select) {
543 cursor_pos_ = std::min(text_.length(), cursor_pos_ + 1);
544 } else {
545 if (HasSelection())
546 cursor_pos_ = std::max(cursor_pos_, selection_start_);
547 else
548 cursor_pos_ = std::min(text_.length(), cursor_pos_ + 1);
549 ClearSelection();
550 }
551 }
552
553 void TextfieldViewsModel::MoveCursorToPreviousWord(bool select) {
554 if (HasCompositionText())
555 ConfirmCompositionText();
556 // Notes: We always iterate words from the begining.
557 // This is probably fast enough for our usage, but we may
558 // want to modify WordIterator so that it can start from the
559 // middle of string and advance backwards.
560 base::i18n::BreakIterator iter(text_, base::i18n::BreakIterator::BREAK_WORD);
561 bool success = iter.Init();
562 DCHECK(success);
563 if (!success)
564 return;
565 int last = 0;
566 while (iter.Advance()) {
567 if (iter.IsWord()) {
568 size_t begin = iter.pos() - iter.GetString().length();
569 if (begin == cursor_pos_) {
570 // The cursor is at the beginning of a word.
571 // Move to previous word.
572 break;
573 } else if(iter.pos() >= cursor_pos_) {
574 // The cursor is in the middle or at the end of a word.
575 // Move to the top of current word.
576 last = begin;
577 break;
578 } else {
579 last = iter.pos() - iter.GetString().length();
580 }
581 }
582 }
583
584 cursor_pos_ = last;
585 if (!select)
586 ClearSelection();
587 }
588
589 void TextfieldViewsModel::MoveCursorToNextWord(bool select) {
590 if (HasCompositionText())
591 ConfirmCompositionText();
592 base::i18n::BreakIterator iter(text_, base::i18n::BreakIterator::BREAK_WORD);
593 bool success = iter.Init();
594 DCHECK(success);
595 if (!success)
596 return;
597 size_t pos = 0;
598 while (iter.Advance()) {
599 pos = iter.pos();
600 if (iter.IsWord() && pos > cursor_pos_) {
601 break;
602 }
603 }
604 cursor_pos_ = pos;
605 if (!select)
606 ClearSelection();
607 }
608
609 void TextfieldViewsModel::MoveCursorToHome(bool select) {
610 MoveCursorTo(0, select);
611 }
612
613 void TextfieldViewsModel::MoveCursorToEnd(bool select) {
614 MoveCursorTo(text_.length(), select);
615 } 381 }
616 382
617 bool TextfieldViewsModel::MoveCursorTo(size_t pos, bool select) { 383 bool TextfieldViewsModel::MoveCursorTo(size_t pos, bool select) {
618 if (HasCompositionText()) 384 if (HasCompositionText())
619 ConfirmCompositionText(); 385 ConfirmCompositionText();
620 bool changed = cursor_pos_ != pos || select != HasSelection(); 386 return render_text_->MoveCursorTo(pos, select);
621 cursor_pos_ = pos;
622 if (!select)
623 ClearSelection();
624 return changed;
625 } 387 }
626 388
627 gfx::Rect TextfieldViewsModel::GetSelectionBounds(const gfx::Font& font) const { 389 bool TextfieldViewsModel::MoveCursorTo(const gfx::Point& point, bool select) {
628 if (!HasSelection()) 390 if (HasCompositionText())
629 return gfx::Rect(); 391 ConfirmCompositionText();
630 size_t start = std::min(selection_start_, cursor_pos_); 392 return render_text_->MoveCursorTo(point, select);
631 size_t end = std::max(selection_start_, cursor_pos_); 393 }
632 int start_x = font.GetStringWidth(text_.substr(0, start)); 394
633 int end_x = font.GetStringWidth(text_.substr(0, end)); 395 std::vector<gfx::Rect> TextfieldViewsModel::GetSelectionBounds() const {
634 return gfx::Rect(start_x, 0, end_x - start_x, font.GetHeight()); 396 return render_text_->GetSubstringBounds(render_text_->GetSelection());
635 } 397 }
636 398
637 string16 TextfieldViewsModel::GetSelectedText() const { 399 string16 TextfieldViewsModel::GetSelectedText() const {
638 return text_.substr( 400 ui::Range selection = render_text_->GetSelection();
639 std::min(cursor_pos_, selection_start_), 401 return GetText().substr(selection.GetMin(), selection.length());
640 std::abs(static_cast<long>(cursor_pos_ - selection_start_)));
641 } 402 }
642 403
643 void TextfieldViewsModel::GetSelectedRange(ui::Range* range) const { 404 void TextfieldViewsModel::GetSelectedRange(ui::Range* range) const {
644 *range = ui::Range(selection_start_, cursor_pos_); 405 *range = render_text_->GetSelection();
645 } 406 }
646 407
647 void TextfieldViewsModel::SelectRange(const ui::Range& range) { 408 void TextfieldViewsModel::SelectRange(const ui::Range& range) {
648 if (HasCompositionText()) 409 if (HasCompositionText())
649 ConfirmCompositionText(); 410 ConfirmCompositionText();
650 selection_start_ = GetSafePosition(range.start()); 411 render_text_->SetSelection(range);
651 cursor_pos_ = GetSafePosition(range.end());
652 } 412 }
653 413
654 void TextfieldViewsModel::SelectAll() { 414 void TextfieldViewsModel::SelectAll() {
655 if (HasCompositionText()) 415 if (HasCompositionText())
656 ConfirmCompositionText(); 416 ConfirmCompositionText();
657 // SelectAll selects towards the end. 417 render_text_->SelectAll();
658 cursor_pos_ = text_.length();
659 selection_start_ = 0;
660 } 418 }
661 419
662 void TextfieldViewsModel::SelectWord() { 420 void TextfieldViewsModel::SelectWord() {
663 if (HasCompositionText()) 421 if (HasCompositionText())
664 ConfirmCompositionText(); 422 ConfirmCompositionText();
665 // First we setup selection_start_ and cursor_pos_. There are so many cases 423 render_text_->SelectWord();
666 // because we try to emulate what select-word looks like in a gtk textfield.
667 // See associated testcase for different cases.
668 if (cursor_pos_ > 0 && cursor_pos_ < text_.length()) {
669 if (isalnum(text_[cursor_pos_])) {
670 selection_start_ = cursor_pos_;
671 cursor_pos_++;
672 } else
673 selection_start_ = cursor_pos_ - 1;
674 } else if (cursor_pos_ == 0) {
675 selection_start_ = cursor_pos_;
676 if (text_.length() > 0)
677 cursor_pos_++;
678 } else {
679 selection_start_ = cursor_pos_ - 1;
680 }
681
682 // Now we move selection_start_ to beginning of selection. Selection boundary
683 // is defined as the position where we have alpha-num character on one side
684 // and non-alpha-num char on the other side.
685 for (; selection_start_ > 0; selection_start_--) {
686 if (IsPositionAtWordSelectionBoundary(selection_start_))
687 break;
688 }
689
690 // Now we move cursor_pos_ to end of selection. Selection boundary
691 // is defined as the position where we have alpha-num character on one side
692 // and non-alpha-num char on the other side.
693 for (; cursor_pos_ < text_.length(); cursor_pos_++) {
694 if (IsPositionAtWordSelectionBoundary(cursor_pos_))
695 break;
696 }
697 } 424 }
698 425
699 void TextfieldViewsModel::ClearSelection() { 426 void TextfieldViewsModel::ClearSelection() {
700 if (HasCompositionText()) 427 if (HasCompositionText())
701 ConfirmCompositionText(); 428 ConfirmCompositionText();
702 selection_start_ = cursor_pos_; 429 render_text_->ClearSelection();
703 } 430 }
704 431
705 bool TextfieldViewsModel::CanUndo() { 432 bool TextfieldViewsModel::CanUndo() {
706 return edit_history_.size() && current_edit_ != edit_history_.end(); 433 return edit_history_.size() && current_edit_ != edit_history_.end();
707 } 434 }
708 435
709 bool TextfieldViewsModel::CanRedo() { 436 bool TextfieldViewsModel::CanRedo() {
710 if (!edit_history_.size()) 437 if (!edit_history_.size())
711 return false; 438 return false;
712 // There is no redo iff the current edit is the last element 439 // There is no redo iff the current edit is the last element
713 // in the history. 440 // in the history.
714 EditHistory::iterator iter = current_edit_; 441 EditHistory::iterator iter = current_edit_;
715 return iter == edit_history_.end() || // at the top. 442 return iter == edit_history_.end() || // at the top.
716 ++iter != edit_history_.end(); 443 ++iter != edit_history_.end();
717 } 444 }
718 445
719 bool TextfieldViewsModel::Undo() { 446 bool TextfieldViewsModel::Undo() {
720 if (!CanUndo()) 447 if (!CanUndo())
721 return false; 448 return false;
722 DCHECK(!HasCompositionText()); 449 DCHECK(!HasCompositionText());
723 if (HasCompositionText()) // safe guard for release build. 450 if (HasCompositionText()) // safe guard for release build.
724 CancelCompositionText(); 451 CancelCompositionText();
725 452
726 string16 old = text_; 453 string16 old = GetText();
727 size_t old_cursor = cursor_pos_; 454 size_t old_cursor = GetCursorPosition();
728 (*current_edit_)->Commit(); 455 (*current_edit_)->Commit();
729 (*current_edit_)->Undo(this); 456 (*current_edit_)->Undo(this);
730 457
731 if (current_edit_ == edit_history_.begin()) 458 if (current_edit_ == edit_history_.begin())
732 current_edit_ = edit_history_.end(); 459 current_edit_ = edit_history_.end();
733 else 460 else
734 current_edit_--; 461 current_edit_--;
735 return old != text_ || old_cursor != cursor_pos_; 462 return old != GetText() || old_cursor != GetCursorPosition();
736 } 463 }
737 464
738 bool TextfieldViewsModel::Redo() { 465 bool TextfieldViewsModel::Redo() {
739 if (!CanRedo()) 466 if (!CanRedo())
740 return false; 467 return false;
741 DCHECK(!HasCompositionText()); 468 DCHECK(!HasCompositionText());
742 if (HasCompositionText()) // safe guard for release build. 469 if (HasCompositionText()) // safe guard for release build.
743 CancelCompositionText(); 470 CancelCompositionText();
744 471
745 if (current_edit_ == edit_history_.end()) 472 if (current_edit_ == edit_history_.end())
746 current_edit_ = edit_history_.begin(); 473 current_edit_ = edit_history_.begin();
747 else 474 else
748 current_edit_ ++; 475 current_edit_ ++;
749 string16 old = text_; 476 string16 old = GetText();
750 size_t old_cursor = cursor_pos_; 477 size_t old_cursor = GetCursorPosition();
751 (*current_edit_)->Redo(this); 478 (*current_edit_)->Redo(this);
752 return old != text_ || old_cursor != cursor_pos_; 479 return old != GetText() || old_cursor != GetCursorPosition();
480 }
481
482 string16 TextfieldViewsModel::GetVisibleText() const {
483 return GetVisibleText(0U, GetText().length());
753 } 484 }
754 485
755 bool TextfieldViewsModel::Cut() { 486 bool TextfieldViewsModel::Cut() {
756 if (!HasCompositionText() && HasSelection()) { 487 if (!HasCompositionText() && HasSelection()) {
757 ui::ScopedClipboardWriter(views::ViewsDelegate::views_delegate 488 ui::ScopedClipboardWriter(views::ViewsDelegate::views_delegate
758 ->GetClipboard()).WriteText(GetSelectedText()); 489 ->GetClipboard()).WriteText(GetSelectedText());
759 // A trick to let undo/redo handle cursor correctly. 490 // A trick to let undo/redo handle cursor correctly.
760 // Undoing CUT moves the cursor to the end of the change rather 491 // Undoing CUT moves the cursor to the end of the change rather
761 // than beginning, unlike Delete/Backspace. 492 // than beginning, unlike Delete/Backspace.
762 // TODO(oshima): Change Delete/Backspace to use DeleteSelection, 493 // TODO(oshima): Change Delete/Backspace to use DeleteSelection,
763 // update DeleteEdit and remove this trick. 494 // update DeleteEdit and remove this trick.
764 std::swap(cursor_pos_, selection_start_); 495 ui::Range selection = render_text_->GetSelection();
496 render_text_->SetSelection(ui::Range(selection.end(), selection.start()));
765 DeleteSelection(); 497 DeleteSelection();
766 return true; 498 return true;
767 } 499 }
768 return false; 500 return false;
769 } 501 }
770 502
771 void TextfieldViewsModel::Copy() { 503 void TextfieldViewsModel::Copy() {
772 if (!HasCompositionText() && HasSelection()) { 504 if (!HasCompositionText() && HasSelection()) {
773 ui::ScopedClipboardWriter(views::ViewsDelegate::views_delegate 505 ui::ScopedClipboardWriter(views::ViewsDelegate::views_delegate
774 ->GetClipboard()).WriteText(GetSelectedText()); 506 ->GetClipboard()).WriteText(GetSelectedText());
775 } 507 }
776 } 508 }
777 509
778 bool TextfieldViewsModel::Paste() { 510 bool TextfieldViewsModel::Paste() {
779 string16 result; 511 string16 result;
780 views::ViewsDelegate::views_delegate->GetClipboard() 512 views::ViewsDelegate::views_delegate->GetClipboard()
781 ->ReadText(ui::Clipboard::BUFFER_STANDARD, &result); 513 ->ReadText(ui::Clipboard::BUFFER_STANDARD, &result);
782 if (!result.empty()) { 514 if (!result.empty()) {
783 InsertTextInternal(result, false); 515 InsertTextInternal(result, false);
784 return true; 516 return true;
785 } 517 }
786 return false; 518 return false;
787 } 519 }
788 520
789 bool TextfieldViewsModel::HasSelection() const { 521 bool TextfieldViewsModel::HasSelection() const {
790 return selection_start_ != cursor_pos_; 522 return !render_text_->GetSelection().is_empty();
791 } 523 }
792 524
793 void TextfieldViewsModel::DeleteSelection() { 525 void TextfieldViewsModel::DeleteSelection() {
794 DCHECK(!HasCompositionText()); 526 DCHECK(!HasCompositionText());
795 DCHECK(HasSelection()); 527 DCHECK(HasSelection());
796 ExecuteAndRecordDelete(selection_start_, cursor_pos_, false); 528 ui::Range selection = render_text_->GetSelection();
529 ExecuteAndRecordDelete(selection.start(), selection.end(), false);
797 } 530 }
798 531
799 void TextfieldViewsModel::DeleteSelectionAndInsertTextAt( 532 void TextfieldViewsModel::DeleteSelectionAndInsertTextAt(
800 const string16& text, size_t position) { 533 const string16& text, size_t position) {
801 if (HasCompositionText()) 534 if (HasCompositionText())
802 CancelCompositionText(); 535 CancelCompositionText();
803 ExecuteAndRecordReplace(DO_NOT_MERGE, 536 ExecuteAndRecordReplace(DO_NOT_MERGE,
804 cursor_pos_, 537 GetCursorPosition(),
805 position + text.length(), 538 position + text.length(),
806 text, 539 text,
807 position); 540 position);
808 } 541 }
809 542
810 string16 TextfieldViewsModel::GetTextFromRange(const ui::Range& range) const { 543 string16 TextfieldViewsModel::GetTextFromRange(const ui::Range& range) const {
811 if (range.IsValid() && range.GetMin() < text_.length()) 544 if (range.IsValid() && range.GetMin() < GetText().length())
812 return text_.substr(range.GetMin(), range.length()); 545 return GetText().substr(range.GetMin(), range.length());
813 return string16(); 546 return string16();
814 } 547 }
815 548
816 void TextfieldViewsModel::GetTextRange(ui::Range* range) const { 549 void TextfieldViewsModel::GetTextRange(ui::Range* range) const {
817 *range = ui::Range(0, text_.length()); 550 *range = ui::Range(0, GetText().length());
818 } 551 }
819 552
820 void TextfieldViewsModel::SetCompositionText( 553 void TextfieldViewsModel::SetCompositionText(
821 const ui::CompositionText& composition) { 554 const ui::CompositionText& composition) {
822 static const TextStyle* composition_style = CreateUnderlineStyle();
823
824 if (HasCompositionText()) 555 if (HasCompositionText())
825 CancelCompositionText(); 556 CancelCompositionText();
826 else if (HasSelection()) 557 else if (HasSelection())
827 DeleteSelection(); 558 DeleteSelection();
828 559
829 if (composition.text.empty()) 560 if (composition.text.empty())
830 return; 561 return;
831 562
832 size_t length = composition.text.length(); 563 size_t cursor = GetCursorPosition();
833 text_.insert(cursor_pos_, composition.text); 564 string16 new_text = GetText();
834 composition_start_ = cursor_pos_; 565 render_text_->SetText(new_text.insert(cursor, composition.text));
835 composition_end_ = composition_start_ + length; 566 ui::Range range(cursor, cursor + composition.text.length());
836 for (ui::CompositionUnderlines::const_iterator iter = 567 render_text_->SetCompositionRange(range);
837 composition.underlines.begin(); 568 // TODO(msw): Support multiple composition underline ranges.
838 iter != composition.underlines.end();
839 iter++) {
840 size_t start = composition_start_ + iter->start_offset;
841 size_t end = composition_start_ + iter->end_offset;
842 InsertStyle(&composition_style_ranges_,
843 new TextStyleRange(composition_style, start, end));
844 }
845 std::sort(composition_style_ranges_.begin(),
846 composition_style_ranges_.end(), TextStyleRangeComparator);
847 569
848 if (composition.selection.IsValid()) { 570 if (composition.selection.IsValid())
849 selection_start_ = 571 render_text_->SetSelection(ui::Range(
850 std::min(composition_start_ + composition.selection.start(), 572 std::min(range.start() + composition.selection.start(), range.end()),
851 composition_end_); 573 std::min(range.start() + composition.selection.end(), range.end())));
852 cursor_pos_ = 574 else
853 std::min(composition_start_ + composition.selection.end(), 575 render_text_->SetCursorPosition(range.end());
854 composition_end_);
855 } else {
856 cursor_pos_ = composition_end_;
857 ClearSelection();
858 }
859 } 576 }
860 577
861 void TextfieldViewsModel::ConfirmCompositionText() { 578 void TextfieldViewsModel::ConfirmCompositionText() {
862 DCHECK(HasCompositionText()); 579 DCHECK(HasCompositionText());
863 string16 new_text = 580 ui::Range range = render_text_->GetCompositionRange();
864 text_.substr(composition_start_, composition_end_ - composition_start_); 581 string16 text = GetText().substr(range.start(), range.length());
865 // TODO(oshima): current behavior on ChromeOS is a bit weird and not 582 // TODO(oshima): current behavior on ChromeOS is a bit weird and not
866 // sure exactly how this should work. Find out and fix if necessary. 583 // sure exactly how this should work. Find out and fix if necessary.
867 AddOrMergeEditHistory(new InsertEdit(false, new_text, composition_start_)); 584 AddOrMergeEditHistory(new InsertEdit(false, text, range.start()));
868 cursor_pos_ = composition_end_; 585 render_text_->SetCursorPosition(range.end());
869 ClearComposition(); 586 ClearComposition();
870 ClearSelection();
871 if (delegate_) 587 if (delegate_)
872 delegate_->OnCompositionTextConfirmedOrCleared(); 588 delegate_->OnCompositionTextConfirmedOrCleared();
873 } 589 }
874 590
875 void TextfieldViewsModel::CancelCompositionText() { 591 void TextfieldViewsModel::CancelCompositionText() {
876 DCHECK(HasCompositionText()); 592 DCHECK(HasCompositionText());
877 text_.erase(composition_start_, composition_end_ - composition_start_); 593 ui::Range range = render_text_->GetCompositionRange();
878 cursor_pos_ = composition_start_; 594 string16 new_text = GetText();
595 render_text_->SetText(new_text.erase(range.start(), range.length()));
596 render_text_->SetCursorPosition(range.start());
879 ClearComposition(); 597 ClearComposition();
880 ClearSelection();
881 if (delegate_) 598 if (delegate_)
882 delegate_->OnCompositionTextConfirmedOrCleared(); 599 delegate_->OnCompositionTextConfirmedOrCleared();
883 } 600 }
884 601
885 void TextfieldViewsModel::ClearComposition() { 602 void TextfieldViewsModel::ClearComposition() {
886 composition_start_ = composition_end_ = string16::npos; 603 render_text_->SetCompositionRange(ui::Range::InvalidRange());
887 STLDeleteContainerPointers(composition_style_ranges_.begin(),
888 composition_style_ranges_.end());
889 composition_style_ranges_.clear();
890 }
891
892 void TextfieldViewsModel::ApplyTextStyle(const TextStyle* style,
893 const ui::Range& range) {
894 TextStyleRange* new_text_style_range = range.is_reversed() ?
895 new TextStyleRange(style, ui::Range(range.end(), range.start())) :
896 new TextStyleRange(style, range);
897 InsertStyle(&style_ranges_, new_text_style_range);
898 sort_style_ranges_ = true;
899 } 604 }
900 605
901 void TextfieldViewsModel::GetCompositionTextRange(ui::Range* range) const { 606 void TextfieldViewsModel::GetCompositionTextRange(ui::Range* range) const {
902 if (HasCompositionText()) 607 *range = ui::Range(render_text_->GetCompositionRange());
903 *range = ui::Range(composition_start_, composition_end_);
904 else
905 *range = ui::Range::InvalidRange();
906 } 608 }
907 609
908 bool TextfieldViewsModel::HasCompositionText() const { 610 bool TextfieldViewsModel::HasCompositionText() const {
909 return composition_start_ != composition_end_; 611 return !render_text_->GetCompositionRange().is_empty();
910 }
911
912 TextStyle* TextfieldViewsModel::CreateTextStyle() {
913 TextStyle* style = new TextStyle();
914 text_styles_.push_back(style);
915 return style;
916 }
917
918 void TextfieldViewsModel::ClearAllTextStyles() {
919 STLDeleteContainerPointers(style_ranges_.begin(), style_ranges_.end());
920 style_ranges_.clear();
921 } 612 }
922 613
923 ///////////////////////////////////////////////////////////////// 614 /////////////////////////////////////////////////////////////////
924 // TextfieldViewsModel: private 615 // TextfieldViewsModel: private
925 616
926 string16 TextfieldViewsModel::GetVisibleText(size_t begin, size_t end) const { 617 string16 TextfieldViewsModel::GetVisibleText(size_t begin, size_t end) const {
927 DCHECK(end >= begin); 618 DCHECK(end >= begin);
928 if (is_password_) 619 if (is_password_)
929 return string16(end - begin, '*'); 620 return string16(end - begin, '*');
930 return text_.substr(begin, end - begin); 621 return GetText().substr(begin, end - begin);
931 }
932
933 bool TextfieldViewsModel::IsPositionAtWordSelectionBoundary(size_t pos) {
934 return (isalnum(text_[pos - 1]) && !isalnum(text_[pos])) ||
935 (!isalnum(text_[pos - 1]) && isalnum(text_[pos]));
936 }
937
938 size_t TextfieldViewsModel::GetSafePosition(size_t position) const {
939 if (position > text_.length()) {
940 return text_.length();
941 }
942 return position;
943 } 622 }
944 623
945 void TextfieldViewsModel::InsertTextInternal(const string16& text, 624 void TextfieldViewsModel::InsertTextInternal(const string16& text,
946 bool mergeable) { 625 bool mergeable) {
947 if (HasCompositionText()) { 626 if (HasCompositionText()) {
948 CancelCompositionText(); 627 CancelCompositionText();
949 ExecuteAndRecordInsert(text, mergeable); 628 ExecuteAndRecordInsert(text, mergeable);
950 } else if (HasSelection()) { 629 } else if (HasSelection()) {
951 ExecuteAndRecordReplaceSelection(mergeable ? MERGEABLE : DO_NOT_MERGE, 630 ExecuteAndRecordReplaceSelection(mergeable ? MERGEABLE : DO_NOT_MERGE,
952 text); 631 text);
953 } else { 632 } else {
954 ExecuteAndRecordInsert(text, mergeable); 633 ExecuteAndRecordInsert(text, mergeable);
955 } 634 }
956 } 635 }
957 636
958 void TextfieldViewsModel::ReplaceTextInternal(const string16& text, 637 void TextfieldViewsModel::ReplaceTextInternal(const string16& text,
959 bool mergeable) { 638 bool mergeable) {
960 if (HasCompositionText()) 639 if (HasCompositionText()) {
961 CancelCompositionText(); 640 CancelCompositionText();
962 else if (!HasSelection()) 641 } else if (!HasSelection()) {
963 SelectRange(ui::Range(cursor_pos_ + text.length(), cursor_pos_)); 642 size_t cursor = GetCursorPosition();
643 render_text_->SetSelection(ui::Range(cursor + text.length(), cursor));
644 }
964 // Edit history is recorded in InsertText. 645 // Edit history is recorded in InsertText.
965 InsertTextInternal(text, mergeable); 646 InsertTextInternal(text, mergeable);
966 } 647 }
967 648
968 void TextfieldViewsModel::ClearEditHistory() { 649 void TextfieldViewsModel::ClearEditHistory() {
969 STLDeleteContainerPointers(edit_history_.begin(), 650 STLDeleteContainerPointers(edit_history_.begin(),
970 edit_history_.end()); 651 edit_history_.end());
971 edit_history_.clear(); 652 edit_history_.clear();
972 current_edit_ = edit_history_.end(); 653 current_edit_ = edit_history_.end();
973 } 654 }
974 655
975 void TextfieldViewsModel::ClearRedoHistory() { 656 void TextfieldViewsModel::ClearRedoHistory() {
976 if (edit_history_.begin() == edit_history_.end()) 657 if (edit_history_.begin() == edit_history_.end())
977 return; 658 return;
978 if (current_edit_ == edit_history_.end()) { 659 if (current_edit_ == edit_history_.end()) {
979 ClearEditHistory(); 660 ClearEditHistory();
980 return; 661 return;
981 } 662 }
982 EditHistory::iterator delete_start = current_edit_; 663 EditHistory::iterator delete_start = current_edit_;
983 delete_start++; 664 delete_start++;
984 STLDeleteContainerPointers(delete_start, edit_history_.end()); 665 STLDeleteContainerPointers(delete_start, edit_history_.end());
985 edit_history_.erase(delete_start, edit_history_.end()); 666 edit_history_.erase(delete_start, edit_history_.end());
986 } 667 }
987 668
988 void TextfieldViewsModel::ExecuteAndRecordDelete(size_t from, 669 void TextfieldViewsModel::ExecuteAndRecordDelete(size_t from,
989 size_t to, 670 size_t to,
990 bool mergeable) { 671 bool mergeable) {
991 size_t old_text_start = std::min(from, to); 672 size_t old_text_start = std::min(from, to);
992 const string16 text = text_.substr(old_text_start, 673 const string16 text = GetText().substr(old_text_start,
993 std::abs(static_cast<long>(from - to))); 674 std::abs(static_cast<long>(from - to)));
994 bool backward = from > to; 675 bool backward = from > to;
995 Edit* edit = new DeleteEdit(mergeable, text, old_text_start, backward); 676 Edit* edit = new DeleteEdit(mergeable, text, old_text_start, backward);
996 bool delete_edit = AddOrMergeEditHistory(edit); 677 bool delete_edit = AddOrMergeEditHistory(edit);
997 edit->Redo(this); 678 edit->Redo(this);
998 if (delete_edit) 679 if (delete_edit)
999 delete edit; 680 delete edit;
1000 } 681 }
1001 682
1002 void TextfieldViewsModel::ExecuteAndRecordReplaceSelection( 683 void TextfieldViewsModel::ExecuteAndRecordReplaceSelection(
1003 MergeType merge_type, const string16& new_text) { 684 MergeType merge_type, const string16& new_text) {
1004 size_t new_text_start = std::min(cursor_pos_, selection_start_); 685 size_t new_text_start = render_text_->GetSelection().GetMin();
1005 size_t new_cursor_pos = new_text_start + new_text.length(); 686 size_t new_cursor_pos = new_text_start + new_text.length();
1006 ExecuteAndRecordReplace(merge_type, 687 ExecuteAndRecordReplace(merge_type,
1007 cursor_pos_, 688 GetCursorPosition(),
1008 new_cursor_pos, 689 new_cursor_pos,
1009 new_text, 690 new_text,
1010 new_text_start); 691 new_text_start);
1011 } 692 }
1012 693
1013 void TextfieldViewsModel::ExecuteAndRecordReplace(MergeType merge_type, 694 void TextfieldViewsModel::ExecuteAndRecordReplace(MergeType merge_type,
1014 size_t old_cursor_pos, 695 size_t old_cursor_pos,
1015 size_t new_cursor_pos, 696 size_t new_cursor_pos,
1016 const string16& new_text, 697 const string16& new_text,
1017 size_t new_text_start) { 698 size_t new_text_start) {
1018 size_t old_text_start = std::min(cursor_pos_, selection_start_); 699 size_t old_text_start = render_text_->GetSelection().GetMin();
1019 bool backward = selection_start_ > cursor_pos_; 700 bool backward = render_text_->GetSelection().is_reversed();
1020 Edit* edit = new ReplaceEdit(merge_type, 701 Edit* edit = new ReplaceEdit(merge_type,
1021 GetSelectedText(), 702 GetSelectedText(),
1022 old_cursor_pos, 703 old_cursor_pos,
1023 old_text_start, 704 old_text_start,
1024 backward, 705 backward,
1025 new_cursor_pos, 706 new_cursor_pos,
1026 new_text, 707 new_text,
1027 new_text_start); 708 new_text_start);
1028 bool delete_edit = AddOrMergeEditHistory(edit); 709 bool delete_edit = AddOrMergeEditHistory(edit);
1029 edit->Redo(this); 710 edit->Redo(this);
1030 if (delete_edit) 711 if (delete_edit)
1031 delete edit; 712 delete edit;
1032 } 713 }
1033 714
1034 void TextfieldViewsModel::ExecuteAndRecordInsert(const string16& text, 715 void TextfieldViewsModel::ExecuteAndRecordInsert(const string16& text,
1035 bool mergeable) { 716 bool mergeable) {
1036 Edit* edit = new InsertEdit(mergeable, text, cursor_pos_); 717 Edit* edit = new InsertEdit(mergeable, text, GetCursorPosition());
1037 bool delete_edit = AddOrMergeEditHistory(edit); 718 bool delete_edit = AddOrMergeEditHistory(edit);
1038 edit->Redo(this); 719 edit->Redo(this);
1039 if (delete_edit) 720 if (delete_edit)
1040 delete edit; 721 delete edit;
1041 } 722 }
1042 723
1043 bool TextfieldViewsModel::AddOrMergeEditHistory(Edit* edit) { 724 bool TextfieldViewsModel::AddOrMergeEditHistory(Edit* edit) {
1044 ClearRedoHistory(); 725 ClearRedoHistory();
1045 726
1046 if (current_edit_ != edit_history_.end() && (*current_edit_)->Merge(edit)) { 727 if (current_edit_ != edit_history_.end() && (*current_edit_)->Merge(edit)) {
(...skipping 13 matching lines...) Expand all
1060 } 741 }
1061 return false; 742 return false;
1062 } 743 }
1063 744
1064 void TextfieldViewsModel::ModifyText(size_t delete_from, 745 void TextfieldViewsModel::ModifyText(size_t delete_from,
1065 size_t delete_to, 746 size_t delete_to,
1066 const string16& new_text, 747 const string16& new_text,
1067 size_t new_text_insert_at, 748 size_t new_text_insert_at,
1068 size_t new_cursor_pos) { 749 size_t new_cursor_pos) {
1069 DCHECK_LE(delete_from, delete_to); 750 DCHECK_LE(delete_from, delete_to);
751 string16 text = GetText();
1070 if (delete_from != delete_to) 752 if (delete_from != delete_to)
1071 text_.erase(delete_from, delete_to - delete_from); 753 render_text_->SetText(text.erase(delete_from, delete_to - delete_from));
1072 if (!new_text.empty()) 754 if (!new_text.empty())
1073 text_.insert(new_text_insert_at, new_text); 755 render_text_->SetText(text.insert(new_text_insert_at, new_text));
1074 cursor_pos_ = new_cursor_pos; 756 render_text_->SetCursorPosition(new_cursor_pos);
1075 ClearSelection();
1076 // TODO(oshima): mac selects the text that is just undone (but gtk doesn't). 757 // TODO(oshima): mac selects the text that is just undone (but gtk doesn't).
1077 // This looks fine feature and we may want to do the same. 758 // This looks fine feature and we may want to do the same.
1078 } 759 }
1079 760
1080 // static
1081 TextStyle* TextfieldViewsModel::CreateUnderlineStyle() {
1082 TextStyle* style = new TextStyle();
1083 style->set_underline(true);
1084 return style;
1085 }
1086
1087 } // namespace views 761 } // namespace views
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698