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

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

Powered by Google App Engine
This is Rietveld 408576698