| OLD | NEW |
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "views/controls/textfield/textfield_views_model.h" | 5 #include "views/controls/textfield/textfield_views_model.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 | 8 |
| 9 #include "base/i18n/break_iterator.h" | 9 #include "base/i18n/break_iterator.h" |
| 10 #include "base/logging.h" | 10 #include "base/logging.h" |
| 11 #include "base/stl_util.h" | 11 #include "base/stl_util.h" |
| 12 #include "base/utf_string_conversions.h" | 12 #include "base/utf_string_conversions.h" |
| 13 #include "ui/base/clipboard/clipboard.h" | 13 #include "ui/base/clipboard/clipboard.h" |
| 14 #include "ui/base/clipboard/scoped_clipboard_writer.h" | 14 #include "ui/base/clipboard/scoped_clipboard_writer.h" |
| 15 #include "ui/base/range/range.h" | 15 #include "ui/base/range/range.h" |
| 16 #include "ui/gfx/canvas.h" |
| 16 #include "ui/gfx/font.h" | 17 #include "ui/gfx/font.h" |
| 17 #include "views/controls/textfield/text_style.h" | 18 #include "ui/gfx/render_text.h" |
| 18 #include "views/controls/textfield/textfield.h" | 19 #include "views/controls/textfield/textfield.h" |
| 19 #include "views/views_delegate.h" | 20 #include "views/views_delegate.h" |
| 20 | 21 |
| 21 namespace views { | 22 namespace views { |
| 22 | 23 |
| 23 namespace internal { | 24 namespace internal { |
| 24 | 25 |
| 25 // An edit object holds enough information/state to undo/redo the | 26 // An edit object holds enough information/state to undo/redo the |
| 26 // change. Two edits are merged when possible, for example, when | 27 // change. Two edits are merged when possible, for example, when |
| 27 // you type new characters in sequence. |Commit()| can be used to | 28 // you type new characters in sequence. |Commit()| can be used to |
| (...skipping 221 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 249 // delete can be merged only with delete at the same | 250 // delete can be merged only with delete at the same |
| 250 // position. | 251 // position. |
| 251 if (edit->delete_backward_ || old_text_start_ != edit->old_text_start_) | 252 if (edit->delete_backward_ || old_text_start_ != edit->old_text_start_) |
| 252 return false; | 253 return false; |
| 253 old_text_ += edit->old_text_; | 254 old_text_ += edit->old_text_; |
| 254 } | 255 } |
| 255 return true; | 256 return true; |
| 256 } | 257 } |
| 257 }; | 258 }; |
| 258 | 259 |
| 259 struct TextStyleRange { | |
| 260 TextStyleRange(const TextStyle* s, size_t start, size_t end) | |
| 261 : style(s), | |
| 262 range(start, end) { | |
| 263 } | |
| 264 TextStyleRange(const TextStyle* s, const ui::Range& r) | |
| 265 : style(s), | |
| 266 range(r) { | |
| 267 } | |
| 268 const TextStyle *style; | |
| 269 ui::Range range; | |
| 270 }; | |
| 271 | |
| 272 } // namespace internal | 260 } // namespace internal |
| 273 | 261 |
| 274 namespace { | |
| 275 | |
| 276 using views::internal::TextStyleRange; | |
| 277 | |
| 278 static bool TextStyleRangeComparator(const TextStyleRange* i, | |
| 279 const TextStyleRange* j) { | |
| 280 return i->range.start() < j->range.start(); | |
| 281 } | |
| 282 | |
| 283 #ifndef NDEBUG | |
| 284 // A test function to check TextStyleRanges' invariant condition: | |
| 285 // "no overlapping range". | |
| 286 bool CheckInvariant(const TextStyleRanges* style_ranges) { | |
| 287 TextStyleRanges copy = *style_ranges; | |
| 288 std::sort(copy.begin(), copy.end(), TextStyleRangeComparator); | |
| 289 | |
| 290 for (TextStyleRanges::size_type i = 0; i < copy.size() - 1; i++) { | |
| 291 ui::Range& former = copy[i]->range; | |
| 292 ui::Range& latter = copy[i + 1]->range; | |
| 293 if (former.is_empty()) { | |
| 294 LOG(WARNING) << "Empty range at " << i << " :" << former; | |
| 295 return false; | |
| 296 } | |
| 297 if (!former.IsValid()) { | |
| 298 LOG(WARNING) << "Invalid range at " << i << " :" << former; | |
| 299 return false; | |
| 300 } | |
| 301 if (former.GetMax() > latter.GetMin()) { | |
| 302 LOG(WARNING) << | |
| 303 "Sorting error. former:" << former << " latter:" << latter; | |
| 304 return false; | |
| 305 } | |
| 306 if (former.Intersects(latter)) { | |
| 307 LOG(ERROR) << "overlapping style range found: former=" << former | |
| 308 << ", latter=" << latter; | |
| 309 return false; | |
| 310 } | |
| 311 } | |
| 312 if ((*copy.rbegin())->range.is_empty()) { | |
| 313 LOG(WARNING) << "Empty range at end"; | |
| 314 return false; | |
| 315 } | |
| 316 if (!(*copy.rbegin())->range.IsValid()) { | |
| 317 LOG(WARNING) << "Invalid range at end"; | |
| 318 return false; | |
| 319 } | |
| 320 return true; | |
| 321 } | |
| 322 #endif | |
| 323 | |
| 324 void InsertStyle(TextStyleRanges* style_ranges, | |
| 325 TextStyleRange* text_style_range) { | |
| 326 const ui::Range& range = text_style_range->range; | |
| 327 if (range.is_empty() || !range.IsValid()) { | |
| 328 delete text_style_range; | |
| 329 return; | |
| 330 } | |
| 331 CHECK(!range.is_reversed()); | |
| 332 | |
| 333 // Invariant condition: all items in the range has no overlaps. | |
| 334 TextStyleRanges::size_type index = 0; | |
| 335 while (index < style_ranges->size()) { | |
| 336 TextStyleRange* current = (*style_ranges)[index]; | |
| 337 if (range.Contains(current->range)) { | |
| 338 style_ranges->erase(style_ranges->begin() + index); | |
| 339 delete current; | |
| 340 continue; | |
| 341 } else if (current->range.Contains(range) && | |
| 342 range.start() != current->range.start() && | |
| 343 range.end() != current->range.end()) { | |
| 344 // Split current style into two styles. | |
| 345 style_ranges->push_back( | |
| 346 new TextStyleRange(current->style, | |
| 347 range.GetMax(), current->range.GetMax())); | |
| 348 current->range.set_end(range.GetMin()); | |
| 349 } else if (range.Intersects(current->range)) { | |
| 350 if (current->range.GetMax() <= range.GetMax()) { | |
| 351 current->range.set_end(range.GetMin()); | |
| 352 } else { | |
| 353 current->range.set_start(range.GetMax()); | |
| 354 } | |
| 355 } else { | |
| 356 // No change needed. Pass it through. | |
| 357 } | |
| 358 index ++; | |
| 359 } | |
| 360 // Add the new range at the end. | |
| 361 style_ranges->push_back(text_style_range); | |
| 362 #ifndef NDEBUG | |
| 363 DCHECK(CheckInvariant(style_ranges)); | |
| 364 #endif | |
| 365 } | |
| 366 | |
| 367 } // namespace | |
| 368 | |
| 369 using internal::Edit; | 262 using internal::Edit; |
| 370 using internal::DeleteEdit; | 263 using internal::DeleteEdit; |
| 371 using internal::InsertEdit; | 264 using internal::InsertEdit; |
| 372 using internal::ReplaceEdit; | 265 using internal::ReplaceEdit; |
| 373 using internal::MergeType; | 266 using internal::MergeType; |
| 374 using internal::DO_NOT_MERGE; | 267 using internal::DO_NOT_MERGE; |
| 375 using internal::MERGE_WITH_PREVIOUS; | 268 using internal::MERGE_WITH_PREVIOUS; |
| 376 using internal::MERGEABLE; | 269 using internal::MERGEABLE; |
| 377 | 270 |
| 378 ///////////////////////////////////////////////////////////////// | 271 ///////////////////////////////////////////////////////////////// |
| 379 // TextfieldViewsModel: public | 272 // TextfieldViewsModel: public |
| 380 | 273 |
| 381 TextfieldViewsModel::Delegate::~Delegate() { | 274 TextfieldViewsModel::Delegate::~Delegate() { |
| 382 } | 275 } |
| 383 | 276 |
| 384 TextfieldViewsModel::TextfieldViewsModel(Delegate* delegate) | 277 TextfieldViewsModel::TextfieldViewsModel(Delegate* delegate) |
| 385 : delegate_(delegate), | 278 : delegate_(delegate), |
| 386 cursor_pos_(0), | 279 render_text_(gfx::RenderText::CreateRenderText()), |
| 387 selection_start_(0), | |
| 388 composition_start_(0), | |
| 389 composition_end_(0), | |
| 390 is_password_(false), | 280 is_password_(false), |
| 391 current_edit_(edit_history_.end()), | 281 current_edit_(edit_history_.end()) { |
| 392 sort_style_ranges_(false) { | |
| 393 } | 282 } |
| 394 | 283 |
| 395 TextfieldViewsModel::~TextfieldViewsModel() { | 284 TextfieldViewsModel::~TextfieldViewsModel() { |
| 396 ClearEditHistory(); | 285 ClearEditHistory(); |
| 397 ClearComposition(); | 286 ClearComposition(); |
| 398 ClearAllTextStyles(); | |
| 399 TextStyles::iterator begin = text_styles_.begin(); | |
| 400 TextStyles::iterator end = text_styles_.end(); | |
| 401 while (begin != end) { | |
| 402 TextStyles::iterator temp = begin; | |
| 403 ++begin; | |
| 404 delete *temp; | |
| 405 } | |
| 406 } | 287 } |
| 407 | 288 |
| 408 void TextfieldViewsModel::GetFragments(TextFragments* fragments) { | 289 const string16& TextfieldViewsModel::GetText() const { |
| 409 static const TextStyle* kNormalStyle = new TextStyle(); | 290 return render_text_->text(); |
| 410 | |
| 411 if (sort_style_ranges_) { | |
| 412 sort_style_ranges_ = false; | |
| 413 std::sort(style_ranges_.begin(), style_ranges_.end(), | |
| 414 TextStyleRangeComparator); | |
| 415 } | |
| 416 | |
| 417 // If a user is compositing text, use composition's style. | |
| 418 // TODO(oshima): ask suzhe for expected behavior. | |
| 419 const TextStyleRanges& ranges = composition_style_ranges_.size() > 0 ? | |
| 420 composition_style_ranges_ : style_ranges_; | |
| 421 TextStyleRanges::const_iterator next_ = ranges.begin(); | |
| 422 | |
| 423 DCHECK(fragments); | |
| 424 fragments->clear(); | |
| 425 size_t current = 0; | |
| 426 size_t end = text_.length(); | |
| 427 while(next_ != ranges.end()) { | |
| 428 const TextStyleRange* text_style_range = *next_++; | |
| 429 const ui::Range& range = text_style_range->range; | |
| 430 const TextStyle* style = text_style_range->style; | |
| 431 | |
| 432 DCHECK(!range.is_empty()); | |
| 433 DCHECK(range.IsValid()); | |
| 434 if (range.is_empty() || !range.IsValid()) | |
| 435 continue; | |
| 436 | |
| 437 size_t start = std::min(range.start(), end); | |
| 438 | |
| 439 if (start == end) // Exit loop if it reached the end. | |
| 440 break; | |
| 441 else if (current < start) // Fill the gap to next style with normal text. | |
| 442 fragments->push_back(TextFragment(current, start, kNormalStyle)); | |
| 443 | |
| 444 current = std::min(range.end(), end); | |
| 445 fragments->push_back(TextFragment(start, current, style)); | |
| 446 } | |
| 447 // If there is any text left add it as normal text. | |
| 448 if (current != end) | |
| 449 fragments->push_back(TextFragment(current, end, kNormalStyle)); | |
| 450 } | 291 } |
| 451 | 292 |
| 452 bool TextfieldViewsModel::SetText(const string16& text) { | 293 bool TextfieldViewsModel::SetText(const string16& text) { |
| 453 bool changed = false; | 294 bool changed = false; |
| 454 if (HasCompositionText()) { | 295 if (HasCompositionText()) { |
| 455 ConfirmCompositionText(); | 296 ConfirmCompositionText(); |
| 456 changed = true; | 297 changed = true; |
| 457 } | 298 } |
| 458 if (text_ != text) { | 299 if (GetText() != text) { |
| 459 if (changed) // No need to remember composition. | 300 if (changed) // No need to remember composition. |
| 460 Undo(); | 301 Undo(); |
| 461 size_t old_cursor = cursor_pos_; | 302 size_t old_cursor = GetCursorPosition(); |
| 462 size_t new_cursor = old_cursor > text.length() ? text.length() : old_cursor; | 303 size_t new_cursor = old_cursor > text.length() ? text.length() : old_cursor; |
| 463 SelectAll(); | 304 SelectAll(); |
| 464 // If there is a composition text, don't merge with previous edit. | 305 // If there is a composition text, don't merge with previous edit. |
| 465 // Otherwise, force merge the edits. | 306 // Otherwise, force merge the edits. |
| 466 ExecuteAndRecordReplace( | 307 ExecuteAndRecordReplace( |
| 467 changed ? DO_NOT_MERGE : MERGE_WITH_PREVIOUS, | 308 changed ? DO_NOT_MERGE : MERGE_WITH_PREVIOUS, |
| 468 old_cursor, | 309 old_cursor, |
| 469 new_cursor, | 310 new_cursor, |
| 470 text, | 311 text, |
| 471 0U); | 312 0U); |
| 472 cursor_pos_ = new_cursor; | 313 render_text_->SetCursorPosition(new_cursor); |
| 473 } | 314 } |
| 474 ClearSelection(); | 315 ClearSelection(); |
| 475 return changed; | 316 return changed; |
| 476 } | 317 } |
| 477 | 318 |
| 478 void TextfieldViewsModel::Append(const string16& text) { | 319 void TextfieldViewsModel::Append(const string16& text) { |
| 479 if (HasCompositionText()) | 320 if (HasCompositionText()) |
| 480 ConfirmCompositionText(); | 321 ConfirmCompositionText(); |
| 481 size_t save = cursor_pos_; | 322 size_t save = GetCursorPosition(); |
| 482 MoveCursorToEnd(false); | 323 MoveCursorRight(gfx::LINE_BREAK, false); |
| 483 InsertText(text); | 324 InsertText(text); |
| 484 cursor_pos_ = save; | 325 render_text_->SetCursorPosition(save); |
| 485 ClearSelection(); | 326 ClearSelection(); |
| 486 } | 327 } |
| 487 | 328 |
| 488 bool TextfieldViewsModel::Delete() { | 329 bool TextfieldViewsModel::Delete() { |
| 489 if (HasCompositionText()) { | 330 if (HasCompositionText()) { |
| 490 // No undo/redo for composition text. | 331 // No undo/redo for composition text. |
| 491 CancelCompositionText(); | 332 CancelCompositionText(); |
| 492 return true; | 333 return true; |
| 493 } | 334 } |
| 494 if (HasSelection()) { | 335 if (HasSelection()) { |
| 495 DeleteSelection(); | 336 DeleteSelection(); |
| 496 return true; | 337 return true; |
| 497 } | 338 } |
| 498 if (text_.length() > cursor_pos_) { | 339 if (GetText().length() > GetCursorPosition()) { |
| 499 ExecuteAndRecordDelete(cursor_pos_, cursor_pos_ + 1, true); | 340 size_t cursor_position = GetCursorPosition(); |
| 341 ExecuteAndRecordDelete(cursor_position, cursor_position + 1, true); |
| 500 return true; | 342 return true; |
| 501 } | 343 } |
| 502 return false; | 344 return false; |
| 503 } | 345 } |
| 504 | 346 |
| 505 bool TextfieldViewsModel::Backspace() { | 347 bool TextfieldViewsModel::Backspace() { |
| 506 if (HasCompositionText()) { | 348 if (HasCompositionText()) { |
| 507 // No undo/redo for composition text. | 349 // No undo/redo for composition text. |
| 508 CancelCompositionText(); | 350 CancelCompositionText(); |
| 509 return true; | 351 return true; |
| 510 } | 352 } |
| 511 if (HasSelection()) { | 353 if (HasSelection()) { |
| 512 DeleteSelection(); | 354 DeleteSelection(); |
| 513 return true; | 355 return true; |
| 514 } | 356 } |
| 515 if (cursor_pos_ > 0) { | 357 if (GetCursorPosition() > 0) { |
| 516 ExecuteAndRecordDelete(cursor_pos_, cursor_pos_ - 1, true); | 358 size_t cursor_position = GetCursorPosition(); |
| 359 ExecuteAndRecordDelete(cursor_position, cursor_position - 1, true); |
| 517 return true; | 360 return true; |
| 518 } | 361 } |
| 519 return false; | 362 return false; |
| 520 } | 363 } |
| 521 | 364 |
| 522 void TextfieldViewsModel::MoveCursorLeft(bool select) { | 365 size_t TextfieldViewsModel::GetCursorPosition() const { |
| 366 return render_text_->GetCursorPosition(); |
| 367 } |
| 368 |
| 369 void TextfieldViewsModel::MoveCursorLeft(gfx::BreakType break_type, |
| 370 bool select) { |
| 523 if (HasCompositionText()) | 371 if (HasCompositionText()) |
| 524 ConfirmCompositionText(); | 372 ConfirmCompositionText(); |
| 525 // TODO(oshima): support BIDI | 373 render_text_->MoveCursorLeft(break_type, select); |
| 526 if (select) { | |
| 527 if (cursor_pos_ > 0) | |
| 528 cursor_pos_--; | |
| 529 } else { | |
| 530 if (HasSelection()) | |
| 531 cursor_pos_ = std::min(cursor_pos_, selection_start_); | |
| 532 else if (cursor_pos_ > 0) | |
| 533 cursor_pos_--; | |
| 534 ClearSelection(); | |
| 535 } | |
| 536 } | 374 } |
| 537 | 375 |
| 538 void TextfieldViewsModel::MoveCursorRight(bool select) { | 376 void TextfieldViewsModel::MoveCursorRight(gfx::BreakType break_type, |
| 377 bool select) { |
| 539 if (HasCompositionText()) | 378 if (HasCompositionText()) |
| 540 ConfirmCompositionText(); | 379 ConfirmCompositionText(); |
| 541 // TODO(oshima): support BIDI | 380 render_text_->MoveCursorRight(break_type, select); |
| 542 if (select) { | |
| 543 cursor_pos_ = std::min(text_.length(), cursor_pos_ + 1); | |
| 544 } else { | |
| 545 if (HasSelection()) | |
| 546 cursor_pos_ = std::max(cursor_pos_, selection_start_); | |
| 547 else | |
| 548 cursor_pos_ = std::min(text_.length(), cursor_pos_ + 1); | |
| 549 ClearSelection(); | |
| 550 } | |
| 551 } | |
| 552 | |
| 553 void TextfieldViewsModel::MoveCursorToPreviousWord(bool select) { | |
| 554 if (HasCompositionText()) | |
| 555 ConfirmCompositionText(); | |
| 556 // Notes: We always iterate words from the begining. | |
| 557 // This is probably fast enough for our usage, but we may | |
| 558 // want to modify WordIterator so that it can start from the | |
| 559 // middle of string and advance backwards. | |
| 560 base::i18n::BreakIterator iter(text_, base::i18n::BreakIterator::BREAK_WORD); | |
| 561 bool success = iter.Init(); | |
| 562 DCHECK(success); | |
| 563 if (!success) | |
| 564 return; | |
| 565 int last = 0; | |
| 566 while (iter.Advance()) { | |
| 567 if (iter.IsWord()) { | |
| 568 size_t begin = iter.pos() - iter.GetString().length(); | |
| 569 if (begin == cursor_pos_) { | |
| 570 // The cursor is at the beginning of a word. | |
| 571 // Move to previous word. | |
| 572 break; | |
| 573 } else if(iter.pos() >= cursor_pos_) { | |
| 574 // The cursor is in the middle or at the end of a word. | |
| 575 // Move to the top of current word. | |
| 576 last = begin; | |
| 577 break; | |
| 578 } else { | |
| 579 last = iter.pos() - iter.GetString().length(); | |
| 580 } | |
| 581 } | |
| 582 } | |
| 583 | |
| 584 cursor_pos_ = last; | |
| 585 if (!select) | |
| 586 ClearSelection(); | |
| 587 } | |
| 588 | |
| 589 void TextfieldViewsModel::MoveCursorToNextWord(bool select) { | |
| 590 if (HasCompositionText()) | |
| 591 ConfirmCompositionText(); | |
| 592 base::i18n::BreakIterator iter(text_, base::i18n::BreakIterator::BREAK_WORD); | |
| 593 bool success = iter.Init(); | |
| 594 DCHECK(success); | |
| 595 if (!success) | |
| 596 return; | |
| 597 size_t pos = 0; | |
| 598 while (iter.Advance()) { | |
| 599 pos = iter.pos(); | |
| 600 if (iter.IsWord() && pos > cursor_pos_) { | |
| 601 break; | |
| 602 } | |
| 603 } | |
| 604 cursor_pos_ = pos; | |
| 605 if (!select) | |
| 606 ClearSelection(); | |
| 607 } | |
| 608 | |
| 609 void TextfieldViewsModel::MoveCursorToHome(bool select) { | |
| 610 MoveCursorTo(0, select); | |
| 611 } | |
| 612 | |
| 613 void TextfieldViewsModel::MoveCursorToEnd(bool select) { | |
| 614 MoveCursorTo(text_.length(), select); | |
| 615 } | 381 } |
| 616 | 382 |
| 617 bool TextfieldViewsModel::MoveCursorTo(size_t pos, bool select) { | 383 bool TextfieldViewsModel::MoveCursorTo(size_t pos, bool select) { |
| 618 if (HasCompositionText()) | 384 if (HasCompositionText()) |
| 619 ConfirmCompositionText(); | 385 ConfirmCompositionText(); |
| 620 bool changed = cursor_pos_ != pos || select != HasSelection(); | 386 return render_text_->MoveCursorTo(pos, select); |
| 621 cursor_pos_ = pos; | |
| 622 if (!select) | |
| 623 ClearSelection(); | |
| 624 return changed; | |
| 625 } | 387 } |
| 626 | 388 |
| 627 gfx::Rect TextfieldViewsModel::GetSelectionBounds(const gfx::Font& font) const { | 389 bool TextfieldViewsModel::MoveCursorTo(const gfx::Point& point, bool select) { |
| 628 if (!HasSelection()) | 390 if (HasCompositionText()) |
| 629 return gfx::Rect(); | 391 ConfirmCompositionText(); |
| 630 size_t start = std::min(selection_start_, cursor_pos_); | 392 return render_text_->MoveCursorTo(point, select); |
| 631 size_t end = std::max(selection_start_, cursor_pos_); | 393 } |
| 632 int start_x = font.GetStringWidth(text_.substr(0, start)); | 394 |
| 633 int end_x = font.GetStringWidth(text_.substr(0, end)); | 395 std::vector<gfx::Rect> TextfieldViewsModel::GetSelectionBounds() const { |
| 634 return gfx::Rect(start_x, 0, end_x - start_x, font.GetHeight()); | 396 return render_text_->GetSubstringBounds(render_text_->GetSelection()); |
| 635 } | 397 } |
| 636 | 398 |
| 637 string16 TextfieldViewsModel::GetSelectedText() const { | 399 string16 TextfieldViewsModel::GetSelectedText() const { |
| 638 return text_.substr( | 400 ui::Range selection = render_text_->GetSelection(); |
| 639 std::min(cursor_pos_, selection_start_), | 401 return GetText().substr(selection.GetMin(), selection.length()); |
| 640 std::abs(static_cast<long>(cursor_pos_ - selection_start_))); | |
| 641 } | 402 } |
| 642 | 403 |
| 643 void TextfieldViewsModel::GetSelectedRange(ui::Range* range) const { | 404 void TextfieldViewsModel::GetSelectedRange(ui::Range* range) const { |
| 644 *range = ui::Range(selection_start_, cursor_pos_); | 405 *range = render_text_->GetSelection(); |
| 645 } | 406 } |
| 646 | 407 |
| 647 void TextfieldViewsModel::SelectRange(const ui::Range& range) { | 408 void TextfieldViewsModel::SelectRange(const ui::Range& range) { |
| 648 if (HasCompositionText()) | 409 if (HasCompositionText()) |
| 649 ConfirmCompositionText(); | 410 ConfirmCompositionText(); |
| 650 selection_start_ = GetSafePosition(range.start()); | 411 render_text_->SetSelection(range); |
| 651 cursor_pos_ = GetSafePosition(range.end()); | |
| 652 } | 412 } |
| 653 | 413 |
| 654 void TextfieldViewsModel::SelectAll() { | 414 void TextfieldViewsModel::SelectAll() { |
| 655 if (HasCompositionText()) | 415 if (HasCompositionText()) |
| 656 ConfirmCompositionText(); | 416 ConfirmCompositionText(); |
| 657 // SelectAll selects towards the end. | 417 render_text_->SelectAll(); |
| 658 cursor_pos_ = text_.length(); | |
| 659 selection_start_ = 0; | |
| 660 } | 418 } |
| 661 | 419 |
| 662 void TextfieldViewsModel::SelectWord() { | 420 void TextfieldViewsModel::SelectWord() { |
| 663 if (HasCompositionText()) | 421 if (HasCompositionText()) |
| 664 ConfirmCompositionText(); | 422 ConfirmCompositionText(); |
| 665 // First we setup selection_start_ and cursor_pos_. There are so many cases | 423 render_text_->SelectWord(); |
| 666 // because we try to emulate what select-word looks like in a gtk textfield. | |
| 667 // See associated testcase for different cases. | |
| 668 if (cursor_pos_ > 0 && cursor_pos_ < text_.length()) { | |
| 669 if (isalnum(text_[cursor_pos_])) { | |
| 670 selection_start_ = cursor_pos_; | |
| 671 cursor_pos_++; | |
| 672 } else | |
| 673 selection_start_ = cursor_pos_ - 1; | |
| 674 } else if (cursor_pos_ == 0) { | |
| 675 selection_start_ = cursor_pos_; | |
| 676 if (text_.length() > 0) | |
| 677 cursor_pos_++; | |
| 678 } else { | |
| 679 selection_start_ = cursor_pos_ - 1; | |
| 680 } | |
| 681 | |
| 682 // Now we move selection_start_ to beginning of selection. Selection boundary | |
| 683 // is defined as the position where we have alpha-num character on one side | |
| 684 // and non-alpha-num char on the other side. | |
| 685 for (; selection_start_ > 0; selection_start_--) { | |
| 686 if (IsPositionAtWordSelectionBoundary(selection_start_)) | |
| 687 break; | |
| 688 } | |
| 689 | |
| 690 // Now we move cursor_pos_ to end of selection. Selection boundary | |
| 691 // is defined as the position where we have alpha-num character on one side | |
| 692 // and non-alpha-num char on the other side. | |
| 693 for (; cursor_pos_ < text_.length(); cursor_pos_++) { | |
| 694 if (IsPositionAtWordSelectionBoundary(cursor_pos_)) | |
| 695 break; | |
| 696 } | |
| 697 } | 424 } |
| 698 | 425 |
| 699 void TextfieldViewsModel::ClearSelection() { | 426 void TextfieldViewsModel::ClearSelection() { |
| 700 if (HasCompositionText()) | 427 if (HasCompositionText()) |
| 701 ConfirmCompositionText(); | 428 ConfirmCompositionText(); |
| 702 selection_start_ = cursor_pos_; | 429 render_text_->ClearSelection(); |
| 703 } | 430 } |
| 704 | 431 |
| 705 bool TextfieldViewsModel::CanUndo() { | 432 bool TextfieldViewsModel::CanUndo() { |
| 706 return edit_history_.size() && current_edit_ != edit_history_.end(); | 433 return edit_history_.size() && current_edit_ != edit_history_.end(); |
| 707 } | 434 } |
| 708 | 435 |
| 709 bool TextfieldViewsModel::CanRedo() { | 436 bool TextfieldViewsModel::CanRedo() { |
| 710 if (!edit_history_.size()) | 437 if (!edit_history_.size()) |
| 711 return false; | 438 return false; |
| 712 // There is no redo iff the current edit is the last element | 439 // There is no redo iff the current edit is the last element |
| 713 // in the history. | 440 // in the history. |
| 714 EditHistory::iterator iter = current_edit_; | 441 EditHistory::iterator iter = current_edit_; |
| 715 return iter == edit_history_.end() || // at the top. | 442 return iter == edit_history_.end() || // at the top. |
| 716 ++iter != edit_history_.end(); | 443 ++iter != edit_history_.end(); |
| 717 } | 444 } |
| 718 | 445 |
| 719 bool TextfieldViewsModel::Undo() { | 446 bool TextfieldViewsModel::Undo() { |
| 720 if (!CanUndo()) | 447 if (!CanUndo()) |
| 721 return false; | 448 return false; |
| 722 DCHECK(!HasCompositionText()); | 449 DCHECK(!HasCompositionText()); |
| 723 if (HasCompositionText()) // safe guard for release build. | 450 if (HasCompositionText()) // safe guard for release build. |
| 724 CancelCompositionText(); | 451 CancelCompositionText(); |
| 725 | 452 |
| 726 string16 old = text_; | 453 string16 old = GetText(); |
| 727 size_t old_cursor = cursor_pos_; | 454 size_t old_cursor = GetCursorPosition(); |
| 728 (*current_edit_)->Commit(); | 455 (*current_edit_)->Commit(); |
| 729 (*current_edit_)->Undo(this); | 456 (*current_edit_)->Undo(this); |
| 730 | 457 |
| 731 if (current_edit_ == edit_history_.begin()) | 458 if (current_edit_ == edit_history_.begin()) |
| 732 current_edit_ = edit_history_.end(); | 459 current_edit_ = edit_history_.end(); |
| 733 else | 460 else |
| 734 current_edit_--; | 461 current_edit_--; |
| 735 return old != text_ || old_cursor != cursor_pos_; | 462 return old != GetText() || old_cursor != GetCursorPosition(); |
| 736 } | 463 } |
| 737 | 464 |
| 738 bool TextfieldViewsModel::Redo() { | 465 bool TextfieldViewsModel::Redo() { |
| 739 if (!CanRedo()) | 466 if (!CanRedo()) |
| 740 return false; | 467 return false; |
| 741 DCHECK(!HasCompositionText()); | 468 DCHECK(!HasCompositionText()); |
| 742 if (HasCompositionText()) // safe guard for release build. | 469 if (HasCompositionText()) // safe guard for release build. |
| 743 CancelCompositionText(); | 470 CancelCompositionText(); |
| 744 | 471 |
| 745 if (current_edit_ == edit_history_.end()) | 472 if (current_edit_ == edit_history_.end()) |
| 746 current_edit_ = edit_history_.begin(); | 473 current_edit_ = edit_history_.begin(); |
| 747 else | 474 else |
| 748 current_edit_ ++; | 475 current_edit_ ++; |
| 749 string16 old = text_; | 476 string16 old = GetText(); |
| 750 size_t old_cursor = cursor_pos_; | 477 size_t old_cursor = GetCursorPosition(); |
| 751 (*current_edit_)->Redo(this); | 478 (*current_edit_)->Redo(this); |
| 752 return old != text_ || old_cursor != cursor_pos_; | 479 return old != GetText() || old_cursor != GetCursorPosition(); |
| 480 } |
| 481 |
| 482 string16 TextfieldViewsModel::GetVisibleText() const { |
| 483 return GetVisibleText(0U, GetText().length()); |
| 753 } | 484 } |
| 754 | 485 |
| 755 bool TextfieldViewsModel::Cut() { | 486 bool TextfieldViewsModel::Cut() { |
| 756 if (!HasCompositionText() && HasSelection()) { | 487 if (!HasCompositionText() && HasSelection()) { |
| 757 ui::ScopedClipboardWriter(views::ViewsDelegate::views_delegate | 488 ui::ScopedClipboardWriter(views::ViewsDelegate::views_delegate |
| 758 ->GetClipboard()).WriteText(GetSelectedText()); | 489 ->GetClipboard()).WriteText(GetSelectedText()); |
| 759 // A trick to let undo/redo handle cursor correctly. | 490 // A trick to let undo/redo handle cursor correctly. |
| 760 // Undoing CUT moves the cursor to the end of the change rather | 491 // Undoing CUT moves the cursor to the end of the change rather |
| 761 // than beginning, unlike Delete/Backspace. | 492 // than beginning, unlike Delete/Backspace. |
| 762 // TODO(oshima): Change Delete/Backspace to use DeleteSelection, | 493 // TODO(oshima): Change Delete/Backspace to use DeleteSelection, |
| 763 // update DeleteEdit and remove this trick. | 494 // update DeleteEdit and remove this trick. |
| 764 std::swap(cursor_pos_, selection_start_); | 495 ui::Range selection = render_text_->GetSelection(); |
| 496 render_text_->SetSelection(ui::Range(selection.end(), selection.start())); |
| 765 DeleteSelection(); | 497 DeleteSelection(); |
| 766 return true; | 498 return true; |
| 767 } | 499 } |
| 768 return false; | 500 return false; |
| 769 } | 501 } |
| 770 | 502 |
| 771 void TextfieldViewsModel::Copy() { | 503 void TextfieldViewsModel::Copy() { |
| 772 if (!HasCompositionText() && HasSelection()) { | 504 if (!HasCompositionText() && HasSelection()) { |
| 773 ui::ScopedClipboardWriter(views::ViewsDelegate::views_delegate | 505 ui::ScopedClipboardWriter(views::ViewsDelegate::views_delegate |
| 774 ->GetClipboard()).WriteText(GetSelectedText()); | 506 ->GetClipboard()).WriteText(GetSelectedText()); |
| 775 } | 507 } |
| 776 } | 508 } |
| 777 | 509 |
| 778 bool TextfieldViewsModel::Paste() { | 510 bool TextfieldViewsModel::Paste() { |
| 779 string16 result; | 511 string16 result; |
| 780 views::ViewsDelegate::views_delegate->GetClipboard() | 512 views::ViewsDelegate::views_delegate->GetClipboard() |
| 781 ->ReadText(ui::Clipboard::BUFFER_STANDARD, &result); | 513 ->ReadText(ui::Clipboard::BUFFER_STANDARD, &result); |
| 782 if (!result.empty()) { | 514 if (!result.empty()) { |
| 783 InsertTextInternal(result, false); | 515 InsertTextInternal(result, false); |
| 784 return true; | 516 return true; |
| 785 } | 517 } |
| 786 return false; | 518 return false; |
| 787 } | 519 } |
| 788 | 520 |
| 789 bool TextfieldViewsModel::HasSelection() const { | 521 bool TextfieldViewsModel::HasSelection() const { |
| 790 return selection_start_ != cursor_pos_; | 522 return !render_text_->GetSelection().is_empty(); |
| 791 } | 523 } |
| 792 | 524 |
| 793 void TextfieldViewsModel::DeleteSelection() { | 525 void TextfieldViewsModel::DeleteSelection() { |
| 794 DCHECK(!HasCompositionText()); | 526 DCHECK(!HasCompositionText()); |
| 795 DCHECK(HasSelection()); | 527 DCHECK(HasSelection()); |
| 796 ExecuteAndRecordDelete(selection_start_, cursor_pos_, false); | 528 ui::Range selection = render_text_->GetSelection(); |
| 529 ExecuteAndRecordDelete(selection.start(), selection.end(), false); |
| 797 } | 530 } |
| 798 | 531 |
| 799 void TextfieldViewsModel::DeleteSelectionAndInsertTextAt( | 532 void TextfieldViewsModel::DeleteSelectionAndInsertTextAt( |
| 800 const string16& text, size_t position) { | 533 const string16& text, size_t position) { |
| 801 if (HasCompositionText()) | 534 if (HasCompositionText()) |
| 802 CancelCompositionText(); | 535 CancelCompositionText(); |
| 803 ExecuteAndRecordReplace(DO_NOT_MERGE, | 536 ExecuteAndRecordReplace(DO_NOT_MERGE, |
| 804 cursor_pos_, | 537 GetCursorPosition(), |
| 805 position + text.length(), | 538 position + text.length(), |
| 806 text, | 539 text, |
| 807 position); | 540 position); |
| 808 } | 541 } |
| 809 | 542 |
| 810 string16 TextfieldViewsModel::GetTextFromRange(const ui::Range& range) const { | 543 string16 TextfieldViewsModel::GetTextFromRange(const ui::Range& range) const { |
| 811 if (range.IsValid() && range.GetMin() < text_.length()) | 544 if (range.IsValid() && range.GetMin() < GetText().length()) |
| 812 return text_.substr(range.GetMin(), range.length()); | 545 return GetText().substr(range.GetMin(), range.length()); |
| 813 return string16(); | 546 return string16(); |
| 814 } | 547 } |
| 815 | 548 |
| 816 void TextfieldViewsModel::GetTextRange(ui::Range* range) const { | 549 void TextfieldViewsModel::GetTextRange(ui::Range* range) const { |
| 817 *range = ui::Range(0, text_.length()); | 550 *range = ui::Range(0, GetText().length()); |
| 818 } | 551 } |
| 819 | 552 |
| 820 void TextfieldViewsModel::SetCompositionText( | 553 void TextfieldViewsModel::SetCompositionText( |
| 821 const ui::CompositionText& composition) { | 554 const ui::CompositionText& composition) { |
| 822 static const TextStyle* composition_style = CreateUnderlineStyle(); | |
| 823 | |
| 824 if (HasCompositionText()) | 555 if (HasCompositionText()) |
| 825 CancelCompositionText(); | 556 CancelCompositionText(); |
| 826 else if (HasSelection()) | 557 else if (HasSelection()) |
| 827 DeleteSelection(); | 558 DeleteSelection(); |
| 828 | 559 |
| 829 if (composition.text.empty()) | 560 if (composition.text.empty()) |
| 830 return; | 561 return; |
| 831 | 562 |
| 832 size_t length = composition.text.length(); | 563 size_t cursor = GetCursorPosition(); |
| 833 text_.insert(cursor_pos_, composition.text); | 564 string16 new_text = GetText(); |
| 834 composition_start_ = cursor_pos_; | 565 render_text_->SetText(new_text.insert(cursor, composition.text)); |
| 835 composition_end_ = composition_start_ + length; | 566 ui::Range range(cursor, cursor + composition.text.length()); |
| 836 for (ui::CompositionUnderlines::const_iterator iter = | 567 render_text_->SetCompositionRange(range); |
| 837 composition.underlines.begin(); | 568 // TODO(msw): Support multiple composition underline ranges. |
| 838 iter != composition.underlines.end(); | |
| 839 iter++) { | |
| 840 size_t start = composition_start_ + iter->start_offset; | |
| 841 size_t end = composition_start_ + iter->end_offset; | |
| 842 InsertStyle(&composition_style_ranges_, | |
| 843 new TextStyleRange(composition_style, start, end)); | |
| 844 } | |
| 845 std::sort(composition_style_ranges_.begin(), | |
| 846 composition_style_ranges_.end(), TextStyleRangeComparator); | |
| 847 | 569 |
| 848 if (composition.selection.IsValid()) { | 570 if (composition.selection.IsValid()) |
| 849 selection_start_ = | 571 render_text_->SetSelection(ui::Range( |
| 850 std::min(composition_start_ + composition.selection.start(), | 572 std::min(range.start() + composition.selection.start(), range.end()), |
| 851 composition_end_); | 573 std::min(range.start() + composition.selection.end(), range.end()))); |
| 852 cursor_pos_ = | 574 else |
| 853 std::min(composition_start_ + composition.selection.end(), | 575 render_text_->SetCursorPosition(range.end()); |
| 854 composition_end_); | |
| 855 } else { | |
| 856 cursor_pos_ = composition_end_; | |
| 857 ClearSelection(); | |
| 858 } | |
| 859 } | 576 } |
| 860 | 577 |
| 861 void TextfieldViewsModel::ConfirmCompositionText() { | 578 void TextfieldViewsModel::ConfirmCompositionText() { |
| 862 DCHECK(HasCompositionText()); | 579 DCHECK(HasCompositionText()); |
| 863 string16 new_text = | 580 ui::Range range = render_text_->GetCompositionRange(); |
| 864 text_.substr(composition_start_, composition_end_ - composition_start_); | 581 string16 text = GetText().substr(range.start(), range.length()); |
| 865 // TODO(oshima): current behavior on ChromeOS is a bit weird and not | 582 // TODO(oshima): current behavior on ChromeOS is a bit weird and not |
| 866 // sure exactly how this should work. Find out and fix if necessary. | 583 // sure exactly how this should work. Find out and fix if necessary. |
| 867 AddOrMergeEditHistory(new InsertEdit(false, new_text, composition_start_)); | 584 AddOrMergeEditHistory(new InsertEdit(false, text, range.start())); |
| 868 cursor_pos_ = composition_end_; | 585 render_text_->SetCursorPosition(range.end()); |
| 869 ClearComposition(); | 586 ClearComposition(); |
| 870 ClearSelection(); | |
| 871 if (delegate_) | 587 if (delegate_) |
| 872 delegate_->OnCompositionTextConfirmedOrCleared(); | 588 delegate_->OnCompositionTextConfirmedOrCleared(); |
| 873 } | 589 } |
| 874 | 590 |
| 875 void TextfieldViewsModel::CancelCompositionText() { | 591 void TextfieldViewsModel::CancelCompositionText() { |
| 876 DCHECK(HasCompositionText()); | 592 DCHECK(HasCompositionText()); |
| 877 text_.erase(composition_start_, composition_end_ - composition_start_); | 593 ui::Range range = render_text_->GetCompositionRange(); |
| 878 cursor_pos_ = composition_start_; | 594 string16 new_text = GetText(); |
| 595 render_text_->SetText(new_text.erase(range.start(), range.length())); |
| 596 render_text_->SetCursorPosition(range.start()); |
| 879 ClearComposition(); | 597 ClearComposition(); |
| 880 ClearSelection(); | |
| 881 if (delegate_) | 598 if (delegate_) |
| 882 delegate_->OnCompositionTextConfirmedOrCleared(); | 599 delegate_->OnCompositionTextConfirmedOrCleared(); |
| 883 } | 600 } |
| 884 | 601 |
| 885 void TextfieldViewsModel::ClearComposition() { | 602 void TextfieldViewsModel::ClearComposition() { |
| 886 composition_start_ = composition_end_ = string16::npos; | 603 render_text_->SetCompositionRange(ui::Range::InvalidRange()); |
| 887 STLDeleteContainerPointers(composition_style_ranges_.begin(), | |
| 888 composition_style_ranges_.end()); | |
| 889 composition_style_ranges_.clear(); | |
| 890 } | |
| 891 | |
| 892 void TextfieldViewsModel::ApplyTextStyle(const TextStyle* style, | |
| 893 const ui::Range& range) { | |
| 894 TextStyleRange* new_text_style_range = range.is_reversed() ? | |
| 895 new TextStyleRange(style, ui::Range(range.end(), range.start())) : | |
| 896 new TextStyleRange(style, range); | |
| 897 InsertStyle(&style_ranges_, new_text_style_range); | |
| 898 sort_style_ranges_ = true; | |
| 899 } | 604 } |
| 900 | 605 |
| 901 void TextfieldViewsModel::GetCompositionTextRange(ui::Range* range) const { | 606 void TextfieldViewsModel::GetCompositionTextRange(ui::Range* range) const { |
| 902 if (HasCompositionText()) | 607 *range = ui::Range(render_text_->GetCompositionRange()); |
| 903 *range = ui::Range(composition_start_, composition_end_); | |
| 904 else | |
| 905 *range = ui::Range::InvalidRange(); | |
| 906 } | 608 } |
| 907 | 609 |
| 908 bool TextfieldViewsModel::HasCompositionText() const { | 610 bool TextfieldViewsModel::HasCompositionText() const { |
| 909 return composition_start_ != composition_end_; | 611 return !render_text_->GetCompositionRange().is_empty(); |
| 910 } | |
| 911 | |
| 912 TextStyle* TextfieldViewsModel::CreateTextStyle() { | |
| 913 TextStyle* style = new TextStyle(); | |
| 914 text_styles_.push_back(style); | |
| 915 return style; | |
| 916 } | |
| 917 | |
| 918 void TextfieldViewsModel::ClearAllTextStyles() { | |
| 919 STLDeleteContainerPointers(style_ranges_.begin(), style_ranges_.end()); | |
| 920 style_ranges_.clear(); | |
| 921 } | 612 } |
| 922 | 613 |
| 923 ///////////////////////////////////////////////////////////////// | 614 ///////////////////////////////////////////////////////////////// |
| 924 // TextfieldViewsModel: private | 615 // TextfieldViewsModel: private |
| 925 | 616 |
| 926 string16 TextfieldViewsModel::GetVisibleText(size_t begin, size_t end) const { | 617 string16 TextfieldViewsModel::GetVisibleText(size_t begin, size_t end) const { |
| 927 DCHECK(end >= begin); | 618 DCHECK(end >= begin); |
| 928 if (is_password_) | 619 if (is_password_) |
| 929 return string16(end - begin, '*'); | 620 return string16(end - begin, '*'); |
| 930 return text_.substr(begin, end - begin); | 621 return GetText().substr(begin, end - begin); |
| 931 } | |
| 932 | |
| 933 bool TextfieldViewsModel::IsPositionAtWordSelectionBoundary(size_t pos) { | |
| 934 return (isalnum(text_[pos - 1]) && !isalnum(text_[pos])) || | |
| 935 (!isalnum(text_[pos - 1]) && isalnum(text_[pos])); | |
| 936 } | |
| 937 | |
| 938 size_t TextfieldViewsModel::GetSafePosition(size_t position) const { | |
| 939 if (position > text_.length()) { | |
| 940 return text_.length(); | |
| 941 } | |
| 942 return position; | |
| 943 } | 622 } |
| 944 | 623 |
| 945 void TextfieldViewsModel::InsertTextInternal(const string16& text, | 624 void TextfieldViewsModel::InsertTextInternal(const string16& text, |
| 946 bool mergeable) { | 625 bool mergeable) { |
| 947 if (HasCompositionText()) { | 626 if (HasCompositionText()) { |
| 948 CancelCompositionText(); | 627 CancelCompositionText(); |
| 949 ExecuteAndRecordInsert(text, mergeable); | 628 ExecuteAndRecordInsert(text, mergeable); |
| 950 } else if (HasSelection()) { | 629 } else if (HasSelection()) { |
| 951 ExecuteAndRecordReplaceSelection(mergeable ? MERGEABLE : DO_NOT_MERGE, | 630 ExecuteAndRecordReplaceSelection(mergeable ? MERGEABLE : DO_NOT_MERGE, |
| 952 text); | 631 text); |
| 953 } else { | 632 } else { |
| 954 ExecuteAndRecordInsert(text, mergeable); | 633 ExecuteAndRecordInsert(text, mergeable); |
| 955 } | 634 } |
| 956 } | 635 } |
| 957 | 636 |
| 958 void TextfieldViewsModel::ReplaceTextInternal(const string16& text, | 637 void TextfieldViewsModel::ReplaceTextInternal(const string16& text, |
| 959 bool mergeable) { | 638 bool mergeable) { |
| 960 if (HasCompositionText()) | 639 if (HasCompositionText()) { |
| 961 CancelCompositionText(); | 640 CancelCompositionText(); |
| 962 else if (!HasSelection()) | 641 } else if (!HasSelection()) { |
| 963 SelectRange(ui::Range(cursor_pos_ + text.length(), cursor_pos_)); | 642 size_t cursor = GetCursorPosition(); |
| 643 render_text_->SetSelection(ui::Range(cursor + text.length(), cursor)); |
| 644 } |
| 964 // Edit history is recorded in InsertText. | 645 // Edit history is recorded in InsertText. |
| 965 InsertTextInternal(text, mergeable); | 646 InsertTextInternal(text, mergeable); |
| 966 } | 647 } |
| 967 | 648 |
| 968 void TextfieldViewsModel::ClearEditHistory() { | 649 void TextfieldViewsModel::ClearEditHistory() { |
| 969 STLDeleteContainerPointers(edit_history_.begin(), | 650 STLDeleteContainerPointers(edit_history_.begin(), |
| 970 edit_history_.end()); | 651 edit_history_.end()); |
| 971 edit_history_.clear(); | 652 edit_history_.clear(); |
| 972 current_edit_ = edit_history_.end(); | 653 current_edit_ = edit_history_.end(); |
| 973 } | 654 } |
| 974 | 655 |
| 975 void TextfieldViewsModel::ClearRedoHistory() { | 656 void TextfieldViewsModel::ClearRedoHistory() { |
| 976 if (edit_history_.begin() == edit_history_.end()) | 657 if (edit_history_.begin() == edit_history_.end()) |
| 977 return; | 658 return; |
| 978 if (current_edit_ == edit_history_.end()) { | 659 if (current_edit_ == edit_history_.end()) { |
| 979 ClearEditHistory(); | 660 ClearEditHistory(); |
| 980 return; | 661 return; |
| 981 } | 662 } |
| 982 EditHistory::iterator delete_start = current_edit_; | 663 EditHistory::iterator delete_start = current_edit_; |
| 983 delete_start++; | 664 delete_start++; |
| 984 STLDeleteContainerPointers(delete_start, edit_history_.end()); | 665 STLDeleteContainerPointers(delete_start, edit_history_.end()); |
| 985 edit_history_.erase(delete_start, edit_history_.end()); | 666 edit_history_.erase(delete_start, edit_history_.end()); |
| 986 } | 667 } |
| 987 | 668 |
| 988 void TextfieldViewsModel::ExecuteAndRecordDelete(size_t from, | 669 void TextfieldViewsModel::ExecuteAndRecordDelete(size_t from, |
| 989 size_t to, | 670 size_t to, |
| 990 bool mergeable) { | 671 bool mergeable) { |
| 991 size_t old_text_start = std::min(from, to); | 672 size_t old_text_start = std::min(from, to); |
| 992 const string16 text = text_.substr(old_text_start, | 673 const string16 text = GetText().substr(old_text_start, |
| 993 std::abs(static_cast<long>(from - to))); | 674 std::abs(static_cast<long>(from - to))); |
| 994 bool backward = from > to; | 675 bool backward = from > to; |
| 995 Edit* edit = new DeleteEdit(mergeable, text, old_text_start, backward); | 676 Edit* edit = new DeleteEdit(mergeable, text, old_text_start, backward); |
| 996 bool delete_edit = AddOrMergeEditHistory(edit); | 677 bool delete_edit = AddOrMergeEditHistory(edit); |
| 997 edit->Redo(this); | 678 edit->Redo(this); |
| 998 if (delete_edit) | 679 if (delete_edit) |
| 999 delete edit; | 680 delete edit; |
| 1000 } | 681 } |
| 1001 | 682 |
| 1002 void TextfieldViewsModel::ExecuteAndRecordReplaceSelection( | 683 void TextfieldViewsModel::ExecuteAndRecordReplaceSelection( |
| 1003 MergeType merge_type, const string16& new_text) { | 684 MergeType merge_type, const string16& new_text) { |
| 1004 size_t new_text_start = std::min(cursor_pos_, selection_start_); | 685 size_t new_text_start = render_text_->GetSelection().GetMin(); |
| 1005 size_t new_cursor_pos = new_text_start + new_text.length(); | 686 size_t new_cursor_pos = new_text_start + new_text.length(); |
| 1006 ExecuteAndRecordReplace(merge_type, | 687 ExecuteAndRecordReplace(merge_type, |
| 1007 cursor_pos_, | 688 GetCursorPosition(), |
| 1008 new_cursor_pos, | 689 new_cursor_pos, |
| 1009 new_text, | 690 new_text, |
| 1010 new_text_start); | 691 new_text_start); |
| 1011 } | 692 } |
| 1012 | 693 |
| 1013 void TextfieldViewsModel::ExecuteAndRecordReplace(MergeType merge_type, | 694 void TextfieldViewsModel::ExecuteAndRecordReplace(MergeType merge_type, |
| 1014 size_t old_cursor_pos, | 695 size_t old_cursor_pos, |
| 1015 size_t new_cursor_pos, | 696 size_t new_cursor_pos, |
| 1016 const string16& new_text, | 697 const string16& new_text, |
| 1017 size_t new_text_start) { | 698 size_t new_text_start) { |
| 1018 size_t old_text_start = std::min(cursor_pos_, selection_start_); | 699 size_t old_text_start = render_text_->GetSelection().GetMin(); |
| 1019 bool backward = selection_start_ > cursor_pos_; | 700 bool backward = render_text_->GetSelection().is_reversed(); |
| 1020 Edit* edit = new ReplaceEdit(merge_type, | 701 Edit* edit = new ReplaceEdit(merge_type, |
| 1021 GetSelectedText(), | 702 GetSelectedText(), |
| 1022 old_cursor_pos, | 703 old_cursor_pos, |
| 1023 old_text_start, | 704 old_text_start, |
| 1024 backward, | 705 backward, |
| 1025 new_cursor_pos, | 706 new_cursor_pos, |
| 1026 new_text, | 707 new_text, |
| 1027 new_text_start); | 708 new_text_start); |
| 1028 bool delete_edit = AddOrMergeEditHistory(edit); | 709 bool delete_edit = AddOrMergeEditHistory(edit); |
| 1029 edit->Redo(this); | 710 edit->Redo(this); |
| 1030 if (delete_edit) | 711 if (delete_edit) |
| 1031 delete edit; | 712 delete edit; |
| 1032 } | 713 } |
| 1033 | 714 |
| 1034 void TextfieldViewsModel::ExecuteAndRecordInsert(const string16& text, | 715 void TextfieldViewsModel::ExecuteAndRecordInsert(const string16& text, |
| 1035 bool mergeable) { | 716 bool mergeable) { |
| 1036 Edit* edit = new InsertEdit(mergeable, text, cursor_pos_); | 717 Edit* edit = new InsertEdit(mergeable, text, GetCursorPosition()); |
| 1037 bool delete_edit = AddOrMergeEditHistory(edit); | 718 bool delete_edit = AddOrMergeEditHistory(edit); |
| 1038 edit->Redo(this); | 719 edit->Redo(this); |
| 1039 if (delete_edit) | 720 if (delete_edit) |
| 1040 delete edit; | 721 delete edit; |
| 1041 } | 722 } |
| 1042 | 723 |
| 1043 bool TextfieldViewsModel::AddOrMergeEditHistory(Edit* edit) { | 724 bool TextfieldViewsModel::AddOrMergeEditHistory(Edit* edit) { |
| 1044 ClearRedoHistory(); | 725 ClearRedoHistory(); |
| 1045 | 726 |
| 1046 if (current_edit_ != edit_history_.end() && (*current_edit_)->Merge(edit)) { | 727 if (current_edit_ != edit_history_.end() && (*current_edit_)->Merge(edit)) { |
| (...skipping 13 matching lines...) Expand all Loading... |
| 1060 } | 741 } |
| 1061 return false; | 742 return false; |
| 1062 } | 743 } |
| 1063 | 744 |
| 1064 void TextfieldViewsModel::ModifyText(size_t delete_from, | 745 void TextfieldViewsModel::ModifyText(size_t delete_from, |
| 1065 size_t delete_to, | 746 size_t delete_to, |
| 1066 const string16& new_text, | 747 const string16& new_text, |
| 1067 size_t new_text_insert_at, | 748 size_t new_text_insert_at, |
| 1068 size_t new_cursor_pos) { | 749 size_t new_cursor_pos) { |
| 1069 DCHECK_LE(delete_from, delete_to); | 750 DCHECK_LE(delete_from, delete_to); |
| 751 string16 text = GetText(); |
| 1070 if (delete_from != delete_to) | 752 if (delete_from != delete_to) |
| 1071 text_.erase(delete_from, delete_to - delete_from); | 753 render_text_->SetText(text.erase(delete_from, delete_to - delete_from)); |
| 1072 if (!new_text.empty()) | 754 if (!new_text.empty()) |
| 1073 text_.insert(new_text_insert_at, new_text); | 755 render_text_->SetText(text.insert(new_text_insert_at, new_text)); |
| 1074 cursor_pos_ = new_cursor_pos; | 756 render_text_->SetCursorPosition(new_cursor_pos); |
| 1075 ClearSelection(); | |
| 1076 // TODO(oshima): mac selects the text that is just undone (but gtk doesn't). | 757 // TODO(oshima): mac selects the text that is just undone (but gtk doesn't). |
| 1077 // This looks fine feature and we may want to do the same. | 758 // This looks fine feature and we may want to do the same. |
| 1078 } | 759 } |
| 1079 | 760 |
| 1080 // static | |
| 1081 TextStyle* TextfieldViewsModel::CreateUnderlineStyle() { | |
| 1082 TextStyle* style = new TextStyle(); | |
| 1083 style->set_underline(true); | |
| 1084 return style; | |
| 1085 } | |
| 1086 | |
| 1087 } // namespace views | 761 } // namespace views |
| OLD | NEW |