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