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