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-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 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(); | 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 Loading... |
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 |
OLD | NEW |