Chromium Code Reviews| 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 #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_editor.h" | 5 #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_editor.h" |
| 6 | 6 |
| 7 #include "base/i18n/break_iterator.h" | |
| 8 #include "base/mac/foundation_util.h" | |
| 7 #include "base/mac/sdk_forward_declarations.h" | 9 #include "base/mac/sdk_forward_declarations.h" |
| 8 #include "base/strings/string_util.h" | 10 #include "base/strings/string_util.h" |
| 9 #include "base/strings/sys_string_conversions.h" | 11 #include "base/strings/sys_string_conversions.h" |
| 10 #include "chrome/app/chrome_command_ids.h" // IDC_* | 12 #include "chrome/app/chrome_command_ids.h" // IDC_* |
| 11 #include "chrome/browser/themes/theme_service.h" | 13 #include "chrome/browser/themes/theme_service.h" |
| 12 #include "chrome/browser/ui/browser_list.h" | 14 #include "chrome/browser/ui/browser_list.h" |
| 13 #import "chrome/browser/ui/cocoa/browser_window_controller.h" | 15 #import "chrome/browser/ui/cocoa/browser_window_controller.h" |
| 14 #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field.h" | 16 #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field.h" |
| 15 #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_cell.h" | 17 #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_cell.h" |
| 16 #import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h" | 18 #import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h" |
| (...skipping 25 matching lines...) Expand all Loading... | |
| 42 BOOL ThePasteboardIsTooDamnBig() { | 44 BOOL ThePasteboardIsTooDamnBig() { |
| 43 NSPasteboard* pb = [NSPasteboard generalPasteboard]; | 45 NSPasteboard* pb = [NSPasteboard generalPasteboard]; |
| 44 NSString* type = | 46 NSString* type = |
| 45 [pb availableTypeFromArray:[NSArray arrayWithObject:NSStringPboardType]]; | 47 [pb availableTypeFromArray:[NSArray arrayWithObject:NSStringPboardType]]; |
| 46 if (!type) | 48 if (!type) |
| 47 return NO; | 49 return NO; |
| 48 | 50 |
| 49 return [[pb stringForType:type] length] > kMaxPasteLength; | 51 return [[pb stringForType:type] length] > kMaxPasteLength; |
| 50 } | 52 } |
| 51 | 53 |
| 54 NSRange VisualSelectedRangeFromRange(NSRange range, NSString* string) { | |
| 55 if (range.location >= string.length) | |
| 56 return range; | |
| 57 base::string16 text = base::SysNSStringToUTF16(string); | |
| 58 base::i18n::BreakIterator grapheme_iterator( | |
| 59 text, base::i18n::BreakIterator::BREAK_CHARACTER); | |
| 60 if (!grapheme_iterator.Init()) | |
| 61 return range; | |
| 62 // This works because NSString uses UTF-16 code units. | |
| 63 while (range.location < text.length() && | |
| 64 !grapheme_iterator.IsGraphemeBoundary( | |
| 65 static_cast<size_t>(range.location))) { | |
| 66 range.location++; | |
| 67 if (range.length > 0) | |
| 68 range.length--; | |
| 69 } | |
| 70 return range; | |
| 71 } | |
| 72 | |
| 52 } // namespace | 73 } // namespace |
| 53 | 74 |
| 75 // Method exposed for the purpose of overriding. | |
| 76 // Used to restore model's selection range when the view doesn't | |
| 77 // match the model due to combining characters. | |
| 78 // | |
| 79 // In some cases, (completing 'y' to 'ÿour', for example), the autocomplete | |
| 80 // system requests a selection range that begins on a combining character. | |
| 81 // setSelectedRange: and friends document that the range passed to them | |
| 82 // "must begin and end on glyph boundaries and not split base glyphs and | |
| 83 // their nonspacing marks". If passed such a range, the selection is | |
| 84 // expanded to include the original user input, preventing the user | |
| 85 // from being able to type other words beginning with that letter. | |
| 86 // | |
| 87 // To resolve this, the field editor modifies the selection to start | |
| 88 // on the next glyph boundary, then keeps track of the original and | |
| 89 // modified selections, substituting the original when the user takes | |
| 90 // actions that operate on the selection. Since there are many methods | |
| 91 // in NSResponder (for example deleteToBeginningOfLine:) that operate | |
| 92 // on the selection, rather than shimming them all, we override this | |
| 93 // private method that they're implemented in terms of. | |
| 94 // | |
| 95 @interface NSTextView (PrivateTextEditing) | |
| 96 - (void)_userReplaceRange:(NSRange)range withString:(NSString*)string; | |
| 97 @end | |
| 98 | |
| 54 @interface AutocompleteTextFieldEditor ()<NSDraggingSource> | 99 @interface AutocompleteTextFieldEditor ()<NSDraggingSource> |
| 55 @end | 100 @end |
| 56 | 101 |
| 57 @implementation AutocompleteTextFieldEditor | 102 @implementation AutocompleteTextFieldEditor |
| 58 | 103 |
| 104 @synthesize actualSelectedRange = actualSelectedRange_; | |
| 105 | |
| 59 - (BOOL)shouldDrawInsertionPoint { | 106 - (BOOL)shouldDrawInsertionPoint { |
| 60 return [super shouldDrawInsertionPoint] && | 107 return [super shouldDrawInsertionPoint] && |
| 61 ![[[self delegate] cell] hideFocusState]; | 108 ![[[self delegate] cell] hideFocusState]; |
| 62 } | 109 } |
| 63 | 110 |
| 64 - (id)initWithFrame:(NSRect)frameRect { | 111 - (id)initWithFrame:(NSRect)frameRect { |
| 65 if ((self = [super initWithFrame:frameRect])) { | 112 if ((self = [super initWithFrame:frameRect])) { |
| 66 dropHandler_.reset([[URLDropTargetHandler alloc] initWithView:self]); | 113 dropHandler_.reset([[URLDropTargetHandler alloc] initWithView:self]); |
| 67 | 114 |
| 68 forbiddenCharacters_.reset([[NSCharacterSet controlCharacterSet] retain]); | 115 forbiddenCharacters_.reset([[NSCharacterSet controlCharacterSet] retain]); |
| (...skipping 324 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 393 // (URLDropTarget protocol) | 440 // (URLDropTarget protocol) |
| 394 - (void)draggingExited:(id<NSDraggingInfo>)sender { | 441 - (void)draggingExited:(id<NSDraggingInfo>)sender { |
| 395 return [dropHandler_ draggingExited:sender]; | 442 return [dropHandler_ draggingExited:sender]; |
| 396 } | 443 } |
| 397 | 444 |
| 398 // (URLDropTarget protocol) | 445 // (URLDropTarget protocol) |
| 399 - (BOOL)performDragOperation:(id<NSDraggingInfo>)sender { | 446 - (BOOL)performDragOperation:(id<NSDraggingInfo>)sender { |
| 400 return [dropHandler_ performDragOperation:sender]; | 447 return [dropHandler_ performDragOperation:sender]; |
| 401 } | 448 } |
| 402 | 449 |
| 450 - (void)_userReplaceRange:(NSRange)range withString:(NSString*)string { | |
| 451 if (NSEqualRanges(visualSelectedRange_, range) && | |
| 452 !NSEqualRanges(visualSelectedRange_, actualSelectedRange_)) { | |
| 453 range = actualSelectedRange_; | |
| 454 } | |
| 455 [super _userReplaceRange:range withString:string]; | |
| 456 } | |
| 457 | |
| 403 // Prevent control characters from being entered into the Omnibox. | 458 // Prevent control characters from being entered into the Omnibox. |
| 404 // This is invoked for keyboard entry, not for pasting. | 459 // This is invoked for keyboard entry, not for pasting. |
| 405 - (void)insertText:(id)aString { | 460 - (void)insertText:(id)aString { |
| 406 AutocompleteTextFieldObserver* observer = [self observer]; | 461 AutocompleteTextFieldObserver* observer = [self observer]; |
| 407 if (observer) | 462 if (observer) |
| 408 observer->OnInsertText(); | 463 observer->OnInsertText(); |
| 409 | 464 |
| 410 // Repeatedly remove control characters. The loop will only ever | 465 // Repeatedly remove control characters. The loop will only ever |
| 411 // execute at all when the user enters control characters (using | 466 // execute at all when the user enters control characters (using |
| 412 // Ctrl-Alt- or Ctrl-Q). Making this generally efficient would | 467 // Ctrl-Alt- or Ctrl-Q). Making this generally efficient would |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 424 } else { | 479 } else { |
| 425 NSRange range = [aString rangeOfCharacterFromSet:forbiddenCharacters_]; | 480 NSRange range = [aString rangeOfCharacterFromSet:forbiddenCharacters_]; |
| 426 while (range.location != NSNotFound) { | 481 while (range.location != NSNotFound) { |
| 427 aString = | 482 aString = |
| 428 [aString stringByReplacingCharactersInRange:range withString:@""]; | 483 [aString stringByReplacingCharactersInRange:range withString:@""]; |
| 429 range = [aString rangeOfCharacterFromSet:forbiddenCharacters_]; | 484 range = [aString rangeOfCharacterFromSet:forbiddenCharacters_]; |
| 430 } | 485 } |
| 431 DCHECK_EQ(range.length, 0U); | 486 DCHECK_EQ(range.length, 0U); |
| 432 } | 487 } |
| 433 | 488 |
| 434 // NOTE: If |aString| is empty, this intentionally replaces the | 489 if (!NSEqualRanges(visualSelectedRange_, actualSelectedRange_)) { |
| 435 // selection with empty. This seems consistent with the case where | 490 [super replaceCharactersInRange:actualSelectedRange_ withString:aString]; |
| 436 // the input contained a mixture of characters and the string ended | 491 } else { |
| 437 // up not empty. | 492 // NOTE: If |aString| is empty, this intentionally replaces the |
| 438 [super insertText:aString]; | 493 // selection with empty. This seems consistent with the case where |
| 494 // the input contained a mixture of characters and the string ended | |
| 495 // up not empty. | |
| 496 [super insertText:aString]; | |
| 497 } | |
| 439 } | 498 } |
| 440 | 499 |
| 441 - (NSRange)selectionRangeForProposedRange:(NSRange)proposedSelRange | 500 - (NSRange)selectionRangeForProposedRange:(NSRange)proposedSelRange |
| 442 granularity:(NSSelectionGranularity)granularity { | 501 granularity:(NSSelectionGranularity)granularity { |
| 443 AutocompleteTextFieldObserver* observer = [self observer]; | 502 AutocompleteTextFieldObserver* observer = [self observer]; |
| 444 NSRange modifiedRange = [super selectionRangeForProposedRange:proposedSelRange | 503 NSRange modifiedRange = [super selectionRangeForProposedRange:proposedSelRange |
| 445 granularity:granularity]; | 504 granularity:granularity]; |
| 446 if (observer) | 505 if (observer) |
| 447 return observer->SelectionRangeForProposedRange(modifiedRange); | 506 return observer->SelectionRangeForProposedRange(modifiedRange); |
| 448 return modifiedRange; | 507 return modifiedRange; |
| 449 } | 508 } |
| 450 | 509 |
| 451 - (void)setSelectedRange:(NSRange)charRange | 510 - (void)setSelectedRange:(NSRange)charRange |
| 452 affinity:(NSSelectionAffinity)affinity | 511 affinity:(NSSelectionAffinity)affinity |
| 453 stillSelecting:(BOOL)flag { | 512 stillSelecting:(BOOL)flag { |
| 454 [super setSelectedRange:charRange affinity:affinity stillSelecting:flag]; | 513 [super setSelectedRange:charRange affinity:affinity stillSelecting:flag]; |
| 455 | 514 |
| 456 // We're only interested in selection changes directly caused by keyboard | 515 // We're only interested in selection changes directly caused by keyboard |
| 457 // input from the user. | 516 // input from the user. |
| 458 if (interpretingKeyEvents_) | 517 if (interpretingKeyEvents_) |
| 459 textChangedByKeyEvents_ = YES; | 518 textChangedByKeyEvents_ = YES; |
| 460 } | 519 } |
| 461 | 520 |
| 521 - (void)setSelectedRanges:(NSArray*)ranges | |
| 522 affinity:(NSSelectionAffinity)affinity | |
| 523 stillSelecting:(BOOL)flag { | |
| 524 DCHECK(ranges.count > 0); | |
| 525 NSMutableArray* mutableRanges = [ranges mutableCopy]; | |
|
erikchen
2016/10/14 21:06:07
gotta throw this in a scoped_nsobject to avoid a l
lgrey
2016/10/17 13:30:28
Thanks, I'm a little rusty on pre-ARC :) Done
| |
| 526 // |ranges| is sorted, and empirically, the first range passed is returned | |
| 527 // as selectedRange. | |
| 528 NSRange firstRange = [base::mac::ObjCCastStrict<NSValue>( | |
| 529 [mutableRanges firstObject]) rangeValue]; | |
| 530 actualSelectedRange_ = firstRange; | |
| 531 visualSelectedRange_ = | |
| 532 VisualSelectedRangeFromRange(firstRange, [self string]); | |
| 533 NSValue* boxedVisualRange = [NSValue valueWithRange:visualSelectedRange_]; | |
| 534 [mutableRanges replaceObjectAtIndex:0 withObject:boxedVisualRange]; | |
| 535 | |
| 536 [super setSelectedRanges:mutableRanges affinity:affinity stillSelecting:flag]; | |
| 537 } | |
| 538 | |
| 462 - (void)interpretKeyEvents:(NSArray *)eventArray { | 539 - (void)interpretKeyEvents:(NSArray *)eventArray { |
| 463 DCHECK(!interpretingKeyEvents_); | 540 DCHECK(!interpretingKeyEvents_); |
| 464 interpretingKeyEvents_ = YES; | 541 interpretingKeyEvents_ = YES; |
| 465 textChangedByKeyEvents_ = NO; | 542 textChangedByKeyEvents_ = NO; |
| 466 AutocompleteTextFieldObserver* observer = [self observer]; | 543 AutocompleteTextFieldObserver* observer = [self observer]; |
| 467 | 544 |
| 468 if (observer) | 545 if (observer) |
| 469 observer->OnBeforeChange(); | 546 observer->OnBeforeChange(); |
| 470 | 547 |
| 471 [super interpretKeyEvents:eventArray]; | 548 [super interpretKeyEvents:eventArray]; |
| (...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 557 [textStorage setAttributedString:aString]; | 634 [textStorage setAttributedString:aString]; |
| 558 | 635 |
| 559 // The text has been changed programmatically. The observer should know | 636 // The text has been changed programmatically. The observer should know |
| 560 // this change, so setting |textChangedByKeyEvents_| to NO to | 637 // this change, so setting |textChangedByKeyEvents_| to NO to |
| 561 // prevent its OnDidChange() method from being called unnecessarily. | 638 // prevent its OnDidChange() method from being called unnecessarily. |
| 562 textChangedByKeyEvents_ = NO; | 639 textChangedByKeyEvents_ = NO; |
| 563 } | 640 } |
| 564 | 641 |
| 565 - (BOOL)validateMenuItem:(NSMenuItem*)item { | 642 - (BOOL)validateMenuItem:(NSMenuItem*)item { |
| 566 if ([item action] == @selector(copyToFindPboard:)) | 643 if ([item action] == @selector(copyToFindPboard:)) |
| 567 return [self selectedRange].length > 0; | 644 return actualSelectedRange_.length > 0; |
| 568 if ([item action] == @selector(pasteAndGo:)) { | 645 if ([item action] == @selector(pasteAndGo:)) { |
| 569 // TODO(rohitrao): If the clipboard is empty, should we show a | 646 // TODO(rohitrao): If the clipboard is empty, should we show a |
| 570 // greyed-out "Paste and Go" or nothing at all? | 647 // greyed-out "Paste and Go" or nothing at all? |
| 571 AutocompleteTextFieldObserver* observer = [self observer]; | 648 AutocompleteTextFieldObserver* observer = [self observer]; |
| 572 DCHECK(observer); | 649 DCHECK(observer); |
| 573 return observer->CanPasteAndGo(); | 650 return observer->CanPasteAndGo(); |
| 574 } | 651 } |
| 575 return [super validateMenuItem:item]; | 652 return [super validateMenuItem:item]; |
| 576 } | 653 } |
| 577 | 654 |
| 578 - (void)copyToFindPboard:(id)sender { | 655 - (void)copyToFindPboard:(id)sender { |
| 579 NSRange selectedRange = [self selectedRange]; | 656 if (actualSelectedRange_.length == 0) |
| 580 if (selectedRange.length == 0) | |
| 581 return; | 657 return; |
| 582 NSAttributedString* selection = | 658 NSAttributedString* selection = |
| 583 [self attributedSubstringForProposedRange:selectedRange | 659 [self attributedSubstringForProposedRange:actualSelectedRange_ |
| 584 actualRange:NULL]; | 660 actualRange:NULL]; |
| 585 if (!selection) | 661 if (!selection) |
| 586 return; | 662 return; |
| 587 | 663 |
| 588 [[FindPasteboard sharedInstance] setFindText:[selection string]]; | 664 [[FindPasteboard sharedInstance] setFindText:[selection string]]; |
| 589 } | 665 } |
| 590 | 666 |
| 591 - (void)drawRect:(NSRect)rect { | 667 - (void)drawRect:(NSRect)rect { |
| 592 [super drawRect:rect]; | 668 [super drawRect:rect]; |
| 593 autocomplete_text_field::DrawGrayTextAutocompletion( | 669 autocomplete_text_field::DrawGrayTextAutocompletion( |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 604 // ThemedWindowDrawing implementation. | 680 // ThemedWindowDrawing implementation. |
| 605 | 681 |
| 606 - (void)windowDidChangeTheme { | 682 - (void)windowDidChangeTheme { |
| 607 [self updateColorsToMatchTheme]; | 683 [self updateColorsToMatchTheme]; |
| 608 } | 684 } |
| 609 | 685 |
| 610 - (void)windowDidChangeActive { | 686 - (void)windowDidChangeActive { |
| 611 } | 687 } |
| 612 | 688 |
| 613 @end | 689 @end |
| OLD | NEW |