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