OLD | NEW |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "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 Loading... |
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 Loading... |
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 |
OLD | NEW |