| 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 |