Home | History | Annotate | Download | only in autocomplete
      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
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/autocomplete/autocomplete_edit_view_mac.h"
      6 
      7 #include <Carbon/Carbon.h>  // kVK_Return
      8 
      9 #include "app/mac/nsimage_cache.h"
     10 #include "base/string_util.h"
     11 #include "base/sys_string_conversions.h"
     12 #include "base/utf_string_conversions.h"
     13 #include "chrome/browser/autocomplete/autocomplete_edit.h"
     14 #include "chrome/browser/autocomplete/autocomplete_match.h"
     15 #include "chrome/browser/autocomplete/autocomplete_popup_model.h"
     16 #include "chrome/browser/autocomplete/autocomplete_popup_view_mac.h"
     17 #include "chrome/browser/browser_process.h"
     18 #include "chrome/browser/ui/cocoa/event_utils.h"
     19 #include "chrome/browser/ui/toolbar/toolbar_model.h"
     20 #include "content/browser/tab_contents/tab_contents.h"
     21 #include "grit/generated_resources.h"
     22 #include "grit/theme_resources.h"
     23 #include "net/base/escape.h"
     24 #import "third_party/mozilla/NSPasteboard+Utils.h"
     25 #include "ui/base/clipboard/clipboard.h"
     26 #include "ui/base/resource/resource_bundle.h"
     27 #include "ui/gfx/image.h"
     28 #include "ui/gfx/rect.h"
     29 
     30 // Focus-handling between |field_| and |model_| is a bit subtle.
     31 // Other platforms detect change of focus, which is inconvenient
     32 // without subclassing NSTextField (even with a subclass, the use of a
     33 // field editor may complicate things).
     34 //
     35 // |model_| doesn't actually do anything when it gains focus, it just
     36 // initializes.  Visible activity happens only after the user edits.
     37 // NSTextField delegate receives messages around starting and ending
     38 // edits, so that suffices to catch focus changes.  Since all calls
     39 // into |model_| start from AutocompleteEditViewMac, in the worst case
     40 // we can add code to sync up the sense of focus as needed.
     41 //
     42 // I've added DCHECK(IsFirstResponder()) in the places which I believe
     43 // should only be reachable when |field_| is being edited.  If these
     44 // fire, it probably means someone unexpected is calling into
     45 // |model_|.
     46 //
     47 // Other platforms don't appear to have the sense of "key window" that
     48 // Mac does (I believe their fields lose focus when the window loses
     49 // focus).  Rather than modifying focus outside the control's edit
     50 // scope, when the window resigns key the autocomplete popup is
     51 // closed.  |model_| still believes it has focus, and the popup will
     52 // be regenerated on the user's next edit.  That seems to match how
     53 // things work on other platforms.
     54 
     55 namespace {
     56 
     57 // TODO(shess): This is ugly, find a better way.  Using it right now
     58 // so that I can crib from gtk and still be able to see that I'm using
     59 // the same values easily.
     60 NSColor* ColorWithRGBBytes(int rr, int gg, int bb) {
     61   DCHECK_LE(rr, 255);
     62   DCHECK_LE(bb, 255);
     63   DCHECK_LE(gg, 255);
     64   return [NSColor colorWithCalibratedRed:static_cast<float>(rr)/255.0
     65                                    green:static_cast<float>(gg)/255.0
     66                                     blue:static_cast<float>(bb)/255.0
     67                                    alpha:1.0];
     68 }
     69 
     70 NSColor* HostTextColor() {
     71   return [NSColor blackColor];
     72 }
     73 NSColor* BaseTextColor() {
     74   return [NSColor darkGrayColor];
     75 }
     76 NSColor* SuggestTextColor() {
     77   return [NSColor grayColor];
     78 }
     79 NSColor* SecureSchemeColor() {
     80   return ColorWithRGBBytes(0x07, 0x95, 0x00);
     81 }
     82 NSColor* SecurityErrorSchemeColor() {
     83   return ColorWithRGBBytes(0xa2, 0x00, 0x00);
     84 }
     85 
     86 // Store's the model and view state across tab switches.
     87 struct AutocompleteEditViewMacState {
     88   AutocompleteEditViewMacState(const AutocompleteEditModel::State model_state,
     89                                const bool has_focus, const NSRange& selection)
     90       : model_state(model_state),
     91         has_focus(has_focus),
     92         selection(selection) {
     93   }
     94 
     95   const AutocompleteEditModel::State model_state;
     96   const bool has_focus;
     97   const NSRange selection;
     98 };
     99 
    100 // Returns a lazily initialized property bag accessor for saving our
    101 // state in a TabContents.  When constructed |accessor| generates a
    102 // globally-unique id used to index into the per-tab PropertyBag used
    103 // to store the state data.
    104 PropertyAccessor<AutocompleteEditViewMacState>* GetStateAccessor() {
    105   static PropertyAccessor<AutocompleteEditViewMacState> accessor;
    106   return &accessor;
    107 }
    108 
    109 // Accessors for storing and getting the state from the tab.
    110 void StoreStateToTab(TabContents* tab,
    111                      const AutocompleteEditViewMacState& state) {
    112   GetStateAccessor()->SetProperty(tab->property_bag(), state);
    113 }
    114 const AutocompleteEditViewMacState* GetStateFromTab(const TabContents* tab) {
    115   return GetStateAccessor()->GetProperty(tab->property_bag());
    116 }
    117 
    118 // Helper to make converting url_parse ranges to NSRange easier to
    119 // read.
    120 NSRange ComponentToNSRange(const url_parse::Component& component) {
    121   return NSMakeRange(static_cast<NSInteger>(component.begin),
    122                      static_cast<NSInteger>(component.len));
    123 }
    124 
    125 }  // namespace
    126 
    127 // static
    128 NSImage* AutocompleteEditViewMac::ImageForResource(int resource_id) {
    129   NSString* image_name = nil;
    130 
    131   switch(resource_id) {
    132     // From the autocomplete popup, or the star icon at the RHS of the
    133     // text field.
    134     case IDR_STAR: image_name = @"star.pdf"; break;
    135     case IDR_STAR_LIT: image_name = @"star_lit.pdf"; break;
    136 
    137     // Values from |AutocompleteMatch::TypeToIcon()|.
    138     case IDR_OMNIBOX_SEARCH:
    139       image_name = @"omnibox_search.pdf"; break;
    140     case IDR_OMNIBOX_HTTP:
    141       image_name = @"omnibox_http.pdf"; break;
    142     case IDR_OMNIBOX_HISTORY:
    143       image_name = @"omnibox_history.pdf"; break;
    144     case IDR_OMNIBOX_EXTENSION_APP:
    145       image_name = @"omnibox_extension_app.pdf"; break;
    146 
    147     // Values from |ToolbarModel::GetIcon()|.
    148     case IDR_OMNIBOX_HTTPS_VALID:
    149       image_name = @"omnibox_https_valid.pdf"; break;
    150     case IDR_OMNIBOX_HTTPS_WARNING:
    151       image_name = @"omnibox_https_warning.pdf"; break;
    152     case IDR_OMNIBOX_HTTPS_INVALID:
    153       image_name = @"omnibox_https_invalid.pdf"; break;
    154   }
    155 
    156   if (image_name) {
    157     if (NSImage* image = app::mac::GetCachedImageWithName(image_name)) {
    158       return image;
    159     } else {
    160       NOTREACHED()
    161           << "Missing image for " << base::SysNSStringToUTF8(image_name);
    162     }
    163   }
    164 
    165   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
    166   return rb.GetNativeImageNamed(resource_id);
    167 }
    168 
    169 AutocompleteEditViewMac::AutocompleteEditViewMac(
    170     AutocompleteEditController* controller,
    171     ToolbarModel* toolbar_model,
    172     Profile* profile,
    173     CommandUpdater* command_updater,
    174     AutocompleteTextField* field)
    175     : model_(new AutocompleteEditModel(this, controller, profile)),
    176       popup_view_(new AutocompletePopupViewMac(this, model_.get(), profile,
    177                                                field)),
    178       controller_(controller),
    179       toolbar_model_(toolbar_model),
    180       command_updater_(command_updater),
    181       field_(field),
    182       suggest_text_length_(0),
    183       delete_was_pressed_(false),
    184       delete_at_end_pressed_(false),
    185       line_height_(0) {
    186   DCHECK(controller);
    187   DCHECK(toolbar_model);
    188   DCHECK(profile);
    189   DCHECK(command_updater);
    190   DCHECK(field);
    191   [field_ setObserver:this];
    192 
    193   // Needed so that editing doesn't lose the styling.
    194   [field_ setAllowsEditingTextAttributes:YES];
    195 
    196   // Get the appropriate line height for the font that we use.
    197   scoped_nsobject<NSLayoutManager>
    198       layoutManager([[NSLayoutManager alloc] init]);
    199   [layoutManager setUsesScreenFonts:YES];
    200   line_height_ = [layoutManager defaultLineHeightForFont:GetFieldFont()];
    201   DCHECK_GT(line_height_, 0);
    202 }
    203 
    204 AutocompleteEditViewMac::~AutocompleteEditViewMac() {
    205   // Destroy popup view before this object in case it tries to call us
    206   // back in the destructor.  Likewise for destroying the model before
    207   // this object.
    208   popup_view_.reset();
    209   model_.reset();
    210 
    211   // Disconnect from |field_|, it outlives this object.
    212   [field_ setObserver:NULL];
    213 }
    214 
    215 AutocompleteEditModel* AutocompleteEditViewMac::model() {
    216   return model_.get();
    217 }
    218 
    219 const AutocompleteEditModel* AutocompleteEditViewMac::model() const {
    220   return model_.get();
    221 }
    222 
    223 void AutocompleteEditViewMac::SaveStateToTab(TabContents* tab) {
    224   DCHECK(tab);
    225 
    226   const bool hasFocus = [field_ currentEditor] ? true : false;
    227 
    228   NSRange range;
    229   if (hasFocus) {
    230     range = GetSelectedRange();
    231   } else {
    232     // If we are not focussed, there is no selection.  Manufacture
    233     // something reasonable in case it starts to matter in the future.
    234     range = NSMakeRange(0, GetTextLength());
    235   }
    236 
    237   AutocompleteEditViewMacState state(model_->GetStateForTabSwitch(),
    238                                      hasFocus, range);
    239   StoreStateToTab(tab, state);
    240 }
    241 
    242 void AutocompleteEditViewMac::Update(
    243     const TabContents* tab_for_state_restoring) {
    244   // TODO(shess): It seems like if the tab is non-NULL, then this code
    245   // shouldn't need to be called at all.  When coded that way, I find
    246   // that the field isn't always updated correctly.  Figure out why
    247   // this is.  Maybe this method should be refactored into more
    248   // specific cases.
    249   const bool user_visible =
    250       model_->UpdatePermanentText(WideToUTF16Hack(toolbar_model_->GetText()));
    251 
    252   if (tab_for_state_restoring) {
    253     RevertAll();
    254 
    255     const AutocompleteEditViewMacState* state =
    256         GetStateFromTab(tab_for_state_restoring);
    257     if (state) {
    258       // Should restore the user's text via SetUserText().
    259       model_->RestoreState(state->model_state);
    260 
    261       // Restore focus and selection if they were present when the tab
    262       // was switched away.
    263       if (state->has_focus) {
    264         // TODO(shess): Unfortunately, there is no safe way to update
    265         // this because TabStripController -selectTabWithContents:* is
    266         // also messing with focus.  Both parties need to agree to
    267         // store existing state before anyone tries to setup the new
    268         // state.  Anyhow, it would look something like this.
    269 #if 0
    270         [[field_ window] makeFirstResponder:field_];
    271         [[field_ currentEditor] setSelectedRange:state->selection];
    272 #endif
    273       }
    274     }
    275   } else if (user_visible) {
    276     // Restore everything to the baseline look.
    277     RevertAll();
    278     // TODO(shess): Figure out how this case is used, to make sure
    279     // we're getting the selection and popup right.
    280 
    281   } else {
    282     // TODO(shess): This corresponds to _win and _gtk, except those
    283     // guard it with a test for whether the security level changed.
    284     // But AFAICT, that can only change if the text changed, and that
    285     // code compares the toolbar_model_ security level with the local
    286     // security level.  Dig in and figure out why this isn't a no-op
    287     // that should go away.
    288     EmphasizeURLComponents();
    289   }
    290 }
    291 
    292 void AutocompleteEditViewMac::OpenURL(const GURL& url,
    293                                       WindowOpenDisposition disposition,
    294                                       PageTransition::Type transition,
    295                                       const GURL& alternate_nav_url,
    296                                       size_t selected_line,
    297                                       const string16& keyword) {
    298   // TODO(shess): Why is the caller passing an invalid url in the
    299   // first place?  Make sure that case isn't being dropped on the
    300   // floor.
    301   if (!url.is_valid()) {
    302     return;
    303   }
    304 
    305   model_->OpenURL(url, disposition, transition, alternate_nav_url,
    306                   selected_line, keyword);
    307 }
    308 
    309 string16 AutocompleteEditViewMac::GetText() const {
    310   return base::SysNSStringToUTF16(GetNonSuggestTextSubstring());
    311 }
    312 
    313 bool AutocompleteEditViewMac::IsEditingOrEmpty() const {
    314   return model_->user_input_in_progress() || !GetTextLength();
    315 }
    316 
    317 int AutocompleteEditViewMac::GetIcon() const {
    318   return IsEditingOrEmpty() ?
    319       AutocompleteMatch::TypeToIcon(model_->CurrentTextType()) :
    320       toolbar_model_->GetIcon();
    321 }
    322 
    323 void AutocompleteEditViewMac::SetUserText(const string16& text) {
    324   SetUserText(text, text, true);
    325 }
    326 
    327 void AutocompleteEditViewMac::SetUserText(const string16& text,
    328                                           const string16& display_text,
    329                                           bool update_popup) {
    330   model_->SetUserText(text);
    331   // TODO(shess): TODO below from gtk.
    332   // TODO(deanm): something about selection / focus change here.
    333   SetText(display_text);
    334   if (update_popup) {
    335     UpdatePopup();
    336   }
    337   model_->OnChanged();
    338 }
    339 
    340 NSRange AutocompleteEditViewMac::GetSelectedRange() const {
    341   return [[field_ currentEditor] selectedRange];
    342 }
    343 
    344 NSRange AutocompleteEditViewMac::GetMarkedRange() const {
    345   DCHECK([field_ currentEditor]);
    346   return [(NSTextView*)[field_ currentEditor] markedRange];
    347 }
    348 
    349 void AutocompleteEditViewMac::SetSelectedRange(const NSRange range) {
    350   // This can be called when we don't have focus.  For instance, when
    351   // the user clicks the "Go" button.
    352   if (model_->has_focus()) {
    353     // TODO(shess): If |model_| thinks we have focus, this should not
    354     // be necessary.  Try to convert to DCHECK(IsFirstResponder()).
    355     if (![field_ currentEditor]) {
    356       [[field_ window] makeFirstResponder:field_];
    357     }
    358 
    359     // TODO(shess): What if it didn't get first responder, and there is
    360     // no field editor?  This will do nothing.  Well, at least it won't
    361     // crash.  Think of something more productive to do, or prove that
    362     // it cannot occur and DCHECK appropriately.
    363     [[field_ currentEditor] setSelectedRange:range];
    364   }
    365 }
    366 
    367 void AutocompleteEditViewMac::SetWindowTextAndCaretPos(const string16& text,
    368                                                        size_t caret_pos) {
    369   DCHECK_LE(caret_pos, text.size());
    370   SetTextAndSelectedRange(text, NSMakeRange(caret_pos, caret_pos));
    371 }
    372 
    373 void AutocompleteEditViewMac::SetForcedQuery() {
    374   // We need to do this first, else |SetSelectedRange()| won't work.
    375   FocusLocation(true);
    376 
    377   const string16 current_text(GetText());
    378   const size_t start = current_text.find_first_not_of(kWhitespaceUTF16);
    379   if (start == string16::npos || (current_text[start] != '?')) {
    380     SetUserText(ASCIIToUTF16("?"));
    381   } else {
    382     NSRange range = NSMakeRange(start + 1, current_text.size() - start - 1);
    383     [[field_ currentEditor] setSelectedRange:range];
    384   }
    385 }
    386 
    387 bool AutocompleteEditViewMac::IsSelectAll() {
    388   if (![field_ currentEditor])
    389     return true;
    390   const NSRange all_range = NSMakeRange(0, GetTextLength());
    391   return NSEqualRanges(all_range, GetSelectedRange());
    392 }
    393 
    394 bool AutocompleteEditViewMac::DeleteAtEndPressed() {
    395   return delete_at_end_pressed_;
    396 }
    397 
    398 void AutocompleteEditViewMac::GetSelectionBounds(string16::size_type* start,
    399                                                  string16::size_type* end) {
    400   if (![field_ currentEditor]) {
    401     *start = *end = 0;
    402     return;
    403   }
    404 
    405   const NSRange selected_range = GetSelectedRange();
    406   *start = static_cast<size_t>(selected_range.location);
    407   *end = static_cast<size_t>(NSMaxRange(selected_range));
    408 }
    409 
    410 void AutocompleteEditViewMac::SelectAll(bool reversed) {
    411   // TODO(shess): Figure out what |reversed| implies.  The gtk version
    412   // has it imply inverting the selection front to back, but I don't
    413   // even know if that makes sense for Mac.
    414 
    415   // TODO(shess): Verify that we should be stealing focus at this
    416   // point.
    417   SetSelectedRange(NSMakeRange(0, GetTextLength()));
    418 }
    419 
    420 void AutocompleteEditViewMac::RevertAll() {
    421   ClosePopup();
    422   model_->Revert();
    423   model_->OnChanged();
    424   [field_ clearUndoChain];
    425 }
    426 
    427 void AutocompleteEditViewMac::UpdatePopup() {
    428   model_->SetInputInProgress(true);
    429   if (!model_->has_focus())
    430     return;
    431 
    432   // Comment copied from AutocompleteEditViewWin::UpdatePopup():
    433   // Don't inline autocomplete when:
    434   //   * The user is deleting text
    435   //   * The caret/selection isn't at the end of the text
    436   //   * The user has just pasted in something that replaced all the text
    437   //   * The user is trying to compose something in an IME
    438   bool prevent_inline_autocomplete = IsImeComposing();
    439   NSTextView* editor = (NSTextView*)[field_ currentEditor];
    440   if (editor) {
    441     if (NSMaxRange([editor selectedRange]) <
    442         [[editor textStorage] length] - suggest_text_length_)
    443       prevent_inline_autocomplete = true;
    444   }
    445 
    446   model_->StartAutocomplete([editor selectedRange].length != 0,
    447                             prevent_inline_autocomplete);
    448 }
    449 
    450 void AutocompleteEditViewMac::ClosePopup() {
    451   model_->StopAutocomplete();
    452 }
    453 
    454 void AutocompleteEditViewMac::SetFocus() {
    455 }
    456 
    457 void AutocompleteEditViewMac::SetText(const string16& display_text) {
    458   // If we are setting the text directly, there cannot be any suggest text.
    459   suggest_text_length_ = 0;
    460   SetTextInternal(display_text);
    461 }
    462 
    463 void AutocompleteEditViewMac::SetTextInternal(
    464     const string16& display_text) {
    465   NSString* ss = base::SysUTF16ToNSString(display_text);
    466   NSMutableAttributedString* as =
    467       [[[NSMutableAttributedString alloc] initWithString:ss] autorelease];
    468 
    469   ApplyTextAttributes(display_text, as);
    470 
    471   [field_ setAttributedStringValue:as];
    472 
    473   // TODO(shess): This may be an appropriate place to call:
    474   //   model_->OnChanged();
    475   // In the current implementation, this tells LocationBarViewMac to
    476   // mess around with |model_| and update |field_|.  Unfortunately,
    477   // when I look at our peer implementations, it's not entirely clear
    478   // to me if this is safe.  SetTextInternal() is sort of an utility method,
    479   // and different callers sometimes have different needs.  Research
    480   // this issue so that it can be added safely.
    481 
    482   // TODO(shess): Also, consider whether this code couldn't just
    483   // manage things directly.  Windows uses a series of overlaid view
    484   // objects to accomplish the hinting stuff that OnChanged() does, so
    485   // it makes sense to have it in the controller that lays those
    486   // things out.  Mac instead pushes the support into a custom
    487   // text-field implementation.
    488 }
    489 
    490 void AutocompleteEditViewMac::SetTextAndSelectedRange(
    491     const string16& display_text, const NSRange range) {
    492   SetText(display_text);
    493   SetSelectedRange(range);
    494 }
    495 
    496 NSString* AutocompleteEditViewMac::GetNonSuggestTextSubstring() const {
    497   NSString* text = [field_ stringValue];
    498   if (suggest_text_length_ > 0) {
    499     NSUInteger length = [text length];
    500 
    501     DCHECK_LE(suggest_text_length_, length);
    502     text = [text substringToIndex:(length - suggest_text_length_)];
    503   }
    504   return text;
    505 }
    506 
    507 NSString* AutocompleteEditViewMac::GetSuggestTextSubstring() const {
    508   if (suggest_text_length_ == 0)
    509     return nil;
    510 
    511   NSString* text = [field_ stringValue];
    512   NSUInteger length = [text length];
    513   DCHECK_LE(suggest_text_length_, length);
    514   return [text substringFromIndex:(length - suggest_text_length_)];
    515 }
    516 
    517 void AutocompleteEditViewMac::EmphasizeURLComponents() {
    518   NSTextView* editor = (NSTextView*)[field_ currentEditor];
    519   // If the autocomplete text field is in editing mode, then we can just change
    520   // its attributes through its editor. Otherwise, we simply reset its content.
    521   if (editor) {
    522     NSTextStorage* storage = [editor textStorage];
    523     [storage beginEditing];
    524 
    525     // Clear the existing attributes from the text storage, then
    526     // overlay the appropriate Omnibox attributes.
    527     [storage setAttributes:[NSDictionary dictionary]
    528                      range:NSMakeRange(0, [storage length])];
    529     ApplyTextAttributes(GetText(), storage);
    530 
    531     [storage endEditing];
    532   } else {
    533     SetText(GetText());
    534   }
    535 }
    536 
    537 void AutocompleteEditViewMac::ApplyTextAttributes(
    538     const string16& display_text, NSMutableAttributedString* as) {
    539   [as addAttribute:NSFontAttributeName value:GetFieldFont()
    540              range:NSMakeRange(0, [as length])];
    541 
    542   // Make a paragraph style locking in the standard line height as the maximum,
    543   // otherwise the baseline may shift "downwards".
    544   scoped_nsobject<NSMutableParagraphStyle>
    545       paragraph_style([[NSMutableParagraphStyle alloc] init]);
    546   [paragraph_style setMaximumLineHeight:line_height_];
    547   [as addAttribute:NSParagraphStyleAttributeName value:paragraph_style
    548              range:NSMakeRange(0, [as length])];
    549 
    550   // Grey out the suggest text.
    551   [as addAttribute:NSForegroundColorAttributeName value:SuggestTextColor()
    552              range:NSMakeRange([as length] - suggest_text_length_,
    553                                suggest_text_length_)];
    554 
    555   url_parse::Component scheme, host;
    556   AutocompleteInput::ParseForEmphasizeComponents(
    557       display_text, model_->GetDesiredTLD(), &scheme, &host);
    558   const bool emphasize = model_->CurrentTextIsURL() && (host.len > 0);
    559   if (emphasize) {
    560     [as addAttribute:NSForegroundColorAttributeName value:BaseTextColor()
    561                range:NSMakeRange(0, [as length])];
    562 
    563     [as addAttribute:NSForegroundColorAttributeName value:HostTextColor()
    564                range:ComponentToNSRange(host)];
    565   }
    566 
    567   // TODO(shess): GTK has this as a member var, figure out why.
    568   // [Could it be to not change if no change?  If so, I'm guessing
    569   // AppKit may already handle that.]
    570   const ToolbarModel::SecurityLevel security_level =
    571       toolbar_model_->GetSecurityLevel();
    572 
    573   // Emphasize the scheme for security UI display purposes (if necessary).
    574   if (!model_->user_input_in_progress() && scheme.is_nonempty() &&
    575       (security_level != ToolbarModel::NONE)) {
    576     NSColor* color;
    577     if (security_level == ToolbarModel::EV_SECURE ||
    578         security_level == ToolbarModel::SECURE) {
    579       color = SecureSchemeColor();
    580     } else if (security_level == ToolbarModel::SECURITY_ERROR) {
    581       color = SecurityErrorSchemeColor();
    582       // Add a strikethrough through the scheme.
    583       [as addAttribute:NSStrikethroughStyleAttributeName
    584                  value:[NSNumber numberWithInt:NSUnderlineStyleSingle]
    585                  range:ComponentToNSRange(scheme)];
    586     } else if (security_level == ToolbarModel::SECURITY_WARNING) {
    587       color = BaseTextColor();
    588     } else {
    589       NOTREACHED();
    590       color = BaseTextColor();
    591     }
    592     [as addAttribute:NSForegroundColorAttributeName value:color
    593                range:ComponentToNSRange(scheme)];
    594   }
    595 }
    596 
    597 void AutocompleteEditViewMac::OnTemporaryTextMaybeChanged(
    598     const string16& display_text, bool save_original_selection) {
    599   if (save_original_selection)
    600     saved_temporary_selection_ = GetSelectedRange();
    601 
    602   suggest_text_length_ = 0;
    603   SetWindowTextAndCaretPos(display_text, display_text.size());
    604   model_->OnChanged();
    605   [field_ clearUndoChain];
    606 }
    607 
    608 void AutocompleteEditViewMac::OnStartingIME() {
    609   // Reset the suggest text just before starting an IME composition session,
    610   // otherwise the IME composition may be interrupted when the suggest text
    611   // gets reset by the IME composition change.
    612   SetInstantSuggestion(string16(), false);
    613 }
    614 
    615 bool AutocompleteEditViewMac::OnInlineAutocompleteTextMaybeChanged(
    616     const string16& display_text, size_t user_text_length) {
    617   // TODO(shess): Make sure that this actually works.  The round trip
    618   // to native form and back may mean that it's the same but not the
    619   // same.
    620   if (display_text == GetText())
    621     return false;
    622 
    623   DCHECK_LE(user_text_length, display_text.size());
    624   const NSRange range =
    625       NSMakeRange(user_text_length, display_text.size() - user_text_length);
    626   SetTextAndSelectedRange(display_text, range);
    627   model_->OnChanged();
    628   [field_ clearUndoChain];
    629 
    630   return true;
    631 }
    632 
    633 void AutocompleteEditViewMac::OnRevertTemporaryText() {
    634   SetSelectedRange(saved_temporary_selection_);
    635 }
    636 
    637 bool AutocompleteEditViewMac::IsFirstResponder() const {
    638   return [field_ currentEditor] != nil ? true : false;
    639 }
    640 
    641 void AutocompleteEditViewMac::OnBeforePossibleChange() {
    642   // We should only arrive here when the field is focussed.
    643   DCHECK(IsFirstResponder());
    644 
    645   selection_before_change_ = GetSelectedRange();
    646   text_before_change_ = GetText();
    647   marked_range_before_change_ = GetMarkedRange();
    648 }
    649 
    650 bool AutocompleteEditViewMac::OnAfterPossibleChange() {
    651   // We should only arrive here when the field is focussed.
    652   DCHECK(IsFirstResponder());
    653 
    654   const NSRange new_selection(GetSelectedRange());
    655   const string16 new_text(GetText());
    656   const size_t length = new_text.length();
    657 
    658   const bool selection_differs =
    659       (new_selection.length || selection_before_change_.length) &&
    660       !NSEqualRanges(new_selection, selection_before_change_);
    661   const bool at_end_of_edit = (length == new_selection.location);
    662   const bool text_differs = (new_text != text_before_change_) ||
    663       !NSEqualRanges(marked_range_before_change_, GetMarkedRange());
    664 
    665   // When the user has deleted text, we don't allow inline
    666   // autocomplete.  This is assumed if the text has gotten shorter AND
    667   // the selection has shifted towards the front of the text.  During
    668   // normal typing the text will almost always be shorter (as the new
    669   // input replaces the autocomplete suggestion), but in that case the
    670   // selection point will have moved towards the end of the text.
    671   // TODO(shess): In our implementation, we can catch -deleteBackward:
    672   // and other methods to provide positive knowledge that a delete
    673   // occured, rather than intuiting it from context.  Consider whether
    674   // that would be a stronger approach.
    675   const bool just_deleted_text =
    676       (length < text_before_change_.length() &&
    677        new_selection.location <= selection_before_change_.location);
    678 
    679   delete_at_end_pressed_ = false;
    680 
    681   const bool something_changed = model_->OnAfterPossibleChange(
    682       new_text, new_selection.location, NSMaxRange(new_selection),
    683       selection_differs, text_differs, just_deleted_text,
    684       !IsImeComposing());
    685 
    686   if (delete_was_pressed_ && at_end_of_edit)
    687     delete_at_end_pressed_ = true;
    688 
    689   // Restyle in case the user changed something.
    690   // TODO(shess): I believe there are multiple-redraw cases, here.
    691   // Linux watches for something_changed && text_differs, but that
    692   // fails for us in case you copy the URL and paste the identical URL
    693   // back (we'll lose the styling).
    694   EmphasizeURLComponents();
    695   model_->OnChanged();
    696 
    697   delete_was_pressed_ = false;
    698 
    699   return something_changed;
    700 }
    701 
    702 gfx::NativeView AutocompleteEditViewMac::GetNativeView() const {
    703   return field_;
    704 }
    705 
    706 CommandUpdater* AutocompleteEditViewMac::GetCommandUpdater() {
    707   return command_updater_;
    708 }
    709 
    710 void AutocompleteEditViewMac::SetInstantSuggestion(
    711     const string16& suggest_text,
    712     bool animate_to_complete) {
    713   NSString* text = GetNonSuggestTextSubstring();
    714   bool needs_update = (suggest_text_length_ > 0);
    715 
    716   // Append the new suggest text.
    717   suggest_text_length_ = suggest_text.length();
    718   if (suggest_text_length_ > 0) {
    719     text = [text stringByAppendingString:base::SysUTF16ToNSString(
    720                suggest_text)];
    721     needs_update = true;
    722   }
    723 
    724   if (needs_update) {
    725     NSRange current_range = GetSelectedRange();
    726     SetTextInternal(base::SysNSStringToUTF16(text));
    727     if (NSMaxRange(current_range) <= [text length] - suggest_text_length_)
    728       SetSelectedRange(current_range);
    729     else
    730       SetSelectedRange(NSMakeRange([text length] - suggest_text_length_, 0));
    731   }
    732 }
    733 
    734 string16 AutocompleteEditViewMac::GetInstantSuggestion() const {
    735   return suggest_text_length_ ?
    736       base::SysNSStringToUTF16(GetSuggestTextSubstring()) : string16();
    737 }
    738 
    739 int AutocompleteEditViewMac::TextWidth() const {
    740   // Not used on mac.
    741   NOTREACHED();
    742   return 0;
    743 }
    744 
    745 bool AutocompleteEditViewMac::IsImeComposing() const {
    746   return [(NSTextView*)[field_ currentEditor] hasMarkedText];
    747 }
    748 
    749 void AutocompleteEditViewMac::OnDidBeginEditing() {
    750   // We should only arrive here when the field is focussed.
    751   DCHECK([field_ currentEditor]);
    752 }
    753 
    754 void AutocompleteEditViewMac::OnBeforeChange() {
    755   // Capture the current state.
    756   OnBeforePossibleChange();
    757 }
    758 
    759 void AutocompleteEditViewMac::OnDidChange() {
    760   // Figure out what changed and notify the model_.
    761   OnAfterPossibleChange();
    762 }
    763 
    764 void AutocompleteEditViewMac::OnDidEndEditing() {
    765   ClosePopup();
    766 }
    767 
    768 bool AutocompleteEditViewMac::OnDoCommandBySelector(SEL cmd) {
    769   // We should only arrive here when the field is focussed.
    770   DCHECK(IsFirstResponder());
    771 
    772   if (cmd != @selector(moveRight:) &&
    773       cmd != @selector(insertTab:) &&
    774       cmd != @selector(insertTabIgnoringFieldEditor:)) {
    775     // Reset the suggest text for any change other than key right or tab.
    776     // TODO(rohitrao): This is here to prevent complications when editing text.
    777     // See if this can be removed.
    778     SetInstantSuggestion(string16(), false);
    779   }
    780 
    781   if (cmd == @selector(deleteForward:))
    782     delete_was_pressed_ = true;
    783 
    784   // Don't intercept up/down-arrow if the popup isn't open.
    785   if (popup_view_->IsOpen()) {
    786     if (cmd == @selector(moveDown:)) {
    787       model_->OnUpOrDownKeyPressed(1);
    788       return true;
    789     }
    790 
    791     if (cmd == @selector(moveUp:)) {
    792       model_->OnUpOrDownKeyPressed(-1);
    793       return true;
    794     }
    795   }
    796 
    797   if (cmd == @selector(moveRight:)) {
    798     // Only commit suggested text if the cursor is all the way to the right and
    799     // there is no selection.
    800     if (suggest_text_length_ > 0 && IsCaretAtEnd()) {
    801       model_->CommitSuggestedText(true);
    802       return true;
    803     }
    804   }
    805 
    806   if (cmd == @selector(scrollPageDown:)) {
    807     model_->OnUpOrDownKeyPressed(model_->result().size());
    808     return true;
    809   }
    810 
    811   if (cmd == @selector(scrollPageUp:)) {
    812     model_->OnUpOrDownKeyPressed(-model_->result().size());
    813     return true;
    814   }
    815 
    816   if (cmd == @selector(cancelOperation:)) {
    817     return model_->OnEscapeKeyPressed();
    818   }
    819 
    820   if (cmd == @selector(insertTab:) ||
    821       cmd == @selector(insertTabIgnoringFieldEditor:)) {
    822     if (model_->is_keyword_hint())
    823       return model_->AcceptKeyword();
    824 
    825     if (suggest_text_length_ > 0) {
    826       model_->CommitSuggestedText(true);
    827       return true;
    828     }
    829 
    830     if (!IsCaretAtEnd()) {
    831       PlaceCaretAt(GetTextLength());
    832       // OnDidChange() will not be triggered when setting selected range in this
    833       // method, so we need to call it explicitly.
    834       OnDidChange();
    835       return true;
    836     }
    837 
    838     if (model_->AcceptCurrentInstantPreview())
    839       return true;
    840   }
    841 
    842   // |-noop:| is sent when the user presses Cmd+Return. Override the no-op
    843   // behavior with the proper WindowOpenDisposition.
    844   NSEvent* event = [NSApp currentEvent];
    845   if (cmd == @selector(insertNewline:) ||
    846      (cmd == @selector(noop:) && [event keyCode] == kVK_Return)) {
    847     WindowOpenDisposition disposition =
    848         event_utils::WindowOpenDispositionFromNSEvent(event);
    849     model_->AcceptInput(disposition, false);
    850     // Opening a URL in a background tab should also revert the omnibox contents
    851     // to their original state.  We cannot do a blanket revert in OpenURL()
    852     // because middle-clicks also open in a new background tab, but those should
    853     // not revert the omnibox text.
    854     RevertAll();
    855     return true;
    856   }
    857 
    858   // Option-Return
    859   if (cmd == @selector(insertNewlineIgnoringFieldEditor:)) {
    860     model_->AcceptInput(NEW_FOREGROUND_TAB, false);
    861     return true;
    862   }
    863 
    864   // When the user does Control-Enter, the existing content has "www."
    865   // prepended and ".com" appended.  |model_| should already have
    866   // received notification when the Control key was depressed, but it
    867   // is safe to tell it twice.
    868   if (cmd == @selector(insertLineBreak:)) {
    869     OnControlKeyChanged(true);
    870     WindowOpenDisposition disposition =
    871         event_utils::WindowOpenDispositionFromNSEvent([NSApp currentEvent]);
    872     model_->AcceptInput(disposition, false);
    873     return true;
    874   }
    875 
    876   if (cmd == @selector(deleteBackward:)) {
    877     if (OnBackspacePressed()) {
    878       return true;
    879     }
    880   }
    881 
    882   if (cmd == @selector(deleteForward:)) {
    883     const NSUInteger modifiers = [[NSApp currentEvent] modifierFlags];
    884     if ((modifiers & NSShiftKeyMask) != 0) {
    885       if (model_->popup_model()->IsOpen()) {
    886         model_->popup_model()->TryDeletingCurrentItem();
    887         return true;
    888       }
    889     }
    890   }
    891 
    892   return false;
    893 }
    894 
    895 void AutocompleteEditViewMac::OnSetFocus(bool control_down) {
    896   model_->OnSetFocus(control_down);
    897   controller_->OnSetFocus();
    898 }
    899 
    900 void AutocompleteEditViewMac::OnKillFocus() {
    901   // Tell the model to reset itself.
    902   model_->OnWillKillFocus(NULL);
    903   model_->OnKillFocus();
    904   controller_->OnKillFocus();
    905 }
    906 
    907 bool AutocompleteEditViewMac::CanCopy() {
    908   const NSRange selection = GetSelectedRange();
    909   return selection.length > 0;
    910 }
    911 
    912 void AutocompleteEditViewMac::CopyToPasteboard(NSPasteboard* pb) {
    913   DCHECK(CanCopy());
    914 
    915   const NSRange selection = GetSelectedRange();
    916   string16 text = base::SysNSStringToUTF16(
    917       [[field_ stringValue] substringWithRange:selection]);
    918 
    919   GURL url;
    920   bool write_url = false;
    921   model_->AdjustTextForCopy(selection.location, IsSelectAll(), &text, &url,
    922                             &write_url);
    923 
    924   NSString* nstext = base::SysUTF16ToNSString(text);
    925   [pb declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
    926   [pb setString:nstext forType:NSStringPboardType];
    927 
    928   if (write_url) {
    929     [pb declareURLPasteboardWithAdditionalTypes:[NSArray array] owner:nil];
    930     [pb setDataForURL:base::SysUTF8ToNSString(url.spec()) title:nstext];
    931   }
    932 }
    933 
    934 void AutocompleteEditViewMac::OnPaste() {
    935   // This code currently expects |field_| to be focussed.
    936   DCHECK([field_ currentEditor]);
    937 
    938   string16 text = GetClipboardText(g_browser_process->clipboard());
    939   if (text.empty()) {
    940     return;
    941   }
    942   NSString* s = base::SysUTF16ToNSString(text);
    943 
    944   // -shouldChangeTextInRange:* and -didChangeText are documented in
    945   // NSTextView as things you need to do if you write additional
    946   // user-initiated editing functions.  They cause the appropriate
    947   // delegate methods to be called.
    948   // TODO(shess): It would be nice to separate the Cocoa-specific code
    949   // from the Chrome-specific code.
    950   NSTextView* editor = static_cast<NSTextView*>([field_ currentEditor]);
    951   const NSRange selectedRange = GetSelectedRange();
    952   if ([editor shouldChangeTextInRange:selectedRange replacementString:s]) {
    953     // Record this paste, so we can do different behavior.
    954     model_->on_paste();
    955 
    956     // Force a Paste operation to trigger the text_changed code in
    957     // OnAfterPossibleChange(), even if identical contents are pasted
    958     // into the text box.
    959     text_before_change_.clear();
    960 
    961     [editor replaceCharactersInRange:selectedRange withString:s];
    962     [editor didChangeText];
    963   }
    964 }
    965 
    966 bool AutocompleteEditViewMac::CanPasteAndGo() {
    967   return
    968     model_->CanPasteAndGo(GetClipboardText(g_browser_process->clipboard()));
    969 }
    970 
    971 int AutocompleteEditViewMac::GetPasteActionStringId() {
    972   DCHECK(CanPasteAndGo());
    973 
    974   // Use PASTE_AND_SEARCH as the default fallback (although the DCHECK above
    975   // should never trigger).
    976   if (!model_->is_paste_and_search())
    977     return IDS_PASTE_AND_GO;
    978   else
    979     return IDS_PASTE_AND_SEARCH;
    980 }
    981 
    982 void AutocompleteEditViewMac::OnPasteAndGo() {
    983   if (CanPasteAndGo())
    984     model_->PasteAndGo();
    985 }
    986 
    987 void AutocompleteEditViewMac::OnFrameChanged() {
    988   // TODO(shess): UpdatePopupAppearance() is called frequently, so it
    989   // should be really cheap, but in this case we could probably make
    990   // things even cheaper by refactoring between the popup-placement
    991   // code and the matrix-population code.
    992   popup_view_->UpdatePopupAppearance();
    993   model_->PopupBoundsChangedTo(popup_view_->GetTargetBounds());
    994 
    995   // Give controller a chance to rearrange decorations.
    996   model_->OnChanged();
    997 }
    998 
    999 bool AutocompleteEditViewMac::OnBackspacePressed() {
   1000   // Don't intercept if not in keyword search mode.
   1001   if (model_->is_keyword_hint() || model_->keyword().empty()) {
   1002     return false;
   1003   }
   1004 
   1005   // Don't intercept if there is a selection, or the cursor isn't at
   1006   // the leftmost position.
   1007   const NSRange selection = GetSelectedRange();
   1008   if (selection.length > 0 || selection.location > 0) {
   1009     return false;
   1010   }
   1011 
   1012   // We're showing a keyword and the user pressed backspace at the
   1013   // beginning of the text.  Delete the selected keyword.
   1014   model_->ClearKeyword(GetText());
   1015   return true;
   1016 }
   1017 
   1018 NSRange AutocompleteEditViewMac::SelectionRangeForProposedRange(
   1019     NSRange proposed_range) {
   1020   // Should never call this function unless editing is in progress.
   1021   DCHECK([field_ currentEditor]);
   1022 
   1023   if (![field_ currentEditor])
   1024     return proposed_range;
   1025 
   1026   // Do not use [field_ stringValue] here, as that forces a sync between the
   1027   // field and the editor.  This sync will end up setting the selection, which
   1028   // in turn calls this method, leading to an infinite loop.  Instead, retrieve
   1029   // the current string value directly from the editor.
   1030   size_t text_length = [[[field_ currentEditor] string] length];
   1031 
   1032   // Cannot select suggested text.
   1033   size_t max = text_length - suggest_text_length_;
   1034   NSUInteger start = proposed_range.location;
   1035   NSUInteger end = proposed_range.location + proposed_range.length;
   1036 
   1037   if (start > max)
   1038     start = max;
   1039 
   1040   if (end > max)
   1041     end = max;
   1042 
   1043   return NSMakeRange(start, end - start);
   1044 }
   1045 
   1046 void AutocompleteEditViewMac::OnControlKeyChanged(bool pressed) {
   1047   model_->OnControlKeyChanged(pressed);
   1048 }
   1049 
   1050 void AutocompleteEditViewMac::FocusLocation(bool select_all) {
   1051   if ([field_ isEditable]) {
   1052     // If the text field has a field editor, it's the first responder, meaning
   1053     // that it's already focused. makeFirstResponder: will select all, so only
   1054     // call it if this behavior is desired.
   1055     if (select_all || ![field_ currentEditor])
   1056       [[field_ window] makeFirstResponder:field_];
   1057     DCHECK_EQ([field_ currentEditor], [[field_ window] firstResponder]);
   1058   }
   1059 }
   1060 
   1061 // TODO(shess): Copied from autocomplete_edit_view_win.cc.  Could this
   1062 // be pushed into the model?
   1063 string16 AutocompleteEditViewMac::GetClipboardText(
   1064     ui::Clipboard* clipboard) {
   1065   // autocomplete_edit_view_win.cc assumes this can never happen, we
   1066   // will too.
   1067   DCHECK(clipboard);
   1068 
   1069   if (clipboard->IsFormatAvailable(ui::Clipboard::GetPlainTextWFormatType(),
   1070                                    ui::Clipboard::BUFFER_STANDARD)) {
   1071     string16 text16;
   1072     clipboard->ReadText(ui::Clipboard::BUFFER_STANDARD, &text16);
   1073 
   1074     // Note: Unlike in the find popup and textfield view, here we completely
   1075     // remove whitespace strings containing newlines.  We assume users are
   1076     // most likely pasting in URLs that may have been split into multiple
   1077     // lines in terminals, email programs, etc., and so linebreaks indicate
   1078     // completely bogus whitespace that would just cause the input to be
   1079     // invalid.
   1080     return CollapseWhitespace(text16, true);
   1081   }
   1082 
   1083   // Try bookmark format.
   1084   //
   1085   // It is tempting to try bookmark format first, but the URL we get out of a
   1086   // bookmark has been cannonicalized via GURL.  This means if a user copies
   1087   // and pastes from the URL bar to itself, the text will get fixed up and
   1088   // cannonicalized, which is not what the user expects.  By pasting in this
   1089   // order, we are sure to paste what the user copied.
   1090   if (clipboard->IsFormatAvailable(ui::Clipboard::GetUrlWFormatType(),
   1091                                    ui::Clipboard::BUFFER_STANDARD)) {
   1092     std::string url_str;
   1093     clipboard->ReadBookmark(NULL, &url_str);
   1094     // pass resulting url string through GURL to normalize
   1095     GURL url(url_str);
   1096     if (url.is_valid()) {
   1097       return UTF8ToUTF16(url.spec());
   1098     }
   1099   }
   1100 
   1101   return string16();
   1102 }
   1103 
   1104 // static
   1105 NSFont* AutocompleteEditViewMac::GetFieldFont() {
   1106   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
   1107   return rb.GetFont(ResourceBundle::BaseFont).GetNativeFont();
   1108 }
   1109 
   1110 NSUInteger AutocompleteEditViewMac::GetTextLength() const {
   1111   return ([field_ currentEditor] ?
   1112           [[[field_ currentEditor] string] length] :
   1113           [[field_ stringValue] length]) - suggest_text_length_;
   1114 }
   1115 
   1116 void AutocompleteEditViewMac::PlaceCaretAt(NSUInteger pos) {
   1117   DCHECK(pos <= GetTextLength());
   1118   SetSelectedRange(NSMakeRange(pos, pos));
   1119 }
   1120 
   1121 bool AutocompleteEditViewMac::IsCaretAtEnd() const {
   1122   const NSRange selection = GetSelectedRange();
   1123   return selection.length == 0 && selection.location == GetTextLength();
   1124 }
   1125