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" |
7 #include "base/mac/sdk_forward_declarations.h" | 10 #include "base/mac/sdk_forward_declarations.h" |
8 #include "base/strings/string_util.h" | 11 #include "base/strings/string_util.h" |
9 #include "base/strings/sys_string_conversions.h" | 12 #include "base/strings/sys_string_conversions.h" |
10 #include "chrome/app/chrome_command_ids.h" // IDC_* | 13 #include "chrome/app/chrome_command_ids.h" // IDC_* |
11 #include "chrome/browser/themes/theme_service.h" | 14 #include "chrome/browser/themes/theme_service.h" |
12 #include "chrome/browser/ui/browser_list.h" | 15 #include "chrome/browser/ui/browser_list.h" |
13 #import "chrome/browser/ui/cocoa/browser_window_controller.h" | 16 #import "chrome/browser/ui/cocoa/browser_window_controller.h" |
14 #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field.h" | 17 #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field.h" |
15 #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_cell.h" | 18 #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_cell.h" |
16 #import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h" | 19 #import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h" |
(...skipping 25 matching lines...) Expand all Loading... |
42 BOOL ThePasteboardIsTooDamnBig() { | 45 BOOL ThePasteboardIsTooDamnBig() { |
43 NSPasteboard* pb = [NSPasteboard generalPasteboard]; | 46 NSPasteboard* pb = [NSPasteboard generalPasteboard]; |
44 NSString* type = | 47 NSString* type = |
45 [pb availableTypeFromArray:[NSArray arrayWithObject:NSStringPboardType]]; | 48 [pb availableTypeFromArray:[NSArray arrayWithObject:NSStringPboardType]]; |
46 if (!type) | 49 if (!type) |
47 return NO; | 50 return NO; |
48 | 51 |
49 return [[pb stringForType:type] length] > kMaxPasteLength; | 52 return [[pb stringForType:type] length] > kMaxPasteLength; |
50 } | 53 } |
51 | 54 |
| 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 |
52 } // namespace | 74 } // namespace |
53 | 75 |
| 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 |
54 @interface AutocompleteTextFieldEditor ()<NSDraggingSource> | 100 @interface AutocompleteTextFieldEditor ()<NSDraggingSource> |
55 @end | 101 @end |
56 | 102 |
57 @implementation AutocompleteTextFieldEditor | 103 @implementation AutocompleteTextFieldEditor |
58 | 104 |
| 105 @synthesize actualSelectedRange = actualSelectedRange_; |
| 106 |
59 - (BOOL)shouldDrawInsertionPoint { | 107 - (BOOL)shouldDrawInsertionPoint { |
60 return [super shouldDrawInsertionPoint] && | 108 return [super shouldDrawInsertionPoint] && |
61 ![[[self delegate] cell] hideFocusState]; | 109 ![[[self delegate] cell] hideFocusState]; |
62 } | 110 } |
63 | 111 |
64 - (id)initWithFrame:(NSRect)frameRect { | 112 - (id)initWithFrame:(NSRect)frameRect { |
65 if ((self = [super initWithFrame:frameRect])) { | 113 if ((self = [super initWithFrame:frameRect])) { |
66 dropHandler_.reset([[URLDropTargetHandler alloc] initWithView:self]); | 114 dropHandler_.reset([[URLDropTargetHandler alloc] initWithView:self]); |
67 | 115 |
68 forbiddenCharacters_.reset([[NSCharacterSet controlCharacterSet] retain]); | 116 forbiddenCharacters_.reset([[NSCharacterSet controlCharacterSet] retain]); |
(...skipping 324 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
393 // (URLDropTarget protocol) | 441 // (URLDropTarget protocol) |
394 - (void)draggingExited:(id<NSDraggingInfo>)sender { | 442 - (void)draggingExited:(id<NSDraggingInfo>)sender { |
395 return [dropHandler_ draggingExited:sender]; | 443 return [dropHandler_ draggingExited:sender]; |
396 } | 444 } |
397 | 445 |
398 // (URLDropTarget protocol) | 446 // (URLDropTarget protocol) |
399 - (BOOL)performDragOperation:(id<NSDraggingInfo>)sender { | 447 - (BOOL)performDragOperation:(id<NSDraggingInfo>)sender { |
400 return [dropHandler_ performDragOperation:sender]; | 448 return [dropHandler_ performDragOperation:sender]; |
401 } | 449 } |
402 | 450 |
| 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 |
403 // Prevent control characters from being entered into the Omnibox. | 459 // Prevent control characters from being entered into the Omnibox. |
404 // This is invoked for keyboard entry, not for pasting. | 460 // This is invoked for keyboard entry, not for pasting. |
405 - (void)insertText:(id)aString { | 461 - (void)insertText:(id)aString { |
406 AutocompleteTextFieldObserver* observer = [self observer]; | 462 AutocompleteTextFieldObserver* observer = [self observer]; |
407 if (observer) | 463 if (observer) |
408 observer->OnInsertText(); | 464 observer->OnInsertText(); |
409 | 465 |
410 // Repeatedly remove control characters. The loop will only ever | 466 // Repeatedly remove control characters. The loop will only ever |
411 // execute at all when the user enters control characters (using | 467 // execute at all when the user enters control characters (using |
412 // Ctrl-Alt- or Ctrl-Q). Making this generally efficient would | 468 // Ctrl-Alt- or Ctrl-Q). Making this generally efficient would |
(...skipping 11 matching lines...) Expand all Loading... |
424 } else { | 480 } else { |
425 NSRange range = [aString rangeOfCharacterFromSet:forbiddenCharacters_]; | 481 NSRange range = [aString rangeOfCharacterFromSet:forbiddenCharacters_]; |
426 while (range.location != NSNotFound) { | 482 while (range.location != NSNotFound) { |
427 aString = | 483 aString = |
428 [aString stringByReplacingCharactersInRange:range withString:@""]; | 484 [aString stringByReplacingCharactersInRange:range withString:@""]; |
429 range = [aString rangeOfCharacterFromSet:forbiddenCharacters_]; | 485 range = [aString rangeOfCharacterFromSet:forbiddenCharacters_]; |
430 } | 486 } |
431 DCHECK_EQ(range.length, 0U); | 487 DCHECK_EQ(range.length, 0U); |
432 } | 488 } |
433 | 489 |
434 // NOTE: If |aString| is empty, this intentionally replaces the | 490 if (!NSEqualRanges(visualSelectedRange_, actualSelectedRange_)) { |
435 // selection with empty. This seems consistent with the case where | 491 [super replaceCharactersInRange:actualSelectedRange_ withString:aString]; |
436 // the input contained a mixture of characters and the string ended | 492 } else { |
437 // up not empty. | 493 // NOTE: If |aString| is empty, this intentionally replaces the |
438 [super insertText:aString]; | 494 // selection with empty. This seems consistent with the case where |
| 495 // the input contained a mixture of characters and the string ended |
| 496 // up not empty. |
| 497 [super insertText:aString]; |
| 498 } |
439 } | 499 } |
440 | 500 |
441 - (NSRange)selectionRangeForProposedRange:(NSRange)proposedSelRange | 501 - (NSRange)selectionRangeForProposedRange:(NSRange)proposedSelRange |
442 granularity:(NSSelectionGranularity)granularity { | 502 granularity:(NSSelectionGranularity)granularity { |
443 AutocompleteTextFieldObserver* observer = [self observer]; | 503 AutocompleteTextFieldObserver* observer = [self observer]; |
444 NSRange modifiedRange = [super selectionRangeForProposedRange:proposedSelRange | 504 NSRange modifiedRange = [super selectionRangeForProposedRange:proposedSelRange |
445 granularity:granularity]; | 505 granularity:granularity]; |
446 if (observer) | 506 if (observer) |
447 return observer->SelectionRangeForProposedRange(modifiedRange); | 507 return observer->SelectionRangeForProposedRange(modifiedRange); |
448 return modifiedRange; | 508 return modifiedRange; |
449 } | 509 } |
450 | 510 |
451 - (void)setSelectedRange:(NSRange)charRange | 511 - (void)setSelectedRange:(NSRange)charRange |
452 affinity:(NSSelectionAffinity)affinity | 512 affinity:(NSSelectionAffinity)affinity |
453 stillSelecting:(BOOL)flag { | 513 stillSelecting:(BOOL)flag { |
454 [super setSelectedRange:charRange affinity:affinity stillSelecting:flag]; | 514 [super setSelectedRange:charRange affinity:affinity stillSelecting:flag]; |
455 | 515 |
456 // We're only interested in selection changes directly caused by keyboard | 516 // We're only interested in selection changes directly caused by keyboard |
457 // input from the user. | 517 // input from the user. |
458 if (interpretingKeyEvents_) | 518 if (interpretingKeyEvents_) |
459 textChangedByKeyEvents_ = YES; | 519 textChangedByKeyEvents_ = YES; |
460 } | 520 } |
461 | 521 |
| 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 |
462 - (void)interpretKeyEvents:(NSArray *)eventArray { | 540 - (void)interpretKeyEvents:(NSArray *)eventArray { |
463 DCHECK(!interpretingKeyEvents_); | 541 DCHECK(!interpretingKeyEvents_); |
464 interpretingKeyEvents_ = YES; | 542 interpretingKeyEvents_ = YES; |
465 textChangedByKeyEvents_ = NO; | 543 textChangedByKeyEvents_ = NO; |
466 AutocompleteTextFieldObserver* observer = [self observer]; | 544 AutocompleteTextFieldObserver* observer = [self observer]; |
467 | 545 |
468 if (observer) | 546 if (observer) |
469 observer->OnBeforeChange(); | 547 observer->OnBeforeChange(); |
470 | 548 |
471 [super interpretKeyEvents:eventArray]; | 549 [super interpretKeyEvents:eventArray]; |
(...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
557 [textStorage setAttributedString:aString]; | 635 [textStorage setAttributedString:aString]; |
558 | 636 |
559 // The text has been changed programmatically. The observer should know | 637 // The text has been changed programmatically. The observer should know |
560 // this change, so setting |textChangedByKeyEvents_| to NO to | 638 // this change, so setting |textChangedByKeyEvents_| to NO to |
561 // prevent its OnDidChange() method from being called unnecessarily. | 639 // prevent its OnDidChange() method from being called unnecessarily. |
562 textChangedByKeyEvents_ = NO; | 640 textChangedByKeyEvents_ = NO; |
563 } | 641 } |
564 | 642 |
565 - (BOOL)validateMenuItem:(NSMenuItem*)item { | 643 - (BOOL)validateMenuItem:(NSMenuItem*)item { |
566 if ([item action] == @selector(copyToFindPboard:)) | 644 if ([item action] == @selector(copyToFindPboard:)) |
567 return [self selectedRange].length > 0; | 645 return actualSelectedRange_.length > 0; |
568 if ([item action] == @selector(pasteAndGo:)) { | 646 if ([item action] == @selector(pasteAndGo:)) { |
569 // TODO(rohitrao): If the clipboard is empty, should we show a | 647 // TODO(rohitrao): If the clipboard is empty, should we show a |
570 // greyed-out "Paste and Go" or nothing at all? | 648 // greyed-out "Paste and Go" or nothing at all? |
571 AutocompleteTextFieldObserver* observer = [self observer]; | 649 AutocompleteTextFieldObserver* observer = [self observer]; |
572 DCHECK(observer); | 650 DCHECK(observer); |
573 return observer->CanPasteAndGo(); | 651 return observer->CanPasteAndGo(); |
574 } | 652 } |
575 return [super validateMenuItem:item]; | 653 return [super validateMenuItem:item]; |
576 } | 654 } |
577 | 655 |
578 - (void)copyToFindPboard:(id)sender { | 656 - (void)copyToFindPboard:(id)sender { |
579 NSRange selectedRange = [self selectedRange]; | 657 if (actualSelectedRange_.length == 0) |
580 if (selectedRange.length == 0) | |
581 return; | 658 return; |
582 NSAttributedString* selection = | 659 NSAttributedString* selection = |
583 [self attributedSubstringForProposedRange:selectedRange | 660 [self attributedSubstringForProposedRange:actualSelectedRange_ |
584 actualRange:NULL]; | 661 actualRange:NULL]; |
585 if (!selection) | 662 if (!selection) |
586 return; | 663 return; |
587 | 664 |
588 [[FindPasteboard sharedInstance] setFindText:[selection string]]; | 665 [[FindPasteboard sharedInstance] setFindText:[selection string]]; |
589 } | 666 } |
590 | 667 |
591 - (void)drawRect:(NSRect)rect { | 668 - (void)drawRect:(NSRect)rect { |
592 [super drawRect:rect]; | 669 [super drawRect:rect]; |
593 autocomplete_text_field::DrawGrayTextAutocompletion( | 670 autocomplete_text_field::DrawGrayTextAutocompletion( |
(...skipping 10 matching lines...) Expand all Loading... |
604 // ThemedWindowDrawing implementation. | 681 // ThemedWindowDrawing implementation. |
605 | 682 |
606 - (void)windowDidChangeTheme { | 683 - (void)windowDidChangeTheme { |
607 [self updateColorsToMatchTheme]; | 684 [self updateColorsToMatchTheme]; |
608 } | 685 } |
609 | 686 |
610 - (void)windowDidChangeActive { | 687 - (void)windowDidChangeActive { |
611 } | 688 } |
612 | 689 |
613 @end | 690 @end |
OLD | NEW |