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

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: Wrap retained object in scoped_nsobject 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"
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
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
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
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
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