Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(458)

Side by Side Diff: chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_editor.mm

Issue 2395233005: [Mac] Preserve original selection when suggesting completions with diacritics (Closed)
Patch Set: Missing import Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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
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
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
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698