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

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: Add TODO comments, revise cursor movement API, etc. 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(); 287 delete render_text_;
oshima 2011/07/23 09:51:12 why not scoped_ptr?
msw 2011/07/25 05:09:54 Done.
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 } 288 }
407 289
408 void TextfieldViewsModel::GetFragments(TextFragments* fragments) { 290 const string16& TextfieldViewsModel::GetText() const {
409 static const TextStyle* kNormalStyle = new TextStyle(); 291 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 } 292 }
451 293
452 bool TextfieldViewsModel::SetText(const string16& text) { 294 bool TextfieldViewsModel::SetText(const string16& text) {
453 bool changed = false; 295 bool changed = false;
454 if (HasCompositionText()) { 296 if (HasCompositionText()) {
455 ConfirmCompositionText(); 297 ConfirmCompositionText();
456 changed = true; 298 changed = true;
457 } 299 }
458 if (text_ != text) { 300 if (GetText() != text) {
459 if (changed) // No need to remember composition. 301 if (changed) // No need to remember composition.
460 Undo(); 302 Undo();
461 size_t old_cursor = cursor_pos_; 303 size_t old_cursor = render_text_->GetCursor();
462 size_t new_cursor = old_cursor > text.length() ? text.length() : old_cursor; 304 size_t new_cursor = old_cursor > text.length() ? text.length() : old_cursor;
463 SelectAll(); 305 SelectAll();
464 // If there is a composition text, don't merge with previous edit. 306 // If there is a composition text, don't merge with previous edit.
465 // Otherwise, force merge the edits. 307 // Otherwise, force merge the edits.
466 ExecuteAndRecordReplace( 308 ExecuteAndRecordReplace(
467 changed ? DO_NOT_MERGE : MERGE_WITH_PREVIOUS, 309 changed ? DO_NOT_MERGE : MERGE_WITH_PREVIOUS,
468 old_cursor, 310 old_cursor,
469 new_cursor, 311 new_cursor,
470 text, 312 text,
471 0U); 313 0U);
472 cursor_pos_ = new_cursor; 314 render_text_->SetCursor(new_cursor);
473 } 315 }
474 ClearSelection(); 316 ClearSelection();
475 return changed; 317 return changed;
476 } 318 }
477 319
478 void TextfieldViewsModel::Append(const string16& text) { 320 void TextfieldViewsModel::Append(const string16& text) {
479 if (HasCompositionText()) 321 if (HasCompositionText())
480 ConfirmCompositionText(); 322 ConfirmCompositionText();
481 size_t save = cursor_pos_; 323 size_t save = render_text_->GetCursor();
482 MoveCursorToEnd(false); 324 MoveCursorRight(gfx::LINE_BREAK, false);
483 InsertText(text); 325 InsertText(text);
484 cursor_pos_ = save; 326 render_text_->SetCursor(save);
485 ClearSelection(); 327 ClearSelection();
486 } 328 }
487 329
488 bool TextfieldViewsModel::Delete() { 330 bool TextfieldViewsModel::Delete() {
489 if (HasCompositionText()) { 331 if (HasCompositionText()) {
490 // No undo/redo for composition text. 332 // No undo/redo for composition text.
491 CancelCompositionText(); 333 CancelCompositionText();
492 return true; 334 return true;
493 } 335 }
494 if (HasSelection()) { 336 if (HasSelection()) {
495 DeleteSelection(); 337 DeleteSelection();
496 return true; 338 return true;
497 } 339 }
498 if (text_.length() > cursor_pos_) { 340 if (GetText().length() > render_text_->GetCursor()) {
499 ExecuteAndRecordDelete(cursor_pos_, cursor_pos_ + 1, true); 341 size_t cursor_position = render_text_->GetCursor();
342 ExecuteAndRecordDelete(cursor_position, cursor_position + 1, true);
500 return true; 343 return true;
501 } 344 }
502 return false; 345 return false;
503 } 346 }
504 347
505 bool TextfieldViewsModel::Backspace() { 348 bool TextfieldViewsModel::Backspace() {
506 if (HasCompositionText()) { 349 if (HasCompositionText()) {
507 // No undo/redo for composition text. 350 // No undo/redo for composition text.
508 CancelCompositionText(); 351 CancelCompositionText();
509 return true; 352 return true;
510 } 353 }
511 if (HasSelection()) { 354 if (HasSelection()) {
512 DeleteSelection(); 355 DeleteSelection();
513 return true; 356 return true;
514 } 357 }
515 if (cursor_pos_ > 0) { 358 if (render_text_->GetCursor() > 0) {
516 ExecuteAndRecordDelete(cursor_pos_, cursor_pos_ - 1, true); 359 size_t cursor_position = render_text_->GetCursor();
360 ExecuteAndRecordDelete(cursor_position, cursor_position - 1, true);
517 return true; 361 return true;
518 } 362 }
519 return false; 363 return false;
520 } 364 }
521 365
522 void TextfieldViewsModel::MoveCursorLeft(bool select) { 366 size_t TextfieldViewsModel::GetCursorPosition() const {
367 return render_text_->GetCursor();
368 }
369
370 void TextfieldViewsModel::MoveCursorLeft(gfx::BreakType break_type,
371 bool select) {
523 if (HasCompositionText()) 372 if (HasCompositionText())
524 ConfirmCompositionText(); 373 ConfirmCompositionText();
525 // TODO(oshima): support BIDI 374 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 } 375 }
537 376
538 void TextfieldViewsModel::MoveCursorRight(bool select) { 377 void TextfieldViewsModel::MoveCursorRight(gfx::BreakType break_type,
378 bool select) {
539 if (HasCompositionText()) 379 if (HasCompositionText())
540 ConfirmCompositionText(); 380 ConfirmCompositionText();
541 // TODO(oshima): support BIDI 381 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 } 382 }
616 383
617 bool TextfieldViewsModel::MoveCursorTo(size_t pos, bool select) { 384 bool TextfieldViewsModel::MoveCursorTo(size_t pos, bool select) {
618 if (HasCompositionText()) 385 if (HasCompositionText())
619 ConfirmCompositionText(); 386 ConfirmCompositionText();
620 bool changed = cursor_pos_ != pos || select != HasSelection(); 387 return render_text_->MoveCursorTo(pos, select);
621 cursor_pos_ = pos;
622 if (!select)
623 ClearSelection();
624 return changed;
625 } 388 }
626 389
627 gfx::Rect TextfieldViewsModel::GetSelectionBounds(const gfx::Font& font) const { 390 bool TextfieldViewsModel::MoveCursorTo(const gfx::Point& point, bool select) {
628 if (!HasSelection()) 391 if (HasCompositionText())
629 return gfx::Rect(); 392 ConfirmCompositionText();
630 size_t start = std::min(selection_start_, cursor_pos_); 393 return render_text_->MoveCursorTo(point, select);
631 size_t end = std::max(selection_start_, cursor_pos_); 394 }
632 int start_x = font.GetStringWidth(text_.substr(0, start)); 395
633 int end_x = font.GetStringWidth(text_.substr(0, end)); 396 std::vector<gfx::Rect> TextfieldViewsModel::GetSelectionBounds() const {
634 return gfx::Rect(start_x, 0, end_x - start_x, font.GetHeight()); 397 return render_text_->GetSubstringBounds(render_text_->GetSelection());
635 } 398 }
636 399
637 string16 TextfieldViewsModel::GetSelectedText() const { 400 string16 TextfieldViewsModel::GetSelectedText() const {
638 return text_.substr( 401 ui::Range selection = render_text_->GetSelection();
639 std::min(cursor_pos_, selection_start_), 402 return GetText().substr(selection.GetMin(), selection.length());
640 std::abs(static_cast<long>(cursor_pos_ - selection_start_)));
641 } 403 }
642 404
643 void TextfieldViewsModel::GetSelectedRange(ui::Range* range) const { 405 void TextfieldViewsModel::GetSelectedRange(ui::Range* range) const {
644 *range = ui::Range(selection_start_, cursor_pos_); 406 *range = render_text_->GetSelection();
645 } 407 }
646 408
647 void TextfieldViewsModel::SelectRange(const ui::Range& range) { 409 void TextfieldViewsModel::SelectRange(const ui::Range& range) {
648 if (HasCompositionText()) 410 if (HasCompositionText())
649 ConfirmCompositionText(); 411 ConfirmCompositionText();
650 selection_start_ = GetSafePosition(range.start()); 412 render_text_->SetSelection(range);
651 cursor_pos_ = GetSafePosition(range.end());
652 } 413 }
653 414
654 void TextfieldViewsModel::SelectAll() { 415 void TextfieldViewsModel::SelectAll() {
655 if (HasCompositionText()) 416 if (HasCompositionText())
656 ConfirmCompositionText(); 417 ConfirmCompositionText();
657 // SelectAll selects towards the end. 418 render_text_->SelectAll();
658 cursor_pos_ = text_.length();
659 selection_start_ = 0;
660 } 419 }
661 420
662 void TextfieldViewsModel::SelectWord() { 421 void TextfieldViewsModel::SelectWord() {
663 if (HasCompositionText()) 422 if (HasCompositionText())
664 ConfirmCompositionText(); 423 ConfirmCompositionText();
665 // First we setup selection_start_ and cursor_pos_. There are so many cases 424 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 } 425 }
698 426
699 void TextfieldViewsModel::ClearSelection() { 427 void TextfieldViewsModel::ClearSelection() {
700 if (HasCompositionText()) 428 if (HasCompositionText())
701 ConfirmCompositionText(); 429 ConfirmCompositionText();
702 selection_start_ = cursor_pos_; 430 render_text_->ClearSelection();
703 } 431 }
704 432
705 bool TextfieldViewsModel::CanUndo() { 433 bool TextfieldViewsModel::CanUndo() {
706 return edit_history_.size() && current_edit_ != edit_history_.end(); 434 return edit_history_.size() && current_edit_ != edit_history_.end();
707 } 435 }
708 436
709 bool TextfieldViewsModel::CanRedo() { 437 bool TextfieldViewsModel::CanRedo() {
710 if (!edit_history_.size()) 438 if (!edit_history_.size())
711 return false; 439 return false;
712 // There is no redo iff the current edit is the last element 440 // There is no redo iff the current edit is the last element
713 // in the history. 441 // in the history.
714 EditHistory::iterator iter = current_edit_; 442 EditHistory::iterator iter = current_edit_;
715 return iter == edit_history_.end() || // at the top. 443 return iter == edit_history_.end() || // at the top.
716 ++iter != edit_history_.end(); 444 ++iter != edit_history_.end();
717 } 445 }
718 446
719 bool TextfieldViewsModel::Undo() { 447 bool TextfieldViewsModel::Undo() {
720 if (!CanUndo()) 448 if (!CanUndo())
721 return false; 449 return false;
722 DCHECK(!HasCompositionText()); 450 DCHECK(!HasCompositionText());
723 if (HasCompositionText()) // safe guard for release build. 451 if (HasCompositionText()) // safe guard for release build.
724 CancelCompositionText(); 452 CancelCompositionText();
725 453
726 string16 old = text_; 454 string16 old = GetText();
727 size_t old_cursor = cursor_pos_; 455 size_t old_cursor = render_text_->GetCursor();
728 (*current_edit_)->Commit(); 456 (*current_edit_)->Commit();
729 (*current_edit_)->Undo(this); 457 (*current_edit_)->Undo(this);
730 458
731 if (current_edit_ == edit_history_.begin()) 459 if (current_edit_ == edit_history_.begin())
732 current_edit_ = edit_history_.end(); 460 current_edit_ = edit_history_.end();
733 else 461 else
734 current_edit_--; 462 current_edit_--;
735 return old != text_ || old_cursor != cursor_pos_; 463 return old != GetText() || old_cursor != render_text_->GetCursor();
736 } 464 }
737 465
738 bool TextfieldViewsModel::Redo() { 466 bool TextfieldViewsModel::Redo() {
739 if (!CanRedo()) 467 if (!CanRedo())
740 return false; 468 return false;
741 DCHECK(!HasCompositionText()); 469 DCHECK(!HasCompositionText());
742 if (HasCompositionText()) // safe guard for release build. 470 if (HasCompositionText()) // safe guard for release build.
743 CancelCompositionText(); 471 CancelCompositionText();
744 472
745 if (current_edit_ == edit_history_.end()) 473 if (current_edit_ == edit_history_.end())
746 current_edit_ = edit_history_.begin(); 474 current_edit_ = edit_history_.begin();
747 else 475 else
748 current_edit_ ++; 476 current_edit_ ++;
749 string16 old = text_; 477 string16 old = GetText();
750 size_t old_cursor = cursor_pos_; 478 size_t old_cursor = render_text_->GetCursor();
751 (*current_edit_)->Redo(this); 479 (*current_edit_)->Redo(this);
752 return old != text_ || old_cursor != cursor_pos_; 480 return old != GetText() || old_cursor != render_text_->GetCursor();
481 }
482
483 string16 TextfieldViewsModel::GetVisibleText() const {
484 return GetVisibleText(0U, GetText().length());
753 } 485 }
754 486
755 bool TextfieldViewsModel::Cut() { 487 bool TextfieldViewsModel::Cut() {
756 if (!HasCompositionText() && HasSelection()) { 488 if (!HasCompositionText() && HasSelection()) {
757 ui::ScopedClipboardWriter(views::ViewsDelegate::views_delegate 489 ui::ScopedClipboardWriter(views::ViewsDelegate::views_delegate
758 ->GetClipboard()).WriteText(GetSelectedText()); 490 ->GetClipboard()).WriteText(GetSelectedText());
759 // A trick to let undo/redo handle cursor correctly. 491 // A trick to let undo/redo handle cursor correctly.
760 // Undoing CUT moves the cursor to the end of the change rather 492 // Undoing CUT moves the cursor to the end of the change rather
761 // than beginning, unlike Delete/Backspace. 493 // than beginning, unlike Delete/Backspace.
762 // TODO(oshima): Change Delete/Backspace to use DeleteSelection, 494 // TODO(oshima): Change Delete/Backspace to use DeleteSelection,
763 // update DeleteEdit and remove this trick. 495 // update DeleteEdit and remove this trick.
764 std::swap(cursor_pos_, selection_start_); 496 ui::Range selection = render_text_->GetSelection();
497 render_text_->SetSelection(ui::Range(selection.end(), selection.start()));
oshima 2011/07/23 09:51:12 doesn't range have a method to swap position?
msw 2011/07/25 05:09:54 Not that I see.
765 DeleteSelection(); 498 DeleteSelection();
766 return true; 499 return true;
767 } 500 }
768 return false; 501 return false;
769 } 502 }
770 503
771 void TextfieldViewsModel::Copy() { 504 void TextfieldViewsModel::Copy() {
772 if (!HasCompositionText() && HasSelection()) { 505 if (!HasCompositionText() && HasSelection()) {
773 ui::ScopedClipboardWriter(views::ViewsDelegate::views_delegate 506 ui::ScopedClipboardWriter(views::ViewsDelegate::views_delegate
774 ->GetClipboard()).WriteText(GetSelectedText()); 507 ->GetClipboard()).WriteText(GetSelectedText());
775 } 508 }
776 } 509 }
777 510
778 bool TextfieldViewsModel::Paste() { 511 bool TextfieldViewsModel::Paste() {
779 string16 result; 512 string16 result;
780 views::ViewsDelegate::views_delegate->GetClipboard() 513 views::ViewsDelegate::views_delegate->GetClipboard()
781 ->ReadText(ui::Clipboard::BUFFER_STANDARD, &result); 514 ->ReadText(ui::Clipboard::BUFFER_STANDARD, &result);
782 if (!result.empty()) { 515 if (!result.empty()) {
783 InsertTextInternal(result, false); 516 InsertTextInternal(result, false);
784 return true; 517 return true;
785 } 518 }
786 return false; 519 return false;
787 } 520 }
788 521
789 bool TextfieldViewsModel::HasSelection() const { 522 bool TextfieldViewsModel::HasSelection() const {
790 return selection_start_ != cursor_pos_; 523 return !render_text_->GetSelection().is_empty();
791 } 524 }
792 525
793 void TextfieldViewsModel::DeleteSelection() { 526 void TextfieldViewsModel::DeleteSelection() {
794 DCHECK(!HasCompositionText()); 527 DCHECK(!HasCompositionText());
795 DCHECK(HasSelection()); 528 DCHECK(HasSelection());
796 ExecuteAndRecordDelete(selection_start_, cursor_pos_, false); 529 ui::Range selection = render_text_->GetSelection();
530 ExecuteAndRecordDelete(selection.start(), selection.end(), false);
797 } 531 }
798 532
799 void TextfieldViewsModel::DeleteSelectionAndInsertTextAt( 533 void TextfieldViewsModel::DeleteSelectionAndInsertTextAt(
800 const string16& text, size_t position) { 534 const string16& text, size_t position) {
801 if (HasCompositionText()) 535 if (HasCompositionText())
802 CancelCompositionText(); 536 CancelCompositionText();
803 ExecuteAndRecordReplace(DO_NOT_MERGE, 537 ExecuteAndRecordReplace(DO_NOT_MERGE,
804 cursor_pos_, 538 render_text_->GetCursor(),
805 position + text.length(), 539 position + text.length(),
806 text, 540 text,
807 position); 541 position);
808 } 542 }
809 543
810 string16 TextfieldViewsModel::GetTextFromRange(const ui::Range& range) const { 544 string16 TextfieldViewsModel::GetTextFromRange(const ui::Range& range) const {
811 if (range.IsValid() && range.GetMin() < text_.length()) 545 if (range.IsValid() && range.GetMin() < GetText().length())
812 return text_.substr(range.GetMin(), range.length()); 546 return GetText().substr(range.GetMin(), range.length());
813 return string16(); 547 return string16();
814 } 548 }
815 549
816 void TextfieldViewsModel::GetTextRange(ui::Range* range) const { 550 void TextfieldViewsModel::GetTextRange(ui::Range* range) const {
817 *range = ui::Range(0, text_.length()); 551 *range = ui::Range(0, GetText().length());
818 } 552 }
819 553
820 void TextfieldViewsModel::SetCompositionText( 554 void TextfieldViewsModel::SetCompositionText(
821 const ui::CompositionText& composition) { 555 const ui::CompositionText& composition) {
822 static const TextStyle* composition_style = CreateUnderlineStyle();
823
824 if (HasCompositionText()) 556 if (HasCompositionText())
825 CancelCompositionText(); 557 CancelCompositionText();
826 else if (HasSelection()) 558 else if (HasSelection())
827 DeleteSelection(); 559 DeleteSelection();
828 560
829 if (composition.text.empty()) 561 if (composition.text.empty())
830 return; 562 return;
831 563
832 size_t length = composition.text.length(); 564 size_t cursor = render_text_->GetCursor();
833 text_.insert(cursor_pos_, composition.text); 565 string16 new_text = GetText();
834 composition_start_ = cursor_pos_; 566 render_text_->SetText(new_text.insert(cursor, composition.text));
835 composition_end_ = composition_start_ + length; 567 ui::Range range(cursor, cursor + composition.text.length());
836 for (ui::CompositionUnderlines::const_iterator iter = 568 render_text_->SetCompositionRange(range);
837 composition.underlines.begin(); 569 // 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 570
848 if (composition.selection.IsValid()) { 571 if (composition.selection.IsValid())
849 selection_start_ = 572 render_text_->SetSelection(ui::Range(
850 std::min(composition_start_ + composition.selection.start(), 573 std::min(range.start() + composition.selection.start(), range.end()),
851 composition_end_); 574 std::min(range.start() + composition.selection.end(), range.end())));
852 cursor_pos_ = 575 else
853 std::min(composition_start_ + composition.selection.end(), 576 render_text_->SetCursor(range.end());
854 composition_end_);
855 } else {
856 cursor_pos_ = composition_end_;
857 ClearSelection();
858 }
859 } 577 }
860 578
861 void TextfieldViewsModel::ConfirmCompositionText() { 579 void TextfieldViewsModel::ConfirmCompositionText() {
862 DCHECK(HasCompositionText()); 580 DCHECK(HasCompositionText());
863 string16 new_text = 581 ui::Range range = render_text_->GetCompositionRange();
864 text_.substr(composition_start_, composition_end_ - composition_start_); 582 string16 text = GetText().substr(range.start(), range.length());
865 // TODO(oshima): current behavior on ChromeOS is a bit weird and not 583 // 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. 584 // sure exactly how this should work. Find out and fix if necessary.
867 AddOrMergeEditHistory(new InsertEdit(false, new_text, composition_start_)); 585 AddOrMergeEditHistory(new InsertEdit(false, text, range.start()));
868 cursor_pos_ = composition_end_; 586 render_text_->SetCursor(range.end());
869 ClearComposition(); 587 ClearComposition();
870 ClearSelection();
871 if (delegate_) 588 if (delegate_)
872 delegate_->OnCompositionTextConfirmedOrCleared(); 589 delegate_->OnCompositionTextConfirmedOrCleared();
873 } 590 }
874 591
875 void TextfieldViewsModel::CancelCompositionText() { 592 void TextfieldViewsModel::CancelCompositionText() {
876 DCHECK(HasCompositionText()); 593 DCHECK(HasCompositionText());
877 text_.erase(composition_start_, composition_end_ - composition_start_); 594 ui::Range range = render_text_->GetCompositionRange();
878 cursor_pos_ = composition_start_; 595 string16 new_text = GetText();
596 render_text_->SetText(new_text.erase(range.start(), range.length()));
597 render_text_->SetCursor(range.start());
879 ClearComposition(); 598 ClearComposition();
880 ClearSelection();
881 if (delegate_) 599 if (delegate_)
882 delegate_->OnCompositionTextConfirmedOrCleared(); 600 delegate_->OnCompositionTextConfirmedOrCleared();
883 } 601 }
884 602
885 void TextfieldViewsModel::ClearComposition() { 603 void TextfieldViewsModel::ClearComposition() {
886 composition_start_ = composition_end_ = string16::npos; 604 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 } 605 }
900 606
901 void TextfieldViewsModel::GetCompositionTextRange(ui::Range* range) const { 607 void TextfieldViewsModel::GetCompositionTextRange(ui::Range* range) const {
902 if (HasCompositionText()) 608 *range = ui::Range(render_text_->GetCompositionRange());
903 *range = ui::Range(composition_start_, composition_end_);
904 else
905 *range = ui::Range::InvalidRange();
906 } 609 }
907 610
908 bool TextfieldViewsModel::HasCompositionText() const { 611 bool TextfieldViewsModel::HasCompositionText() const {
909 return composition_start_ != composition_end_; 612 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 } 613 }
922 614
923 ///////////////////////////////////////////////////////////////// 615 /////////////////////////////////////////////////////////////////
924 // TextfieldViewsModel: private 616 // TextfieldViewsModel: private
925 617
926 string16 TextfieldViewsModel::GetVisibleText(size_t begin, size_t end) const { 618 string16 TextfieldViewsModel::GetVisibleText(size_t begin, size_t end) const {
927 DCHECK(end >= begin); 619 DCHECK(end >= begin);
928 if (is_password_) 620 if (is_password_)
929 return string16(end - begin, '*'); 621 return string16(end - begin, '*');
930 return text_.substr(begin, end - begin); 622 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 } 623 }
944 624
945 void TextfieldViewsModel::InsertTextInternal(const string16& text, 625 void TextfieldViewsModel::InsertTextInternal(const string16& text,
946 bool mergeable) { 626 bool mergeable) {
947 if (HasCompositionText()) { 627 if (HasCompositionText()) {
948 CancelCompositionText(); 628 CancelCompositionText();
949 ExecuteAndRecordInsert(text, mergeable); 629 ExecuteAndRecordInsert(text, mergeable);
950 } else if (HasSelection()) { 630 } else if (HasSelection()) {
951 ExecuteAndRecordReplaceSelection(mergeable ? MERGEABLE : DO_NOT_MERGE, 631 ExecuteAndRecordReplaceSelection(mergeable ? MERGEABLE : DO_NOT_MERGE,
952 text); 632 text);
953 } else { 633 } else {
954 ExecuteAndRecordInsert(text, mergeable); 634 ExecuteAndRecordInsert(text, mergeable);
955 } 635 }
956 } 636 }
957 637
958 void TextfieldViewsModel::ReplaceTextInternal(const string16& text, 638 void TextfieldViewsModel::ReplaceTextInternal(const string16& text,
959 bool mergeable) { 639 bool mergeable) {
960 if (HasCompositionText()) 640 if (HasCompositionText())
961 CancelCompositionText(); 641 CancelCompositionText();
962 else if (!HasSelection()) 642 else if (!HasSelection()) {
oshima 2011/07/23 09:51:12 you need {} for the 1st if, i believe. check style
msw 2011/07/25 05:09:54 Done.
963 SelectRange(ui::Range(cursor_pos_ + text.length(), cursor_pos_)); 643 size_t cursor = render_text_->GetCursor();
644 render_text_->SetSelection(ui::Range(cursor + text.length(), cursor));
645 }
964 // Edit history is recorded in InsertText. 646 // Edit history is recorded in InsertText.
965 InsertTextInternal(text, mergeable); 647 InsertTextInternal(text, mergeable);
966 } 648 }
967 649
968 void TextfieldViewsModel::ClearEditHistory() { 650 void TextfieldViewsModel::ClearEditHistory() {
969 STLDeleteContainerPointers(edit_history_.begin(), 651 STLDeleteContainerPointers(edit_history_.begin(),
970 edit_history_.end()); 652 edit_history_.end());
971 edit_history_.clear(); 653 edit_history_.clear();
972 current_edit_ = edit_history_.end(); 654 current_edit_ = edit_history_.end();
973 } 655 }
974 656
975 void TextfieldViewsModel::ClearRedoHistory() { 657 void TextfieldViewsModel::ClearRedoHistory() {
976 if (edit_history_.begin() == edit_history_.end()) 658 if (edit_history_.begin() == edit_history_.end())
977 return; 659 return;
978 if (current_edit_ == edit_history_.end()) { 660 if (current_edit_ == edit_history_.end()) {
979 ClearEditHistory(); 661 ClearEditHistory();
980 return; 662 return;
981 } 663 }
982 EditHistory::iterator delete_start = current_edit_; 664 EditHistory::iterator delete_start = current_edit_;
983 delete_start++; 665 delete_start++;
984 STLDeleteContainerPointers(delete_start, edit_history_.end()); 666 STLDeleteContainerPointers(delete_start, edit_history_.end());
985 edit_history_.erase(delete_start, edit_history_.end()); 667 edit_history_.erase(delete_start, edit_history_.end());
986 } 668 }
987 669
988 void TextfieldViewsModel::ExecuteAndRecordDelete(size_t from, 670 void TextfieldViewsModel::ExecuteAndRecordDelete(size_t from,
989 size_t to, 671 size_t to,
990 bool mergeable) { 672 bool mergeable) {
991 size_t old_text_start = std::min(from, to); 673 size_t old_text_start = std::min(from, to);
992 const string16 text = text_.substr(old_text_start, 674 const string16 text = GetText().substr(old_text_start,
993 std::abs(static_cast<long>(from - to))); 675 std::abs(static_cast<long>(from - to)));
994 bool backward = from > to; 676 bool backward = from > to;
995 Edit* edit = new DeleteEdit(mergeable, text, old_text_start, backward); 677 Edit* edit = new DeleteEdit(mergeable, text, old_text_start, backward);
996 bool delete_edit = AddOrMergeEditHistory(edit); 678 bool delete_edit = AddOrMergeEditHistory(edit);
997 edit->Redo(this); 679 edit->Redo(this);
998 if (delete_edit) 680 if (delete_edit)
999 delete edit; 681 delete edit;
1000 } 682 }
1001 683
1002 void TextfieldViewsModel::ExecuteAndRecordReplaceSelection( 684 void TextfieldViewsModel::ExecuteAndRecordReplaceSelection(
1003 MergeType merge_type, const string16& new_text) { 685 MergeType merge_type, const string16& new_text) {
1004 size_t new_text_start = std::min(cursor_pos_, selection_start_); 686 size_t new_text_start = render_text_->GetSelection().GetMin();
1005 size_t new_cursor_pos = new_text_start + new_text.length(); 687 size_t new_cursor_pos = new_text_start + new_text.length();
1006 ExecuteAndRecordReplace(merge_type, 688 ExecuteAndRecordReplace(merge_type,
1007 cursor_pos_, 689 render_text_->GetCursor(),
1008 new_cursor_pos, 690 new_cursor_pos,
1009 new_text, 691 new_text,
1010 new_text_start); 692 new_text_start);
1011 } 693 }
1012 694
1013 void TextfieldViewsModel::ExecuteAndRecordReplace(MergeType merge_type, 695 void TextfieldViewsModel::ExecuteAndRecordReplace(MergeType merge_type,
1014 size_t old_cursor_pos, 696 size_t old_cursor_pos,
1015 size_t new_cursor_pos, 697 size_t new_cursor_pos,
1016 const string16& new_text, 698 const string16& new_text,
1017 size_t new_text_start) { 699 size_t new_text_start) {
1018 size_t old_text_start = std::min(cursor_pos_, selection_start_); 700 size_t old_text_start = render_text_->GetSelection().GetMin();
1019 bool backward = selection_start_ > cursor_pos_; 701 bool backward = render_text_->GetSelection().is_reversed();
1020 Edit* edit = new ReplaceEdit(merge_type, 702 Edit* edit = new ReplaceEdit(merge_type,
1021 GetSelectedText(), 703 GetSelectedText(),
1022 old_cursor_pos, 704 old_cursor_pos,
1023 old_text_start, 705 old_text_start,
1024 backward, 706 backward,
1025 new_cursor_pos, 707 new_cursor_pos,
1026 new_text, 708 new_text,
1027 new_text_start); 709 new_text_start);
1028 bool delete_edit = AddOrMergeEditHistory(edit); 710 bool delete_edit = AddOrMergeEditHistory(edit);
1029 edit->Redo(this); 711 edit->Redo(this);
1030 if (delete_edit) 712 if (delete_edit)
1031 delete edit; 713 delete edit;
1032 } 714 }
1033 715
1034 void TextfieldViewsModel::ExecuteAndRecordInsert(const string16& text, 716 void TextfieldViewsModel::ExecuteAndRecordInsert(const string16& text,
1035 bool mergeable) { 717 bool mergeable) {
1036 Edit* edit = new InsertEdit(mergeable, text, cursor_pos_); 718 Edit* edit = new InsertEdit(mergeable, text, render_text_->GetCursor());
1037 bool delete_edit = AddOrMergeEditHistory(edit); 719 bool delete_edit = AddOrMergeEditHistory(edit);
1038 edit->Redo(this); 720 edit->Redo(this);
1039 if (delete_edit) 721 if (delete_edit)
1040 delete edit; 722 delete edit;
1041 } 723 }
1042 724
1043 bool TextfieldViewsModel::AddOrMergeEditHistory(Edit* edit) { 725 bool TextfieldViewsModel::AddOrMergeEditHistory(Edit* edit) {
1044 ClearRedoHistory(); 726 ClearRedoHistory();
1045 727
1046 if (current_edit_ != edit_history_.end() && (*current_edit_)->Merge(edit)) { 728 if (current_edit_ != edit_history_.end() && (*current_edit_)->Merge(edit)) {
(...skipping 13 matching lines...) Expand all
1060 } 742 }
1061 return false; 743 return false;
1062 } 744 }
1063 745
1064 void TextfieldViewsModel::ModifyText(size_t delete_from, 746 void TextfieldViewsModel::ModifyText(size_t delete_from,
1065 size_t delete_to, 747 size_t delete_to,
1066 const string16& new_text, 748 const string16& new_text,
1067 size_t new_text_insert_at, 749 size_t new_text_insert_at,
1068 size_t new_cursor_pos) { 750 size_t new_cursor_pos) {
1069 DCHECK_LE(delete_from, delete_to); 751 DCHECK_LE(delete_from, delete_to);
752 string16 text = GetText();
1070 if (delete_from != delete_to) 753 if (delete_from != delete_to)
1071 text_.erase(delete_from, delete_to - delete_from); 754 render_text_->SetText(text.erase(delete_from, delete_to - delete_from));
1072 if (!new_text.empty()) 755 if (!new_text.empty())
1073 text_.insert(new_text_insert_at, new_text); 756 render_text_->SetText(text.insert(new_text_insert_at, new_text));
1074 cursor_pos_ = new_cursor_pos; 757 render_text_->SetCursor(new_cursor_pos);
1075 ClearSelection();
1076 // TODO(oshima): mac selects the text that is just undone (but gtk doesn't). 758 // 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. 759 // This looks fine feature and we may want to do the same.
1078 } 760 }
1079 761
1080 // static
1081 TextStyle* TextfieldViewsModel::CreateUnderlineStyle() {
1082 TextStyle* style = new TextStyle();
1083 style->set_underline(true);
1084 return style;
1085 }
1086
1087 } // namespace views 762 } // namespace views
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698