Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / omnibox / omnibox_view_mac.mm
blob95bacdd45de4ba73ed3315ff9cd0c2efef2eb9f5
1 // Copyright (c) 2012 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/ui/cocoa/omnibox/omnibox_view_mac.h"
7 #include <Carbon/Carbon.h>  // kVK_Return
9 #include "base/mac/foundation_util.h"
10 #include "base/metrics/histogram.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/sys_string_conversions.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "chrome/browser/autocomplete/autocomplete_input.h"
15 #include "chrome/browser/autocomplete/autocomplete_match.h"
16 #include "chrome/browser/browser_process.h"
17 #include "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_cell.h"
18 #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_editor.h"
19 #include "chrome/browser/ui/cocoa/omnibox/omnibox_popup_view_mac.h"
20 #include "chrome/browser/ui/omnibox/omnibox_edit_controller.h"
21 #include "chrome/browser/ui/omnibox/omnibox_popup_model.h"
22 #include "chrome/browser/ui/toolbar/toolbar_model.h"
23 #include "content/public/browser/web_contents.h"
24 #include "extensions/common/constants.h"
25 #include "grit/generated_resources.h"
26 #include "grit/theme_resources.h"
27 #import "third_party/mozilla/NSPasteboard+Utils.h"
28 #include "ui/base/clipboard/clipboard.h"
29 #import "ui/base/cocoa/cocoa_event_utils.h"
30 #include "ui/base/resource/resource_bundle.h"
31 #include "ui/gfx/rect.h"
33 using content::WebContents;
35 // Focus-handling between |field_| and model() is a bit subtle.
36 // Other platforms detect change of focus, which is inconvenient
37 // without subclassing NSTextField (even with a subclass, the use of a
38 // field editor may complicate things).
40 // model() doesn't actually do anything when it gains focus, it just
41 // initializes.  Visible activity happens only after the user edits.
42 // NSTextField delegate receives messages around starting and ending
43 // edits, so that suffices to catch focus changes.  Since all calls
44 // into model() start from OmniboxViewMac, in the worst case
45 // we can add code to sync up the sense of focus as needed.
47 // I've added DCHECK(IsFirstResponder()) in the places which I believe
48 // should only be reachable when |field_| is being edited.  If these
49 // fire, it probably means someone unexpected is calling into
50 // model().
52 // Other platforms don't appear to have the sense of "key window" that
53 // Mac does (I believe their fields lose focus when the window loses
54 // focus).  Rather than modifying focus outside the control's edit
55 // scope, when the window resigns key the autocomplete popup is
56 // closed.  model() still believes it has focus, and the popup will
57 // be regenerated on the user's next edit.  That seems to match how
58 // things work on other platforms.
60 namespace {
62 // TODO(shess): This is ugly, find a better way.  Using it right now
63 // so that I can crib from gtk and still be able to see that I'm using
64 // the same values easily.
65 NSColor* ColorWithRGBBytes(int rr, int gg, int bb) {
66   DCHECK_LE(rr, 255);
67   DCHECK_LE(bb, 255);
68   DCHECK_LE(gg, 255);
69   return [NSColor colorWithCalibratedRed:static_cast<float>(rr)/255.0
70                                    green:static_cast<float>(gg)/255.0
71                                     blue:static_cast<float>(bb)/255.0
72                                    alpha:1.0];
75 NSColor* HostTextColor() {
76   return [NSColor blackColor];
78 NSColor* BaseTextColor() {
79   return [NSColor darkGrayColor];
81 NSColor* SecureSchemeColor() {
82   return ColorWithRGBBytes(0x07, 0x95, 0x00);
84 NSColor* SecurityErrorSchemeColor() {
85   return ColorWithRGBBytes(0xa2, 0x00, 0x00);
88 const char kOmniboxViewMacStateKey[] = "OmniboxViewMacState";
90 // Store's the model and view state across tab switches.
91 struct OmniboxViewMacState : public base::SupportsUserData::Data {
92   OmniboxViewMacState(const OmniboxEditModel::State model_state,
93                       const bool has_focus,
94                       const NSRange& selection)
95       : model_state(model_state),
96         has_focus(has_focus),
97         selection(selection) {
98   }
99   virtual ~OmniboxViewMacState() {}
101   const OmniboxEditModel::State model_state;
102   const bool has_focus;
103   const NSRange selection;
106 // Accessors for storing and getting the state from the tab.
107 void StoreStateToTab(WebContents* tab,
108                      OmniboxViewMacState* state) {
109   tab->SetUserData(kOmniboxViewMacStateKey, state);
111 const OmniboxViewMacState* GetStateFromTab(const WebContents* tab) {
112   return static_cast<OmniboxViewMacState*>(
113       tab->GetUserData(&kOmniboxViewMacStateKey));
116 // Helper to make converting url_parse ranges to NSRange easier to
117 // read.
118 NSRange ComponentToNSRange(const url_parse::Component& component) {
119   return NSMakeRange(static_cast<NSInteger>(component.begin),
120                      static_cast<NSInteger>(component.len));
123 }  // namespace
125 // static
126 NSImage* OmniboxViewMac::ImageForResource(int resource_id) {
127   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
128   return rb.GetNativeImageNamed(resource_id).ToNSImage();
131 // static
132 NSColor* OmniboxViewMac::SuggestTextColor() {
133   return [NSColor colorWithCalibratedWhite:0.0 alpha:0.5];
136 OmniboxViewMac::OmniboxViewMac(OmniboxEditController* controller,
137                                Profile* profile,
138                                CommandUpdater* command_updater,
139                                AutocompleteTextField* field)
140     : OmniboxView(profile, controller, command_updater),
141       popup_view_(new OmniboxPopupViewMac(this, model(), field)),
142       field_(field),
143       saved_temporary_selection_(NSMakeRange(0, 0)),
144       selection_before_change_(NSMakeRange(0, 0)),
145       marked_range_before_change_(NSMakeRange(0, 0)),
146       delete_was_pressed_(false),
147       delete_at_end_pressed_(false) {
148   [field_ setObserver:this];
150   // Needed so that editing doesn't lose the styling.
151   [field_ setAllowsEditingTextAttributes:YES];
153   // Get the appropriate line height for the font that we use.
154   base::scoped_nsobject<NSLayoutManager> layoutManager(
155       [[NSLayoutManager alloc] init]);
156   [layoutManager setUsesScreenFonts:YES];
159 OmniboxViewMac::~OmniboxViewMac() {
160   // Destroy popup view before this object in case it tries to call us
161   // back in the destructor.
162   popup_view_.reset();
164   // Disconnect from |field_|, it outlives this object.
165   [field_ setObserver:NULL];
168 void OmniboxViewMac::SaveStateToTab(WebContents* tab) {
169   DCHECK(tab);
171   const bool hasFocus = [field_ currentEditor] ? true : false;
173   NSRange range;
174   if (hasFocus) {
175     range = GetSelectedRange();
176   } else {
177     // If we are not focussed, there is no selection.  Manufacture
178     // something reasonable in case it starts to matter in the future.
179     range = NSMakeRange(0, GetTextLength());
180   }
182   OmniboxViewMacState* state =
183       new OmniboxViewMacState(model()->GetStateForTabSwitch(), hasFocus, range);
184   StoreStateToTab(tab, state);
187 void OmniboxViewMac::OnTabChanged(const WebContents* web_contents) {
188   const OmniboxViewMacState* state = GetStateFromTab(web_contents);
189   model()->RestoreState(state ? &state->model_state : NULL);
190   // Restore focus and selection if they were present when the tab
191   // was switched away.
192   if (state && state->has_focus) {
193     // TODO(shess): Unfortunately, there is no safe way to update
194     // this because TabStripController -selectTabWithContents:* is
195     // also messing with focus.  Both parties need to agree to
196     // store existing state before anyone tries to setup the new
197     // state.  Anyhow, it would look something like this.
198 #if 0
199     [[field_ window] makeFirstResponder:field_];
200     [[field_ currentEditor] setSelectedRange:state->selection];
201 #endif
202   }
205 void OmniboxViewMac::Update() {
206   if (model()->UpdatePermanentText()) {
207     // Something visibly changed.  Re-enable URL replacement.
208     controller()->GetToolbarModel()->set_url_replacement_enabled(true);
209     model()->UpdatePermanentText();
211     // Restore everything to the baseline look.
212     RevertAll();
214     // TODO(shess): Figure out how this case is used, to make sure
215     // we're getting the selection and popup right.
216   } else {
217     // TODO(shess): This corresponds to _win and _gtk, except those
218     // guard it with a test for whether the security level changed.
219     // But AFAICT, that can only change if the text changed, and that
220     // code compares the toolbar model security level with the local
221     // security level.  Dig in and figure out why this isn't a no-op
222     // that should go away.
223     EmphasizeURLComponents();
224   }
227 base::string16 OmniboxViewMac::GetText() const {
228   return base::SysNSStringToUTF16([field_ stringValue]);
231 NSRange OmniboxViewMac::GetSelectedRange() const {
232   return [[field_ currentEditor] selectedRange];
235 NSRange OmniboxViewMac::GetMarkedRange() const {
236   DCHECK([field_ currentEditor]);
237   return [(NSTextView*)[field_ currentEditor] markedRange];
240 void OmniboxViewMac::SetSelectedRange(const NSRange range) {
241   // This can be called when we don't have focus.  For instance, when
242   // the user clicks the "Go" button.
243   if (model()->has_focus()) {
244     // TODO(shess): If model() thinks we have focus, this should not
245     // be necessary.  Try to convert to DCHECK(IsFirstResponder()).
246     if (![field_ currentEditor]) {
247       [[field_ window] makeFirstResponder:field_];
248     }
250     // TODO(shess): What if it didn't get first responder, and there is
251     // no field editor?  This will do nothing.  Well, at least it won't
252     // crash.  Think of something more productive to do, or prove that
253     // it cannot occur and DCHECK appropriately.
254     [[field_ currentEditor] setSelectedRange:range];
255   }
258 void OmniboxViewMac::SetWindowTextAndCaretPos(const base::string16& text,
259                                               size_t caret_pos,
260                                               bool update_popup,
261                                               bool notify_text_changed) {
262   DCHECK_LE(caret_pos, text.size());
263   SetTextAndSelectedRange(text, NSMakeRange(caret_pos, 0));
265   if (update_popup)
266     UpdatePopup();
268   if (notify_text_changed)
269     TextChanged();
272 void OmniboxViewMac::SetForcedQuery() {
273   // We need to do this first, else |SetSelectedRange()| won't work.
274   FocusLocation(true);
276   const base::string16 current_text(GetText());
277   const size_t start = current_text.find_first_not_of(base::kWhitespaceUTF16);
278   if (start == base::string16::npos || (current_text[start] != '?')) {
279     SetUserText(base::ASCIIToUTF16("?"));
280   } else {
281     NSRange range = NSMakeRange(start + 1, current_text.size() - start - 1);
282     [[field_ currentEditor] setSelectedRange:range];
283   }
286 bool OmniboxViewMac::IsSelectAll() const {
287   if (![field_ currentEditor])
288     return true;
289   const NSRange all_range = NSMakeRange(0, GetTextLength());
290   return NSEqualRanges(all_range, GetSelectedRange());
293 bool OmniboxViewMac::DeleteAtEndPressed() {
294   return delete_at_end_pressed_;
297 void OmniboxViewMac::GetSelectionBounds(base::string16::size_type* start,
298                                         base::string16::size_type* end) const {
299   if (![field_ currentEditor]) {
300     *start = *end = 0;
301     return;
302   }
304   const NSRange selected_range = GetSelectedRange();
305   *start = static_cast<size_t>(selected_range.location);
306   *end = static_cast<size_t>(NSMaxRange(selected_range));
309 void OmniboxViewMac::SelectAll(bool reversed) {
310   // TODO(shess): Figure out what |reversed| implies.  The gtk version
311   // has it imply inverting the selection front to back, but I don't
312   // even know if that makes sense for Mac.
314   // TODO(shess): Verify that we should be stealing focus at this
315   // point.
316   SetSelectedRange(NSMakeRange(0, GetTextLength()));
319 void OmniboxViewMac::RevertAll() {
320   OmniboxView::RevertAll();
321   [field_ clearUndoChain];
324 void OmniboxViewMac::UpdatePopup() {
325   model()->SetInputInProgress(true);
326   if (!model()->has_focus())
327     return;
329   // Comment copied from OmniboxViewWin::UpdatePopup():
330   // Don't inline autocomplete when:
331   //   * The user is deleting text
332   //   * The caret/selection isn't at the end of the text
333   //   * The user has just pasted in something that replaced all the text
334   //   * The user is trying to compose something in an IME
335   bool prevent_inline_autocomplete = IsImeComposing();
336   NSTextView* editor = (NSTextView*)[field_ currentEditor];
337   if (editor) {
338     if (NSMaxRange([editor selectedRange]) < [[editor textStorage] length])
339       prevent_inline_autocomplete = true;
340   }
342   model()->StartAutocomplete([editor selectedRange].length != 0,
343                             prevent_inline_autocomplete);
346 void OmniboxViewMac::CloseOmniboxPopup() {
347   // Call both base class methods.
348   ClosePopup();
349   OmniboxView::CloseOmniboxPopup();
352 void OmniboxViewMac::SetFocus() {
353   FocusLocation(false);
354   model()->SetCaretVisibility(true);
357 void OmniboxViewMac::ApplyCaretVisibility() {
358   [[field_ cell] setHideFocusState:!model()->is_caret_visible()
359                             ofView:field_];
362 void OmniboxViewMac::SetText(const base::string16& display_text) {
363   SetTextInternal(display_text);
366 void OmniboxViewMac::SetTextInternal(const base::string16& display_text) {
367   NSString* ss = base::SysUTF16ToNSString(display_text);
368   NSMutableAttributedString* as =
369       [[[NSMutableAttributedString alloc] initWithString:ss] autorelease];
371   ApplyTextAttributes(display_text, as);
372   [field_ setAttributedStringValue:as];
374   // TODO(shess): This may be an appropriate place to call:
375   //   model()->OnChanged();
376   // In the current implementation, this tells LocationBarViewMac to
377   // mess around with model() and update |field_|.  Unfortunately,
378   // when I look at our peer implementations, it's not entirely clear
379   // to me if this is safe.  SetTextInternal() is sort of an utility method,
380   // and different callers sometimes have different needs.  Research
381   // this issue so that it can be added safely.
383   // TODO(shess): Also, consider whether this code couldn't just
384   // manage things directly.  Windows uses a series of overlaid view
385   // objects to accomplish the hinting stuff that OnChanged() does, so
386   // it makes sense to have it in the controller that lays those
387   // things out.  Mac instead pushes the support into a custom
388   // text-field implementation.
391 void OmniboxViewMac::SetTextAndSelectedRange(const base::string16& display_text,
392                                              const NSRange range) {
393   SetText(display_text);
394   SetSelectedRange(range);
397 void OmniboxViewMac::EmphasizeURLComponents() {
398   NSTextView* editor = (NSTextView*)[field_ currentEditor];
399   // If the autocomplete text field is in editing mode, then we can just change
400   // its attributes through its editor. Otherwise, we simply reset its content.
401   if (editor) {
402     NSTextStorage* storage = [editor textStorage];
403     [storage beginEditing];
405     // Clear the existing attributes from the text storage, then
406     // overlay the appropriate Omnibox attributes.
407     [storage setAttributes:[NSDictionary dictionary]
408                      range:NSMakeRange(0, [storage length])];
409     ApplyTextAttributes(GetText(), storage);
411     [storage endEditing];
412   } else {
413     SetText(GetText());
414   }
417 void OmniboxViewMac::ApplyTextAttributes(const base::string16& display_text,
418                                          NSMutableAttributedString* as) {
419   NSUInteger as_length = [as length];
420   NSRange as_entire_string = NSMakeRange(0, as_length);
422   [as addAttribute:NSFontAttributeName value:GetFieldFont()
423              range:as_entire_string];
425   // A kinda hacky way to add breaking at periods. This is what Safari does.
426   // This works for IDNs too, despite the "en_US".
427   [as addAttribute:@"NSLanguage" value:@"en_US_POSIX"
428              range:as_entire_string];
430   // Make a paragraph style locking in the standard line height as the maximum,
431   // otherwise the baseline may shift "downwards".
432   base::scoped_nsobject<NSMutableParagraphStyle> paragraph_style(
433       [[NSMutableParagraphStyle alloc] init]);
434   CGFloat line_height = [[field_ cell] lineHeight];
435   [paragraph_style setMaximumLineHeight:line_height];
436   [paragraph_style setMinimumLineHeight:line_height];
437   [paragraph_style setLineBreakMode:NSLineBreakByTruncatingTail];
438   [as addAttribute:NSParagraphStyleAttributeName value:paragraph_style
439              range:as_entire_string];
441   url_parse::Component scheme, host;
442   AutocompleteInput::ParseForEmphasizeComponents(
443       display_text, &scheme, &host);
444   bool grey_out_url = display_text.substr(scheme.begin, scheme.len) ==
445       base::UTF8ToUTF16(extensions::kExtensionScheme);
446   if (model()->CurrentTextIsURL() &&
447       (host.is_nonempty() || grey_out_url)) {
448     [as addAttribute:NSForegroundColorAttributeName value:BaseTextColor()
449                range:as_entire_string];
451     if (!grey_out_url) {
452       [as addAttribute:NSForegroundColorAttributeName value:HostTextColor()
453                range:ComponentToNSRange(host)];
454     }
455   }
457   // TODO(shess): GTK has this as a member var, figure out why.
458   // [Could it be to not change if no change?  If so, I'm guessing
459   // AppKit may already handle that.]
460   const ToolbarModel::SecurityLevel security_level =
461       controller()->GetToolbarModel()->GetSecurityLevel(false);
463   // Emphasize the scheme for security UI display purposes (if necessary).
464   if (!model()->user_input_in_progress() && model()->CurrentTextIsURL() &&
465       scheme.is_nonempty() && (security_level != ToolbarModel::NONE)) {
466     NSColor* color;
467     if (security_level == ToolbarModel::EV_SECURE ||
468         security_level == ToolbarModel::SECURE) {
469       color = SecureSchemeColor();
470     } else if (security_level == ToolbarModel::SECURITY_ERROR) {
471       color = SecurityErrorSchemeColor();
472       // Add a strikethrough through the scheme.
473       [as addAttribute:NSStrikethroughStyleAttributeName
474                  value:[NSNumber numberWithInt:NSUnderlineStyleSingle]
475                  range:ComponentToNSRange(scheme)];
476     } else if (security_level == ToolbarModel::SECURITY_WARNING) {
477       color = BaseTextColor();
478     } else {
479       NOTREACHED();
480       color = BaseTextColor();
481     }
482     [as addAttribute:NSForegroundColorAttributeName value:color
483                range:ComponentToNSRange(scheme)];
484   }
487 void OmniboxViewMac::OnTemporaryTextMaybeChanged(
488     const base::string16& display_text,
489     bool save_original_selection,
490     bool notify_text_changed) {
491   if (save_original_selection)
492     saved_temporary_selection_ = GetSelectedRange();
494   SetWindowTextAndCaretPos(display_text, display_text.size(), false, false);
495   if (notify_text_changed)
496     model()->OnChanged();
497   [field_ clearUndoChain];
500 bool OmniboxViewMac::OnInlineAutocompleteTextMaybeChanged(
501     const base::string16& display_text,
502     size_t user_text_length) {
503   // TODO(shess): Make sure that this actually works.  The round trip
504   // to native form and back may mean that it's the same but not the
505   // same.
506   if (display_text == GetText())
507     return false;
509   DCHECK_LE(user_text_length, display_text.size());
510   const NSRange range =
511       NSMakeRange(user_text_length, display_text.size() - user_text_length);
512   SetTextAndSelectedRange(display_text, range);
513   model()->OnChanged();
514   [field_ clearUndoChain];
516   return true;
519 void OmniboxViewMac::OnInlineAutocompleteTextCleared() {
522 void OmniboxViewMac::OnRevertTemporaryText() {
523   SetSelectedRange(saved_temporary_selection_);
524   // We got here because the user hit the Escape key. We explicitly don't call
525   // TextChanged(), since OmniboxPopupModel::ResetToDefaultMatch() has already
526   // been called by now, and it would've called TextChanged() if it was
527   // warranted.
530 bool OmniboxViewMac::IsFirstResponder() const {
531   return [field_ currentEditor] != nil ? true : false;
534 void OmniboxViewMac::OnBeforePossibleChange() {
535   // We should only arrive here when the field is focussed.
536   DCHECK(IsFirstResponder());
538   selection_before_change_ = GetSelectedRange();
539   text_before_change_ = GetText();
540   marked_range_before_change_ = GetMarkedRange();
543 bool OmniboxViewMac::OnAfterPossibleChange() {
544   // We should only arrive here when the field is focussed.
545   DCHECK(IsFirstResponder());
547   const NSRange new_selection(GetSelectedRange());
548   const base::string16 new_text(GetText());
549   const size_t length = new_text.length();
551   const bool selection_differs =
552       (new_selection.length || selection_before_change_.length) &&
553       !NSEqualRanges(new_selection, selection_before_change_);
554   const bool at_end_of_edit = (length == new_selection.location);
555   const bool text_differs = (new_text != text_before_change_) ||
556       !NSEqualRanges(marked_range_before_change_, GetMarkedRange());
558   // When the user has deleted text, we don't allow inline
559   // autocomplete.  This is assumed if the text has gotten shorter AND
560   // the selection has shifted towards the front of the text.  During
561   // normal typing the text will almost always be shorter (as the new
562   // input replaces the autocomplete suggestion), but in that case the
563   // selection point will have moved towards the end of the text.
564   // TODO(shess): In our implementation, we can catch -deleteBackward:
565   // and other methods to provide positive knowledge that a delete
566   // occured, rather than intuiting it from context.  Consider whether
567   // that would be a stronger approach.
568   const bool just_deleted_text =
569       (length < text_before_change_.length() &&
570        new_selection.location <= selection_before_change_.location);
572   delete_at_end_pressed_ = false;
574   const bool something_changed = model()->OnAfterPossibleChange(
575       text_before_change_, new_text, new_selection.location,
576       NSMaxRange(new_selection), selection_differs, text_differs,
577       just_deleted_text, !IsImeComposing());
579   if (delete_was_pressed_ && at_end_of_edit)
580     delete_at_end_pressed_ = true;
582   // Restyle in case the user changed something.
583   // TODO(shess): I believe there are multiple-redraw cases, here.
584   // Linux watches for something_changed && text_differs, but that
585   // fails for us in case you copy the URL and paste the identical URL
586   // back (we'll lose the styling).
587   TextChanged();
589   delete_was_pressed_ = false;
591   return something_changed;
594 gfx::NativeView OmniboxViewMac::GetNativeView() const {
595   return field_;
598 gfx::NativeView OmniboxViewMac::GetRelativeWindowForPopup() const {
599   // Not used on mac.
600   NOTREACHED();
601   return NULL;
604 void OmniboxViewMac::SetGrayTextAutocompletion(
605     const base::string16& suggest_text) {
606   if (suggest_text == suggest_text_)
607     return;
608   suggest_text_ = suggest_text;
609   [field_ setGrayTextAutocompletion:base::SysUTF16ToNSString(suggest_text)
610                           textColor:SuggestTextColor()];
613 base::string16 OmniboxViewMac::GetGrayTextAutocompletion() const {
614   return suggest_text_;
617 int OmniboxViewMac::GetTextWidth() const {
618   // Not used on mac.
619   NOTREACHED();
620   return 0;
623 int OmniboxViewMac::GetWidth() const {
624   return ceil([field_ bounds].size.width);
627 bool OmniboxViewMac::IsImeComposing() const {
628   return [(NSTextView*)[field_ currentEditor] hasMarkedText];
631 void OmniboxViewMac::OnDidBeginEditing() {
632   // We should only arrive here when the field is focussed.
633   DCHECK([field_ currentEditor]);
636 void OmniboxViewMac::OnBeforeChange() {
637   // Capture the current state.
638   OnBeforePossibleChange();
641 void OmniboxViewMac::OnDidChange() {
642   // Figure out what changed and notify the model.
643   OnAfterPossibleChange();
646 void OmniboxViewMac::OnDidEndEditing() {
647   ClosePopup();
650 bool OmniboxViewMac::OnDoCommandBySelector(SEL cmd) {
651   if (cmd == @selector(deleteForward:))
652     delete_was_pressed_ = true;
654   if (cmd == @selector(moveDown:)) {
655     model()->OnUpOrDownKeyPressed(1);
656     return true;
657   }
659   if (cmd == @selector(moveUp:)) {
660     model()->OnUpOrDownKeyPressed(-1);
661     return true;
662   }
664   if (model()->popup_model()->IsOpen()) {
665     if (cmd == @selector(insertBacktab:)) {
666       if (model()->popup_model()->selected_line_state() ==
667             OmniboxPopupModel::KEYWORD) {
668         model()->ClearKeyword(GetText());
669         return true;
670       } else {
671         model()->OnUpOrDownKeyPressed(-1);
672         return true;
673       }
674     }
676     if ((cmd == @selector(insertTab:) ||
677         cmd == @selector(insertTabIgnoringFieldEditor:)) &&
678         !model()->is_keyword_hint()) {
679       model()->OnUpOrDownKeyPressed(1);
680       return true;
681     }
682   }
684   if (cmd == @selector(moveRight:)) {
685     // Only commit suggested text if the cursor is all the way to the right and
686     // there is no selection.
687     if (suggest_text_.length() > 0 && IsCaretAtEnd()) {
688       model()->CommitSuggestedText();
689       return true;
690     }
691   }
693   if (cmd == @selector(scrollPageDown:)) {
694     model()->OnUpOrDownKeyPressed(model()->result().size());
695     return true;
696   }
698   if (cmd == @selector(scrollPageUp:)) {
699     model()->OnUpOrDownKeyPressed(-model()->result().size());
700     return true;
701   }
703   if (cmd == @selector(cancelOperation:)) {
704     return model()->OnEscapeKeyPressed();
705   }
707   if ((cmd == @selector(insertTab:) ||
708       cmd == @selector(insertTabIgnoringFieldEditor:)) &&
709       model()->is_keyword_hint()) {
710     return model()->AcceptKeyword(ENTERED_KEYWORD_MODE_VIA_TAB);
711   }
713   // |-noop:| is sent when the user presses Cmd+Return. Override the no-op
714   // behavior with the proper WindowOpenDisposition.
715   NSEvent* event = [NSApp currentEvent];
716   if (cmd == @selector(insertNewline:) ||
717      (cmd == @selector(noop:) &&
718       ([event type] == NSKeyDown || [event type] == NSKeyUp) &&
719       [event keyCode] == kVK_Return)) {
720     WindowOpenDisposition disposition =
721         ui::WindowOpenDispositionFromNSEvent(event);
722     model()->AcceptInput(disposition, false);
723     // Opening a URL in a background tab should also revert the omnibox contents
724     // to their original state.  We cannot do a blanket revert in OpenURL()
725     // because middle-clicks also open in a new background tab, but those should
726     // not revert the omnibox text.
727     RevertAll();
728     return true;
729   }
731   // Option-Return
732   if (cmd == @selector(insertNewlineIgnoringFieldEditor:)) {
733     model()->AcceptInput(NEW_FOREGROUND_TAB, false);
734     return true;
735   }
737   // When the user does Control-Enter, the existing content has "www."
738   // prepended and ".com" appended.  model() should already have
739   // received notification when the Control key was depressed, but it
740   // is safe to tell it twice.
741   if (cmd == @selector(insertLineBreak:)) {
742     OnControlKeyChanged(true);
743     WindowOpenDisposition disposition =
744         ui::WindowOpenDispositionFromNSEvent([NSApp currentEvent]);
745     model()->AcceptInput(disposition, false);
746     return true;
747   }
749   if (cmd == @selector(deleteBackward:)) {
750     if (OnBackspacePressed()) {
751       return true;
752     }
753   }
755   if (cmd == @selector(deleteForward:)) {
756     const NSUInteger modifiers = [[NSApp currentEvent] modifierFlags];
757     if ((modifiers & NSShiftKeyMask) != 0) {
758       if (model()->popup_model()->IsOpen()) {
759         model()->popup_model()->TryDeletingCurrentItem();
760         return true;
761       }
762     }
763   }
765   return false;
768 void OmniboxViewMac::OnSetFocus(bool control_down) {
769   model()->OnSetFocus(control_down);
770   controller()->OnSetFocus();
773 void OmniboxViewMac::OnKillFocus() {
774   // Tell the model to reset itself.
775   model()->OnWillKillFocus(NULL);
776   model()->OnKillFocus();
779 void OmniboxViewMac::OnMouseDown(NSInteger button_number) {
780   // Restore caret visibility whenever the user clicks in the the omnibox. This
781   // is not always covered by OnSetFocus() because when clicking while the
782   // omnibox has invisible focus does not trigger a new OnSetFocus() call.
783   if (button_number == 0 || button_number == 1)
784     model()->SetCaretVisibility(true);
787 bool OmniboxViewMac::ShouldSelectAllOnMouseDown() {
788   return !controller()->GetToolbarModel()->WouldPerformSearchTermReplacement(
789       false);
792 bool OmniboxViewMac::CanCopy() {
793   const NSRange selection = GetSelectedRange();
794   return selection.length > 0;
797 void OmniboxViewMac::CopyToPasteboard(NSPasteboard* pb) {
798   DCHECK(CanCopy());
800   const NSRange selection = GetSelectedRange();
801   base::string16 text = base::SysNSStringToUTF16(
802       [[field_ stringValue] substringWithRange:selection]);
804   // Copy the URL unless this is the search URL and it's being replaced by the
805   // Extended Instant API.
806   GURL url;
807   bool write_url = false;
808   if (!controller()->GetToolbarModel()->WouldPerformSearchTermReplacement(
809       false)) {
810     model()->AdjustTextForCopy(selection.location, IsSelectAll(), &text, &url,
811                                &write_url);
812   }
814   if (IsSelectAll())
815     UMA_HISTOGRAM_COUNTS(OmniboxEditModel::kCutOrCopyAllTextHistogram, 1);
817   NSString* nstext = base::SysUTF16ToNSString(text);
818   [pb declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
819   [pb setString:nstext forType:NSStringPboardType];
821   if (write_url) {
822     [pb declareURLPasteboardWithAdditionalTypes:[NSArray array] owner:nil];
823     [pb setDataForURL:base::SysUTF8ToNSString(url.spec()) title:nstext];
824   }
827 void OmniboxViewMac::ShowURL() {
828   DCHECK(ShouldEnableShowURL());
829   OmniboxView::ShowURL();
832 void OmniboxViewMac::OnPaste() {
833   // This code currently expects |field_| to be focussed.
834   DCHECK([field_ currentEditor]);
836   base::string16 text = GetClipboardText();
837   if (text.empty()) {
838     return;
839   }
840   NSString* s = base::SysUTF16ToNSString(text);
842   // -shouldChangeTextInRange:* and -didChangeText are documented in
843   // NSTextView as things you need to do if you write additional
844   // user-initiated editing functions.  They cause the appropriate
845   // delegate methods to be called.
846   // TODO(shess): It would be nice to separate the Cocoa-specific code
847   // from the Chrome-specific code.
848   NSTextView* editor = static_cast<NSTextView*>([field_ currentEditor]);
849   const NSRange selectedRange = GetSelectedRange();
850   if ([editor shouldChangeTextInRange:selectedRange replacementString:s]) {
851     // Record this paste, so we can do different behavior.
852     model()->OnPaste();
854     // Force a Paste operation to trigger the text_changed code in
855     // OnAfterPossibleChange(), even if identical contents are pasted
856     // into the text box.
857     text_before_change_.clear();
859     [editor replaceCharactersInRange:selectedRange withString:s];
860     [editor didChangeText];
861   }
864 // TODO(dominich): Move to OmniboxView base class? Currently this is defined on
865 // the AutocompleteTextFieldObserver but the logic is shared between all
866 // platforms. Some refactor might be necessary to simplify this. Or at least
867 // this method could call the OmniboxView version.
868 bool OmniboxViewMac::ShouldEnableShowURL() {
869   return controller()->GetToolbarModel()->WouldReplaceURL();
872 bool OmniboxViewMac::CanPasteAndGo() {
873   return model()->CanPasteAndGo(GetClipboardText());
876 int OmniboxViewMac::GetPasteActionStringId() {
877   base::string16 text(GetClipboardText());
878   DCHECK(model()->CanPasteAndGo(text));
879   return model()->IsPasteAndSearch(text) ?
880       IDS_PASTE_AND_SEARCH : IDS_PASTE_AND_GO;
883 void OmniboxViewMac::OnPasteAndGo() {
884   base::string16 text(GetClipboardText());
885   if (model()->CanPasteAndGo(text))
886     model()->PasteAndGo(text);
889 void OmniboxViewMac::OnFrameChanged() {
890   // TODO(shess): UpdatePopupAppearance() is called frequently, so it
891   // should be really cheap, but in this case we could probably make
892   // things even cheaper by refactoring between the popup-placement
893   // code and the matrix-population code.
894   popup_view_->UpdatePopupAppearance();
896   // Give controller a chance to rearrange decorations.
897   model()->OnChanged();
900 void OmniboxViewMac::ClosePopup() {
901   OmniboxView::CloseOmniboxPopup();
904 bool OmniboxViewMac::OnBackspacePressed() {
905   // Don't intercept if not in keyword search mode.
906   if (model()->is_keyword_hint() || model()->keyword().empty()) {
907     return false;
908   }
910   // Don't intercept if there is a selection, or the cursor isn't at
911   // the leftmost position.
912   const NSRange selection = GetSelectedRange();
913   if (selection.length > 0 || selection.location > 0) {
914     return false;
915   }
917   // We're showing a keyword and the user pressed backspace at the
918   // beginning of the text.  Delete the selected keyword.
919   model()->ClearKeyword(GetText());
920   return true;
923 NSRange OmniboxViewMac::SelectionRangeForProposedRange(NSRange proposed_range) {
924   return proposed_range;
927 void OmniboxViewMac::OnControlKeyChanged(bool pressed) {
928   model()->OnControlKeyChanged(pressed);
931 void OmniboxViewMac::FocusLocation(bool select_all) {
932   if ([field_ isEditable]) {
933     // If the text field has a field editor, it's the first responder, meaning
934     // that it's already focused. makeFirstResponder: will select all, so only
935     // call it if this behavior is desired.
936     if (select_all || ![field_ currentEditor])
937       [[field_ window] makeFirstResponder:field_];
938     DCHECK_EQ([field_ currentEditor], [[field_ window] firstResponder]);
939   }
942 // static
943 NSFont* OmniboxViewMac::GetFieldFont() {
944   // This value should be kept in sync with InstantPage::InitializeFonts.
945   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
946   return rb.GetFont(ResourceBundle::BaseFont).DeriveFont(1).GetNativeFont();
949 int OmniboxViewMac::GetOmniboxTextLength() const {
950   return static_cast<int>(GetTextLength());
953 NSUInteger OmniboxViewMac::GetTextLength() const {
954   return [field_ currentEditor] ?  [[[field_ currentEditor] string] length] :
955                                    [[field_ stringValue] length];
958 bool OmniboxViewMac::IsCaretAtEnd() const {
959   const NSRange selection = GetSelectedRange();
960   return NSMaxRange(selection) == GetTextLength();