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

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: Handle setSelectedRanges: 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"
7 #include "base/mac/sdk_forward_declarations.h" 8 #include "base/mac/sdk_forward_declarations.h"
8 #include "base/strings/string_util.h" 9 #include "base/strings/string_util.h"
9 #include "base/strings/sys_string_conversions.h" 10 #include "base/strings/sys_string_conversions.h"
10 #include "chrome/app/chrome_command_ids.h" // IDC_* 11 #include "chrome/app/chrome_command_ids.h" // IDC_*
11 #include "chrome/browser/themes/theme_service.h" 12 #include "chrome/browser/themes/theme_service.h"
12 #include "chrome/browser/ui/browser_list.h" 13 #include "chrome/browser/ui/browser_list.h"
13 #import "chrome/browser/ui/cocoa/browser_window_controller.h" 14 #import "chrome/browser/ui/cocoa/browser_window_controller.h"
14 #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field.h" 15 #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field.h"
15 #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_cell.h" 16 #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_cell.h"
16 #import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h" 17 #import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h"
(...skipping 25 matching lines...) Expand all
42 BOOL ThePasteboardIsTooDamnBig() { 43 BOOL ThePasteboardIsTooDamnBig() {
43 NSPasteboard* pb = [NSPasteboard generalPasteboard]; 44 NSPasteboard* pb = [NSPasteboard generalPasteboard];
44 NSString* type = 45 NSString* type =
45 [pb availableTypeFromArray:[NSArray arrayWithObject:NSStringPboardType]]; 46 [pb availableTypeFromArray:[NSArray arrayWithObject:NSStringPboardType]];
46 if (!type) 47 if (!type)
47 return NO; 48 return NO;
48 49
49 return [[pb stringForType:type] length] > kMaxPasteLength; 50 return [[pb stringForType:type] length] > kMaxPasteLength;
50 } 51 }
51 52
53 NSRange VisualSelectedRangeFromRange(NSRange range, NSString* string) {
54 if (range.location >= string.length)
55 return range;
56 base::string16 text = base::SysNSStringToUTF16(string);
57 base::i18n::BreakIterator grapheme_iterator(
58 text, base::i18n::BreakIterator::BREAK_CHARACTER);
59 if (!grapheme_iterator.Init())
60 return range;
61 // This works because NSString uses UTF-16 code units.
62 while (range.location < text.length() &&
63 !grapheme_iterator.IsGraphemeBoundary(
64 static_cast<size_t>(range.location))) {
65 range.location++;
66 if (range.length > 0)
67 range.length--;
68 }
69 return range;
70 }
71
52 } // namespace 72 } // namespace
53 73
74 // Method exposed for the purpose of overriding.
75 // Used to restore model's selection range when the view doesn't
76 // match the model due to combining characters.
77 //
78 // In some cases, (completing 'y' to 'ÿour', for example), the autocomplete
79 // system requests a selection range that begins on a combining character.
80 // setSelectedRange: and friends document that the range passed to them
81 // "must begin and end on glyph boundaries and not split base glyphs and
82 // their nonspacing marks". If passed such a range, the selection is
83 // expanded to include the original user input, preventing the user
84 // from being able to type other words beginning with that letter.
85 //
86 // To resolve this, the field editor modifies the selection to start
87 // on the next glyph boundary, then keeps track of the original and
88 // modified selections, substituting the original when the user takes
89 // actions that operate on the selection. Since there are many methods
90 // in NSResponder (for example deleteToBeginningOfLine:) that operate
91 // on the selection, rather than shimming them all, we override this
92 // private method that they're implemented in terms of.
93 //
94 @interface NSTextView (PrivateTextEditing)
95 - (void)_userReplaceRange:(NSRange)range withString:(NSString*)string;
96 @end
97
54 @interface AutocompleteTextFieldEditor ()<NSDraggingSource> 98 @interface AutocompleteTextFieldEditor ()<NSDraggingSource>
55 @end 99 @end
56 100
57 @implementation AutocompleteTextFieldEditor 101 @implementation AutocompleteTextFieldEditor
58 102
103 @synthesize actualSelectedRange = actualSelectedRange_;
104
59 - (BOOL)shouldDrawInsertionPoint { 105 - (BOOL)shouldDrawInsertionPoint {
60 return [super shouldDrawInsertionPoint] && 106 return [super shouldDrawInsertionPoint] &&
61 ![[[self delegate] cell] hideFocusState]; 107 ![[[self delegate] cell] hideFocusState];
62 } 108 }
63 109
64 - (id)initWithFrame:(NSRect)frameRect { 110 - (id)initWithFrame:(NSRect)frameRect {
65 if ((self = [super initWithFrame:frameRect])) { 111 if ((self = [super initWithFrame:frameRect])) {
66 dropHandler_.reset([[URLDropTargetHandler alloc] initWithView:self]); 112 dropHandler_.reset([[URLDropTargetHandler alloc] initWithView:self]);
67 113
68 forbiddenCharacters_.reset([[NSCharacterSet controlCharacterSet] retain]); 114 forbiddenCharacters_.reset([[NSCharacterSet controlCharacterSet] retain]);
(...skipping 324 matching lines...) Expand 10 before | Expand all | Expand 10 after
393 // (URLDropTarget protocol) 439 // (URLDropTarget protocol)
394 - (void)draggingExited:(id<NSDraggingInfo>)sender { 440 - (void)draggingExited:(id<NSDraggingInfo>)sender {
395 return [dropHandler_ draggingExited:sender]; 441 return [dropHandler_ draggingExited:sender];
396 } 442 }
397 443
398 // (URLDropTarget protocol) 444 // (URLDropTarget protocol)
399 - (BOOL)performDragOperation:(id<NSDraggingInfo>)sender { 445 - (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
400 return [dropHandler_ performDragOperation:sender]; 446 return [dropHandler_ performDragOperation:sender];
401 } 447 }
402 448
449 - (void)_userReplaceRange:(NSRange)range withString:(NSString*)string {
450 if (NSEqualRanges(visualSelectedRange_, range) &&
451 !NSEqualRanges(visualSelectedRange_, actualSelectedRange_)) {
452 range = actualSelectedRange_;
453 }
454 [super _userReplaceRange:range withString:string];
455 }
456
403 // Prevent control characters from being entered into the Omnibox. 457 // Prevent control characters from being entered into the Omnibox.
404 // This is invoked for keyboard entry, not for pasting. 458 // This is invoked for keyboard entry, not for pasting.
405 - (void)insertText:(id)aString { 459 - (void)insertText:(id)aString {
406 AutocompleteTextFieldObserver* observer = [self observer]; 460 AutocompleteTextFieldObserver* observer = [self observer];
407 if (observer) 461 if (observer)
408 observer->OnInsertText(); 462 observer->OnInsertText();
409 463
410 // Repeatedly remove control characters. The loop will only ever 464 // Repeatedly remove control characters. The loop will only ever
411 // execute at all when the user enters control characters (using 465 // execute at all when the user enters control characters (using
412 // Ctrl-Alt- or Ctrl-Q). Making this generally efficient would 466 // Ctrl-Alt- or Ctrl-Q). Making this generally efficient would
(...skipping 11 matching lines...) Expand all
424 } else { 478 } else {
425 NSRange range = [aString rangeOfCharacterFromSet:forbiddenCharacters_]; 479 NSRange range = [aString rangeOfCharacterFromSet:forbiddenCharacters_];
426 while (range.location != NSNotFound) { 480 while (range.location != NSNotFound) {
427 aString = 481 aString =
428 [aString stringByReplacingCharactersInRange:range withString:@""]; 482 [aString stringByReplacingCharactersInRange:range withString:@""];
429 range = [aString rangeOfCharacterFromSet:forbiddenCharacters_]; 483 range = [aString rangeOfCharacterFromSet:forbiddenCharacters_];
430 } 484 }
431 DCHECK_EQ(range.length, 0U); 485 DCHECK_EQ(range.length, 0U);
432 } 486 }
433 487
434 // NOTE: If |aString| is empty, this intentionally replaces the 488 if (!NSEqualRanges(visualSelectedRange_, actualSelectedRange_)) {
435 // selection with empty. This seems consistent with the case where 489 [super replaceCharactersInRange:actualSelectedRange_ withString:aString];
436 // the input contained a mixture of characters and the string ended 490 } else {
437 // up not empty. 491 // NOTE: If |aString| is empty, this intentionally replaces the
438 [super insertText:aString]; 492 // selection with empty. This seems consistent with the case where
493 // the input contained a mixture of characters and the string ended
494 // up not empty.
495 [super insertText:aString];
496 }
439 } 497 }
440 498
441 - (NSRange)selectionRangeForProposedRange:(NSRange)proposedSelRange 499 - (NSRange)selectionRangeForProposedRange:(NSRange)proposedSelRange
442 granularity:(NSSelectionGranularity)granularity { 500 granularity:(NSSelectionGranularity)granularity {
443 AutocompleteTextFieldObserver* observer = [self observer]; 501 AutocompleteTextFieldObserver* observer = [self observer];
444 NSRange modifiedRange = [super selectionRangeForProposedRange:proposedSelRange 502 NSRange modifiedRange = [super selectionRangeForProposedRange:proposedSelRange
445 granularity:granularity]; 503 granularity:granularity];
446 if (observer) 504 if (observer)
447 return observer->SelectionRangeForProposedRange(modifiedRange); 505 return observer->SelectionRangeForProposedRange(modifiedRange);
448 return modifiedRange; 506 return modifiedRange;
449 } 507 }
450 508
451 - (void)setSelectedRange:(NSRange)charRange 509 - (void)setSelectedRange:(NSRange)charRange
452 affinity:(NSSelectionAffinity)affinity 510 affinity:(NSSelectionAffinity)affinity
453 stillSelecting:(BOOL)flag { 511 stillSelecting:(BOOL)flag {
454 [super setSelectedRange:charRange affinity:affinity stillSelecting:flag]; 512 actualSelectedRange_ = charRange;
513 visualSelectedRange_ = VisualSelectedRangeFromRange(charRange, [self string]);
514 [super setSelectedRange:visualSelectedRange_
515 affinity:affinity
516 stillSelecting:flag];
455 517
456 // We're only interested in selection changes directly caused by keyboard 518 // We're only interested in selection changes directly caused by keyboard
457 // input from the user. 519 // input from the user.
458 if (interpretingKeyEvents_) 520 if (interpretingKeyEvents_)
459 textChangedByKeyEvents_ = YES; 521 textChangedByKeyEvents_ = YES;
460 } 522 }
461 523
524 // This is called by |moveLeftAndModifySelection:| and similar methods, despite
525 // the fact that this field is opted out of multiple selection due to the
526 // delegate not implementing
527 // |textView:willChangeSelectionFromCharacterRanges:oldSelectedCharRanges:
528 // toCharacterRanges:|
529 //
530 // Since this method isn't being invoked by the omnibox completion, we just
531 // ensure that |visualSelectedRange_| and |actualSelectedRange_| and don't try
532 // to adjust for grapheme boundaries.
533 - (void)setSelectedRanges:(NSArray<NSValue *> *) ranges
534 affinity:(NSSelectionAffinity)affinity
535 stillSelecting:(BOOL)flag {
erikchen 2016/10/14 18:41:35 -setSelectedRange:... actually calls -setSelectedR
lgrey 2016/10/14 20:42:35 How's this?
536 [super setSelectedRanges:ranges affinity:affinity stillSelecting:flag];
537 actualSelectedRange_ = visualSelectedRange_ = [self selectedRange];
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
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
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698