NaCl: Update revision in DEPS, r12770 -> r12773
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / omnibox / omnibox_view_mac.mm
blobac87f9e73b3c834bf689fad11d4c21aa1dfa0e1e
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/font_list.h"
32 #include "ui/gfx/geometry/rect.h"
34 using content::WebContents;
36 // Focus-handling between |field_| and model() is a bit subtle.
37 // Other platforms detect change of focus, which is inconvenient
38 // without subclassing NSTextField (even with a subclass, the use of a
39 // field editor may complicate things).
41 // model() doesn't actually do anything when it gains focus, it just
42 // initializes.  Visible activity happens only after the user edits.
43 // NSTextField delegate receives messages around starting and ending
44 // edits, so that suffices to catch focus changes.  Since all calls
45 // into model() start from OmniboxViewMac, in the worst case
46 // we can add code to sync up the sense of focus as needed.
48 // I've added DCHECK(IsFirstResponder()) in the places which I believe
49 // should only be reachable when |field_| is being edited.  If these
50 // fire, it probably means someone unexpected is calling into
51 // model().
53 // Other platforms don't appear to have the sense of "key window" that
54 // Mac does (I believe their fields lose focus when the window loses
55 // focus).  Rather than modifying focus outside the control's edit
56 // scope, when the window resigns key the autocomplete popup is
57 // closed.  model() still believes it has focus, and the popup will
58 // be regenerated on the user's next edit.  That seems to match how
59 // things work on other platforms.
61 namespace {
63 // TODO(shess): This is ugly, find a better way.  Using it right now
64 // so that I can crib from gtk and still be able to see that I'm using
65 // the same values easily.
66 NSColor* ColorWithRGBBytes(int rr, int gg, int bb) {
67   DCHECK_LE(rr, 255);
68   DCHECK_LE(bb, 255);
69   DCHECK_LE(gg, 255);
70   return [NSColor colorWithCalibratedRed:static_cast<float>(rr)/255.0
71                                    green:static_cast<float>(gg)/255.0
72                                     blue:static_cast<float>(bb)/255.0
73                                    alpha:1.0];
76 NSColor* HostTextColor() {
77   return [NSColor blackColor];
79 NSColor* BaseTextColor() {
80   return [NSColor darkGrayColor];
82 NSColor* SecureSchemeColor() {
83   return ColorWithRGBBytes(0x07, 0x95, 0x00);
85 NSColor* SecurityErrorSchemeColor() {
86   return ColorWithRGBBytes(0xa2, 0x00, 0x00);
89 const char kOmniboxViewMacStateKey[] = "OmniboxViewMacState";
91 // Store's the model and view state across tab switches.
92 struct OmniboxViewMacState : public base::SupportsUserData::Data {
93   OmniboxViewMacState(const OmniboxEditModel::State model_state,
94                       const bool has_focus,
95                       const NSRange& selection)
96       : model_state(model_state),
97         has_focus(has_focus),
98         selection(selection) {
99   }
100   virtual ~OmniboxViewMacState() {}
102   const OmniboxEditModel::State model_state;
103   const bool has_focus;
104   const NSRange selection;
107 // Accessors for storing and getting the state from the tab.
108 void StoreStateToTab(WebContents* tab,
109                      OmniboxViewMacState* state) {
110   tab->SetUserData(kOmniboxViewMacStateKey, state);
112 const OmniboxViewMacState* GetStateFromTab(const WebContents* tab) {
113   return static_cast<OmniboxViewMacState*>(
114       tab->GetUserData(&kOmniboxViewMacStateKey));
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* OmniboxViewMac::ImageForResource(int resource_id) {
128   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
129   return rb.GetNativeImageNamed(resource_id).ToNSImage();
132 // static
133 NSColor* OmniboxViewMac::SuggestTextColor() {
134   return [NSColor colorWithCalibratedWhite:0.0 alpha:0.5];
137 OmniboxViewMac::OmniboxViewMac(OmniboxEditController* controller,
138                                Profile* profile,
139                                CommandUpdater* command_updater,
140                                AutocompleteTextField* field)
141     : OmniboxView(profile, controller, command_updater),
142       popup_view_(new OmniboxPopupViewMac(this, model(), field)),
143       field_(field),
144       saved_temporary_selection_(NSMakeRange(0, 0)),
145       selection_before_change_(NSMakeRange(0, 0)),
146       marked_range_before_change_(NSMakeRange(0, 0)),
147       delete_was_pressed_(false),
148       delete_at_end_pressed_(false) {
149   [field_ setObserver:this];
151   // Needed so that editing doesn't lose the styling.
152   [field_ setAllowsEditingTextAttributes:YES];
154   // Get the appropriate line height for the font that we use.
155   base::scoped_nsobject<NSLayoutManager> layoutManager(
156       [[NSLayoutManager alloc] init]);
157   [layoutManager setUsesScreenFonts:YES];
160 OmniboxViewMac::~OmniboxViewMac() {
161   // Destroy popup view before this object in case it tries to call us
162   // back in the destructor.
163   popup_view_.reset();
165   // Disconnect from |field_|, it outlives this object.
166   [field_ setObserver:NULL];
169 void OmniboxViewMac::SaveStateToTab(WebContents* tab) {
170   DCHECK(tab);
172   const bool hasFocus = [field_ currentEditor] ? true : false;
174   NSRange range;
175   if (hasFocus) {
176     range = GetSelectedRange();
177   } else {
178     // If we are not focussed, there is no selection.  Manufacture
179     // something reasonable in case it starts to matter in the future.
180     range = NSMakeRange(0, GetTextLength());
181   }
183   OmniboxViewMacState* state =
184       new OmniboxViewMacState(model()->GetStateForTabSwitch(), hasFocus, range);
185   StoreStateToTab(tab, state);
188 void OmniboxViewMac::OnTabChanged(const WebContents* web_contents) {
189   const OmniboxViewMacState* state = GetStateFromTab(web_contents);
190   model()->RestoreState(state ? &state->model_state : NULL);
191   // Restore focus and selection if they were present when the tab
192   // was switched away.
193   if (state && state->has_focus) {
194     // TODO(shess): Unfortunately, there is no safe way to update
195     // this because TabStripController -selectTabWithContents:* is
196     // also messing with focus.  Both parties need to agree to
197     // store existing state before anyone tries to setup the new
198     // state.  Anyhow, it would look something like this.
199 #if 0
200     [[field_ window] makeFirstResponder:field_];
201     [[field_ currentEditor] setSelectedRange:state->selection];
202 #endif
203   }
206 void OmniboxViewMac::Update() {
207   if (model()->UpdatePermanentText()) {
208     // Something visibly changed.  Re-enable URL replacement.
209     controller()->GetToolbarModel()->set_url_replacement_enabled(true);
210     model()->UpdatePermanentText();
212     // Restore everything to the baseline look.
213     RevertAll();
215     // TODO(shess): Figure out how this case is used, to make sure
216     // we're getting the selection and popup right.
217   } else {
218     // TODO(shess): This corresponds to _win and _gtk, except those
219     // guard it with a test for whether the security level changed.
220     // But AFAICT, that can only change if the text changed, and that
221     // code compares the toolbar model security level with the local
222     // security level.  Dig in and figure out why this isn't a no-op
223     // that should go away.
224     EmphasizeURLComponents();
225   }
228 base::string16 OmniboxViewMac::GetText() const {
229   return base::SysNSStringToUTF16([field_ stringValue]);
232 NSRange OmniboxViewMac::GetSelectedRange() const {
233   return [[field_ currentEditor] selectedRange];
236 NSRange OmniboxViewMac::GetMarkedRange() const {
237   DCHECK([field_ currentEditor]);
238   return [(NSTextView*)[field_ currentEditor] markedRange];
241 void OmniboxViewMac::SetSelectedRange(const NSRange range) {
242   // This can be called when we don't have focus.  For instance, when
243   // the user clicks the "Go" button.
244   if (model()->has_focus()) {
245     // TODO(shess): If model() thinks we have focus, this should not
246     // be necessary.  Try to convert to DCHECK(IsFirstResponder()).
247     if (![field_ currentEditor]) {
248       [[field_ window] makeFirstResponder:field_];
249     }
251     // TODO(shess): What if it didn't get first responder, and there is
252     // no field editor?  This will do nothing.  Well, at least it won't
253     // crash.  Think of something more productive to do, or prove that
254     // it cannot occur and DCHECK appropriately.
255     [[field_ currentEditor] setSelectedRange:range];
256   }
259 void OmniboxViewMac::SetWindowTextAndCaretPos(const base::string16& text,
260                                               size_t caret_pos,
261                                               bool update_popup,
262                                               bool notify_text_changed) {
263   DCHECK_LE(caret_pos, text.size());
264   SetTextAndSelectedRange(text, NSMakeRange(caret_pos, 0));
266   if (update_popup)
267     UpdatePopup();
269   if (notify_text_changed)
270     TextChanged();
273 void OmniboxViewMac::SetForcedQuery() {
274   // We need to do this first, else |SetSelectedRange()| won't work.
275   FocusLocation(true);
277   const base::string16 current_text(GetText());
278   const size_t start = current_text.find_first_not_of(base::kWhitespaceUTF16);
279   if (start == base::string16::npos || (current_text[start] != '?')) {
280     SetUserText(base::ASCIIToUTF16("?"));
281   } else {
282     NSRange range = NSMakeRange(start + 1, current_text.size() - start - 1);
283     [[field_ currentEditor] setSelectedRange:range];
284   }
287 bool OmniboxViewMac::IsSelectAll() const {
288   if (![field_ currentEditor])
289     return true;
290   const NSRange all_range = NSMakeRange(0, GetTextLength());
291   return NSEqualRanges(all_range, GetSelectedRange());
294 bool OmniboxViewMac::DeleteAtEndPressed() {
295   return delete_at_end_pressed_;
298 void OmniboxViewMac::GetSelectionBounds(base::string16::size_type* start,
299                                         base::string16::size_type* end) const {
300   if (![field_ currentEditor]) {
301     *start = *end = 0;
302     return;
303   }
305   const NSRange selected_range = GetSelectedRange();
306   *start = static_cast<size_t>(selected_range.location);
307   *end = static_cast<size_t>(NSMaxRange(selected_range));
310 void OmniboxViewMac::SelectAll(bool reversed) {
311   // TODO(shess): Figure out what |reversed| implies.  The gtk version
312   // has it imply inverting the selection front to back, but I don't
313   // even know if that makes sense for Mac.
315   // TODO(shess): Verify that we should be stealing focus at this
316   // point.
317   SetSelectedRange(NSMakeRange(0, GetTextLength()));
320 void OmniboxViewMac::RevertAll() {
321   OmniboxView::RevertAll();
322   [field_ clearUndoChain];
325 void OmniboxViewMac::UpdatePopup() {
326   model()->SetInputInProgress(true);
327   if (!model()->has_focus())
328     return;
330   // Comment copied from OmniboxViewWin::UpdatePopup():
331   // Don't inline autocomplete when:
332   //   * The user is deleting text
333   //   * The caret/selection isn't at the end of the text
334   //   * The user has just pasted in something that replaced all the text
335   //   * The user is trying to compose something in an IME
336   bool prevent_inline_autocomplete = IsImeComposing();
337   NSTextView* editor = (NSTextView*)[field_ currentEditor];
338   if (editor) {
339     if (NSMaxRange([editor selectedRange]) < [[editor textStorage] length])
340       prevent_inline_autocomplete = true;
341   }
343   model()->StartAutocomplete([editor selectedRange].length != 0,
344                             prevent_inline_autocomplete);
347 void OmniboxViewMac::CloseOmniboxPopup() {
348   // Call both base class methods.
349   ClosePopup();
350   OmniboxView::CloseOmniboxPopup();
353 void OmniboxViewMac::SetFocus() {
354   FocusLocation(false);
355   model()->SetCaretVisibility(true);
358 void OmniboxViewMac::ApplyCaretVisibility() {
359   [[field_ cell] setHideFocusState:!model()->is_caret_visible()
360                             ofView:field_];
363 void OmniboxViewMac::SetText(const base::string16& display_text) {
364   SetTextInternal(display_text);
367 void OmniboxViewMac::SetTextInternal(const base::string16& display_text) {
368   NSString* ss = base::SysUTF16ToNSString(display_text);
369   NSMutableAttributedString* as =
370       [[[NSMutableAttributedString alloc] initWithString:ss] autorelease];
372   ApplyTextAttributes(display_text, as);
373   [field_ setAttributedStringValue:as];
375   // TODO(shess): This may be an appropriate place to call:
376   //   model()->OnChanged();
377   // In the current implementation, this tells LocationBarViewMac to
378   // mess around with model() and update |field_|.  Unfortunately,
379   // when I look at our peer implementations, it's not entirely clear
380   // to me if this is safe.  SetTextInternal() is sort of an utility method,
381   // and different callers sometimes have different needs.  Research
382   // this issue so that it can be added safely.
384   // TODO(shess): Also, consider whether this code couldn't just
385   // manage things directly.  Windows uses a series of overlaid view
386   // objects to accomplish the hinting stuff that OnChanged() does, so
387   // it makes sense to have it in the controller that lays those
388   // things out.  Mac instead pushes the support into a custom
389   // text-field implementation.
392 void OmniboxViewMac::SetTextAndSelectedRange(const base::string16& display_text,
393                                              const NSRange range) {
394   SetText(display_text);
395   SetSelectedRange(range);
398 void OmniboxViewMac::EmphasizeURLComponents() {
399   NSTextView* editor = (NSTextView*)[field_ currentEditor];
400   // If the autocomplete text field is in editing mode, then we can just change
401   // its attributes through its editor. Otherwise, we simply reset its content.
402   if (editor) {
403     NSTextStorage* storage = [editor textStorage];
404     [storage beginEditing];
406     // Clear the existing attributes from the text storage, then
407     // overlay the appropriate Omnibox attributes.
408     [storage setAttributes:[NSDictionary dictionary]
409                      range:NSMakeRange(0, [storage length])];
410     ApplyTextAttributes(GetText(), storage);
412     [storage endEditing];
413   } else {
414     SetText(GetText());
415   }
418 void OmniboxViewMac::ApplyTextAttributes(const base::string16& display_text,
419                                          NSMutableAttributedString* as) {
420   NSUInteger as_length = [as length];
421   NSRange as_entire_string = NSMakeRange(0, as_length);
423   [as addAttribute:NSFontAttributeName value:GetFieldFont()
424              range:as_entire_string];
426   // A kinda hacky way to add breaking at periods. This is what Safari does.
427   // This works for IDNs too, despite the "en_US".
428   [as addAttribute:@"NSLanguage" value:@"en_US_POSIX"
429              range:as_entire_string];
431   // Make a paragraph style locking in the standard line height as the maximum,
432   // otherwise the baseline may shift "downwards".
433   base::scoped_nsobject<NSMutableParagraphStyle> paragraph_style(
434       [[NSMutableParagraphStyle alloc] init]);
435   CGFloat line_height = [[field_ cell] lineHeight];
436   [paragraph_style setMaximumLineHeight:line_height];
437   [paragraph_style setMinimumLineHeight:line_height];
438   [paragraph_style setLineBreakMode:NSLineBreakByTruncatingTail];
439   [as addAttribute:NSParagraphStyleAttributeName value:paragraph_style
440              range:as_entire_string];
442   url_parse::Component scheme, host;
443   AutocompleteInput::ParseForEmphasizeComponents(
444       display_text, &scheme, &host);
445   bool grey_out_url = display_text.substr(scheme.begin, scheme.len) ==
446       base::UTF8ToUTF16(extensions::kExtensionScheme);
447   if (model()->CurrentTextIsURL() &&
448       (host.is_nonempty() || grey_out_url)) {
449     [as addAttribute:NSForegroundColorAttributeName value:BaseTextColor()
450                range:as_entire_string];
452     if (!grey_out_url) {
453       [as addAttribute:NSForegroundColorAttributeName value:HostTextColor()
454                range:ComponentToNSRange(host)];
455     }
456   }
458   // TODO(shess): GTK has this as a member var, figure out why.
459   // [Could it be to not change if no change?  If so, I'm guessing
460   // AppKit may already handle that.]
461   const ToolbarModel::SecurityLevel security_level =
462       controller()->GetToolbarModel()->GetSecurityLevel(false);
464   // Emphasize the scheme for security UI display purposes (if necessary).
465   if (!model()->user_input_in_progress() && model()->CurrentTextIsURL() &&
466       scheme.is_nonempty() && (security_level != ToolbarModel::NONE)) {
467     NSColor* color;
468     if (security_level == ToolbarModel::EV_SECURE ||
469         security_level == ToolbarModel::SECURE) {
470       color = SecureSchemeColor();
471     } else if (security_level == ToolbarModel::SECURITY_ERROR) {
472       color = SecurityErrorSchemeColor();
473       // Add a strikethrough through the scheme.
474       [as addAttribute:NSStrikethroughStyleAttributeName
475                  value:[NSNumber numberWithInt:NSUnderlineStyleSingle]
476                  range:ComponentToNSRange(scheme)];
477     } else if (security_level == ToolbarModel::SECURITY_WARNING) {
478       color = BaseTextColor();
479     } else {
480       NOTREACHED();
481       color = BaseTextColor();
482     }
483     [as addAttribute:NSForegroundColorAttributeName value:color
484                range:ComponentToNSRange(scheme)];
485   }
488 void OmniboxViewMac::OnTemporaryTextMaybeChanged(
489     const base::string16& display_text,
490     bool save_original_selection,
491     bool notify_text_changed) {
492   if (save_original_selection)
493     saved_temporary_selection_ = GetSelectedRange();
495   SetWindowTextAndCaretPos(display_text, display_text.size(), false, false);
496   if (notify_text_changed)
497     model()->OnChanged();
498   [field_ clearUndoChain];
501 bool OmniboxViewMac::OnInlineAutocompleteTextMaybeChanged(
502     const base::string16& display_text,
503     size_t user_text_length) {
504   // TODO(shess): Make sure that this actually works.  The round trip
505   // to native form and back may mean that it's the same but not the
506   // same.
507   if (display_text == GetText())
508     return false;
510   DCHECK_LE(user_text_length, display_text.size());
511   const NSRange range =
512       NSMakeRange(user_text_length, display_text.size() - user_text_length);
513   SetTextAndSelectedRange(display_text, range);
514   model()->OnChanged();
515   [field_ clearUndoChain];
517   return true;
520 void OmniboxViewMac::OnInlineAutocompleteTextCleared() {
523 void OmniboxViewMac::OnRevertTemporaryText() {
524   SetSelectedRange(saved_temporary_selection_);
525   // We got here because the user hit the Escape key. We explicitly don't call
526   // TextChanged(), since OmniboxPopupModel::ResetToDefaultMatch() has already
527   // been called by now, and it would've called TextChanged() if it was
528   // warranted.
531 bool OmniboxViewMac::IsFirstResponder() const {
532   return [field_ currentEditor] != nil ? true : false;
535 void OmniboxViewMac::OnBeforePossibleChange() {
536   // We should only arrive here when the field is focussed.
537   DCHECK(IsFirstResponder());
539   selection_before_change_ = GetSelectedRange();
540   text_before_change_ = GetText();
541   marked_range_before_change_ = GetMarkedRange();
544 bool OmniboxViewMac::OnAfterPossibleChange() {
545   // We should only arrive here when the field is focussed.
546   DCHECK(IsFirstResponder());
548   const NSRange new_selection(GetSelectedRange());
549   const base::string16 new_text(GetText());
550   const size_t length = new_text.length();
552   const bool selection_differs =
553       (new_selection.length || selection_before_change_.length) &&
554       !NSEqualRanges(new_selection, selection_before_change_);
555   const bool at_end_of_edit = (length == new_selection.location);
556   const bool text_differs = (new_text != text_before_change_) ||
557       !NSEqualRanges(marked_range_before_change_, GetMarkedRange());
559   // When the user has deleted text, we don't allow inline
560   // autocomplete.  This is assumed if the text has gotten shorter AND
561   // the selection has shifted towards the front of the text.  During
562   // normal typing the text will almost always be shorter (as the new
563   // input replaces the autocomplete suggestion), but in that case the
564   // selection point will have moved towards the end of the text.
565   // TODO(shess): In our implementation, we can catch -deleteBackward:
566   // and other methods to provide positive knowledge that a delete
567   // occured, rather than intuiting it from context.  Consider whether
568   // that would be a stronger approach.
569   const bool just_deleted_text =
570       (length < text_before_change_.length() &&
571        new_selection.location <= selection_before_change_.location);
573   delete_at_end_pressed_ = false;
575   const bool something_changed = model()->OnAfterPossibleChange(
576       text_before_change_, new_text, new_selection.location,
577       NSMaxRange(new_selection), selection_differs, text_differs,
578       just_deleted_text, !IsImeComposing());
580   if (delete_was_pressed_ && at_end_of_edit)
581     delete_at_end_pressed_ = true;
583   // Restyle in case the user changed something.
584   // TODO(shess): I believe there are multiple-redraw cases, here.
585   // Linux watches for something_changed && text_differs, but that
586   // fails for us in case you copy the URL and paste the identical URL
587   // back (we'll lose the styling).
588   TextChanged();
590   delete_was_pressed_ = false;
592   return something_changed;
595 gfx::NativeView OmniboxViewMac::GetNativeView() const {
596   return field_;
599 gfx::NativeView OmniboxViewMac::GetRelativeWindowForPopup() const {
600   // Not used on mac.
601   NOTREACHED();
602   return NULL;
605 void OmniboxViewMac::SetGrayTextAutocompletion(
606     const base::string16& suggest_text) {
607   if (suggest_text == suggest_text_)
608     return;
609   suggest_text_ = suggest_text;
610   [field_ setGrayTextAutocompletion:base::SysUTF16ToNSString(suggest_text)
611                           textColor:SuggestTextColor()];
614 base::string16 OmniboxViewMac::GetGrayTextAutocompletion() const {
615   return suggest_text_;
618 int OmniboxViewMac::GetTextWidth() const {
619   // Not used on mac.
620   NOTREACHED();
621   return 0;
624 int OmniboxViewMac::GetWidth() const {
625   return ceil([field_ bounds].size.width);
628 bool OmniboxViewMac::IsImeComposing() const {
629   return [(NSTextView*)[field_ currentEditor] hasMarkedText];
632 void OmniboxViewMac::OnDidBeginEditing() {
633   // We should only arrive here when the field is focussed.
634   DCHECK([field_ currentEditor]);
637 void OmniboxViewMac::OnBeforeChange() {
638   // Capture the current state.
639   OnBeforePossibleChange();
642 void OmniboxViewMac::OnDidChange() {
643   // Figure out what changed and notify the model.
644   OnAfterPossibleChange();
647 void OmniboxViewMac::OnDidEndEditing() {
648   ClosePopup();
651 bool OmniboxViewMac::OnDoCommandBySelector(SEL cmd) {
652   if (cmd == @selector(deleteForward:))
653     delete_was_pressed_ = true;
655   if (cmd == @selector(moveDown:)) {
656     model()->OnUpOrDownKeyPressed(1);
657     return true;
658   }
660   if (cmd == @selector(moveUp:)) {
661     model()->OnUpOrDownKeyPressed(-1);
662     return true;
663   }
665   if (model()->popup_model()->IsOpen()) {
666     if (cmd == @selector(insertBacktab:)) {
667       if (model()->popup_model()->selected_line_state() ==
668             OmniboxPopupModel::KEYWORD) {
669         model()->ClearKeyword(GetText());
670         return true;
671       } else {
672         model()->OnUpOrDownKeyPressed(-1);
673         return true;
674       }
675     }
677     if ((cmd == @selector(insertTab:) ||
678         cmd == @selector(insertTabIgnoringFieldEditor:)) &&
679         !model()->is_keyword_hint()) {
680       model()->OnUpOrDownKeyPressed(1);
681       return true;
682     }
683   }
685   if (cmd == @selector(moveRight:)) {
686     // Only commit suggested text if the cursor is all the way to the right and
687     // there is no selection.
688     if (suggest_text_.length() > 0 && IsCaretAtEnd()) {
689       model()->CommitSuggestedText();
690       return true;
691     }
692   }
694   if (cmd == @selector(scrollPageDown:)) {
695     model()->OnUpOrDownKeyPressed(model()->result().size());
696     return true;
697   }
699   if (cmd == @selector(scrollPageUp:)) {
700     model()->OnUpOrDownKeyPressed(-model()->result().size());
701     return true;
702   }
704   if (cmd == @selector(cancelOperation:)) {
705     return model()->OnEscapeKeyPressed();
706   }
708   if ((cmd == @selector(insertTab:) ||
709       cmd == @selector(insertTabIgnoringFieldEditor:)) &&
710       model()->is_keyword_hint()) {
711     return model()->AcceptKeyword(ENTERED_KEYWORD_MODE_VIA_TAB);
712   }
714   // |-noop:| is sent when the user presses Cmd+Return. Override the no-op
715   // behavior with the proper WindowOpenDisposition.
716   NSEvent* event = [NSApp currentEvent];
717   if (cmd == @selector(insertNewline:) ||
718      (cmd == @selector(noop:) &&
719       ([event type] == NSKeyDown || [event type] == NSKeyUp) &&
720       [event keyCode] == kVK_Return)) {
721     WindowOpenDisposition disposition =
722         ui::WindowOpenDispositionFromNSEvent(event);
723     model()->AcceptInput(disposition, false);
724     // Opening a URL in a background tab should also revert the omnibox contents
725     // to their original state.  We cannot do a blanket revert in OpenURL()
726     // because middle-clicks also open in a new background tab, but those should
727     // not revert the omnibox text.
728     RevertAll();
729     return true;
730   }
732   // Option-Return
733   if (cmd == @selector(insertNewlineIgnoringFieldEditor:)) {
734     model()->AcceptInput(NEW_FOREGROUND_TAB, false);
735     return true;
736   }
738   // When the user does Control-Enter, the existing content has "www."
739   // prepended and ".com" appended.  model() should already have
740   // received notification when the Control key was depressed, but it
741   // is safe to tell it twice.
742   if (cmd == @selector(insertLineBreak:)) {
743     OnControlKeyChanged(true);
744     WindowOpenDisposition disposition =
745         ui::WindowOpenDispositionFromNSEvent([NSApp currentEvent]);
746     model()->AcceptInput(disposition, false);
747     return true;
748   }
750   if (cmd == @selector(deleteBackward:)) {
751     if (OnBackspacePressed()) {
752       return true;
753     }
754   }
756   if (cmd == @selector(deleteForward:)) {
757     const NSUInteger modifiers = [[NSApp currentEvent] modifierFlags];
758     if ((modifiers & NSShiftKeyMask) != 0) {
759       if (model()->popup_model()->IsOpen()) {
760         model()->popup_model()->TryDeletingCurrentItem();
761         return true;
762       }
763     }
764   }
766   return false;
769 void OmniboxViewMac::OnSetFocus(bool control_down) {
770   model()->OnSetFocus(control_down);
771   controller()->OnSetFocus();
774 void OmniboxViewMac::OnKillFocus() {
775   // Tell the model to reset itself.
776   model()->OnWillKillFocus(NULL);
777   model()->OnKillFocus();
780 void OmniboxViewMac::OnMouseDown(NSInteger button_number) {
781   // Restore caret visibility whenever the user clicks in the the omnibox. This
782   // is not always covered by OnSetFocus() because when clicking while the
783   // omnibox has invisible focus does not trigger a new OnSetFocus() call.
784   if (button_number == 0 || button_number == 1)
785     model()->SetCaretVisibility(true);
788 bool OmniboxViewMac::ShouldSelectAllOnMouseDown() {
789   return !controller()->GetToolbarModel()->WouldPerformSearchTermReplacement(
790       false);
793 bool OmniboxViewMac::CanCopy() {
794   const NSRange selection = GetSelectedRange();
795   return selection.length > 0;
798 void OmniboxViewMac::CopyToPasteboard(NSPasteboard* pb) {
799   DCHECK(CanCopy());
801   const NSRange selection = GetSelectedRange();
802   base::string16 text = base::SysNSStringToUTF16(
803       [[field_ stringValue] substringWithRange:selection]);
805   // Copy the URL unless this is the search URL and it's being replaced by the
806   // Extended Instant API.
807   GURL url;
808   bool write_url = false;
809   if (!controller()->GetToolbarModel()->WouldPerformSearchTermReplacement(
810       false)) {
811     model()->AdjustTextForCopy(selection.location, IsSelectAll(), &text, &url,
812                                &write_url);
813   }
815   if (IsSelectAll())
816     UMA_HISTOGRAM_COUNTS(OmniboxEditModel::kCutOrCopyAllTextHistogram, 1);
818   NSString* nstext = base::SysUTF16ToNSString(text);
819   [pb declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
820   [pb setString:nstext forType:NSStringPboardType];
822   if (write_url) {
823     [pb declareURLPasteboardWithAdditionalTypes:[NSArray array] owner:nil];
824     [pb setDataForURL:base::SysUTF8ToNSString(url.spec()) title:nstext];
825   }
828 void OmniboxViewMac::ShowURL() {
829   DCHECK(ShouldEnableShowURL());
830   OmniboxView::ShowURL();
833 void OmniboxViewMac::OnPaste() {
834   // This code currently expects |field_| to be focussed.
835   DCHECK([field_ currentEditor]);
837   base::string16 text = GetClipboardText();
838   if (text.empty()) {
839     return;
840   }
841   NSString* s = base::SysUTF16ToNSString(text);
843   // -shouldChangeTextInRange:* and -didChangeText are documented in
844   // NSTextView as things you need to do if you write additional
845   // user-initiated editing functions.  They cause the appropriate
846   // delegate methods to be called.
847   // TODO(shess): It would be nice to separate the Cocoa-specific code
848   // from the Chrome-specific code.
849   NSTextView* editor = static_cast<NSTextView*>([field_ currentEditor]);
850   const NSRange selectedRange = GetSelectedRange();
851   if ([editor shouldChangeTextInRange:selectedRange replacementString:s]) {
852     // Record this paste, so we can do different behavior.
853     model()->OnPaste();
855     // Force a Paste operation to trigger the text_changed code in
856     // OnAfterPossibleChange(), even if identical contents are pasted
857     // into the text box.
858     text_before_change_.clear();
860     [editor replaceCharactersInRange:selectedRange withString:s];
861     [editor didChangeText];
862   }
865 // TODO(dominich): Move to OmniboxView base class? Currently this is defined on
866 // the AutocompleteTextFieldObserver but the logic is shared between all
867 // platforms. Some refactor might be necessary to simplify this. Or at least
868 // this method could call the OmniboxView version.
869 bool OmniboxViewMac::ShouldEnableShowURL() {
870   return controller()->GetToolbarModel()->WouldReplaceURL();
873 bool OmniboxViewMac::CanPasteAndGo() {
874   return model()->CanPasteAndGo(GetClipboardText());
877 int OmniboxViewMac::GetPasteActionStringId() {
878   base::string16 text(GetClipboardText());
879   DCHECK(model()->CanPasteAndGo(text));
880   return model()->IsPasteAndSearch(text) ?
881       IDS_PASTE_AND_SEARCH : IDS_PASTE_AND_GO;
884 void OmniboxViewMac::OnPasteAndGo() {
885   base::string16 text(GetClipboardText());
886   if (model()->CanPasteAndGo(text))
887     model()->PasteAndGo(text);
890 void OmniboxViewMac::OnFrameChanged() {
891   // TODO(shess): UpdatePopupAppearance() is called frequently, so it
892   // should be really cheap, but in this case we could probably make
893   // things even cheaper by refactoring between the popup-placement
894   // code and the matrix-population code.
895   popup_view_->UpdatePopupAppearance();
897   // Give controller a chance to rearrange decorations.
898   model()->OnChanged();
901 void OmniboxViewMac::ClosePopup() {
902   OmniboxView::CloseOmniboxPopup();
905 bool OmniboxViewMac::OnBackspacePressed() {
906   // Don't intercept if not in keyword search mode.
907   if (model()->is_keyword_hint() || model()->keyword().empty()) {
908     return false;
909   }
911   // Don't intercept if there is a selection, or the cursor isn't at
912   // the leftmost position.
913   const NSRange selection = GetSelectedRange();
914   if (selection.length > 0 || selection.location > 0) {
915     return false;
916   }
918   // We're showing a keyword and the user pressed backspace at the
919   // beginning of the text.  Delete the selected keyword.
920   model()->ClearKeyword(GetText());
921   return true;
924 NSRange OmniboxViewMac::SelectionRangeForProposedRange(NSRange proposed_range) {
925   return proposed_range;
928 void OmniboxViewMac::OnControlKeyChanged(bool pressed) {
929   model()->OnControlKeyChanged(pressed);
932 void OmniboxViewMac::FocusLocation(bool select_all) {
933   if ([field_ isEditable]) {
934     // If the text field has a field editor, it's the first responder, meaning
935     // that it's already focused. makeFirstResponder: will select all, so only
936     // call it if this behavior is desired.
937     if (select_all || ![field_ currentEditor])
938       [[field_ window] makeFirstResponder:field_];
939     DCHECK_EQ([field_ currentEditor], [[field_ window] firstResponder]);
940   }
943 // static
944 NSFont* OmniboxViewMac::GetFieldFont() {
945   // This value should be kept in sync with InstantPage::InitializeFonts.
946   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
947   return rb.GetFontList(ui::ResourceBundle::BaseFont).DeriveWithSizeDelta(1)
948       .GetPrimaryFont().GetNativeFont();
951 int OmniboxViewMac::GetOmniboxTextLength() const {
952   return static_cast<int>(GetTextLength());
955 NSUInteger OmniboxViewMac::GetTextLength() const {
956   return [field_ currentEditor] ?  [[[field_ currentEditor] string] length] :
957                                    [[field_ stringValue] length];
960 bool OmniboxViewMac::IsCaretAtEnd() const {
961   const NSRange selection = GetSelectedRange();
962   return NSMaxRange(selection) == GetTextLength();