Add ICU message format support
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / omnibox / omnibox_view_mac.mm
blobb44638c2d1cb23d40a1e288c9d5f6d7b2a758533
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/chrome_autocomplete_scheme_classifier.h"
15 #include "chrome/browser/browser_process.h"
16 #include "chrome/browser/search/search.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/chrome_omnibox_client.h"
21 #include "chrome/browser/ui/toolbar/chrome_toolbar_model.h"
22 #include "chrome/grit/generated_resources.h"
23 #include "components/omnibox/browser/autocomplete_input.h"
24 #include "components/omnibox/browser/autocomplete_match.h"
25 #include "components/omnibox/browser/omnibox_edit_controller.h"
26 #include "components/omnibox/browser/omnibox_field_trial.h"
27 #include "components/omnibox/browser/omnibox_popup_model.h"
28 #include "content/public/browser/web_contents.h"
29 #include "extensions/common/constants.h"
30 #import "third_party/mozilla/NSPasteboard+Utils.h"
31 #include "ui/base/clipboard/clipboard.h"
32 #import "ui/base/cocoa/cocoa_base_utils.h"
33 #include "ui/base/resource/resource_bundle.h"
34 #include "ui/gfx/font.h"
35 #include "ui/gfx/font_list.h"
36 #include "ui/gfx/geometry/rect.h"
38 using content::WebContents;
40 // Focus-handling between |field_| and model() is a bit subtle.
41 // Other platforms detect change of focus, which is inconvenient
42 // without subclassing NSTextField (even with a subclass, the use of a
43 // field editor may complicate things).
45 // model() doesn't actually do anything when it gains focus, it just
46 // initializes.  Visible activity happens only after the user edits.
47 // NSTextField delegate receives messages around starting and ending
48 // edits, so that suffices to catch focus changes.  Since all calls
49 // into model() start from OmniboxViewMac, in the worst case
50 // we can add code to sync up the sense of focus as needed.
52 // I've added DCHECK(IsFirstResponder()) in the places which I believe
53 // should only be reachable when |field_| is being edited.  If these
54 // fire, it probably means someone unexpected is calling into
55 // model().
57 // Other platforms don't appear to have the sense of "key window" that
58 // Mac does (I believe their fields lose focus when the window loses
59 // focus).  Rather than modifying focus outside the control's edit
60 // scope, when the window resigns key the autocomplete popup is
61 // closed.  model() still believes it has focus, and the popup will
62 // be regenerated on the user's next edit.  That seems to match how
63 // things work on other platforms.
65 namespace {
67 // TODO(shess): This is ugly, find a better way.  Using it right now
68 // so that I can crib from gtk and still be able to see that I'm using
69 // the same values easily.
70 NSColor* ColorWithRGBBytes(int rr, int gg, int bb) {
71   DCHECK_LE(rr, 255);
72   DCHECK_LE(bb, 255);
73   DCHECK_LE(gg, 255);
74   return [NSColor colorWithCalibratedRed:static_cast<float>(rr)/255.0
75                                    green:static_cast<float>(gg)/255.0
76                                     blue:static_cast<float>(bb)/255.0
77                                    alpha:1.0];
80 NSColor* HostTextColor() {
81   return [NSColor blackColor];
83 NSColor* BaseTextColor() {
84   return [NSColor darkGrayColor];
86 NSColor* SecureSchemeColor() {
87   return ColorWithRGBBytes(0x07, 0x95, 0x00);
89 NSColor* SecurityErrorSchemeColor() {
90   return ColorWithRGBBytes(0xa2, 0x00, 0x00);
93 const char kOmniboxViewMacStateKey[] = "OmniboxViewMacState";
95 // Store's the model and view state across tab switches.
96 struct OmniboxViewMacState : public base::SupportsUserData::Data {
97   OmniboxViewMacState(const OmniboxEditModel::State model_state,
98                       const bool has_focus,
99                       const NSRange& selection)
100       : model_state(model_state),
101         has_focus(has_focus),
102         selection(selection) {
103   }
104   ~OmniboxViewMacState() override {}
106   const OmniboxEditModel::State model_state;
107   const bool has_focus;
108   const NSRange selection;
111 // Accessors for storing and getting the state from the tab.
112 void StoreStateToTab(WebContents* tab,
113                      OmniboxViewMacState* state) {
114   tab->SetUserData(kOmniboxViewMacStateKey, state);
116 const OmniboxViewMacState* GetStateFromTab(const WebContents* tab) {
117   return static_cast<OmniboxViewMacState*>(
118       tab->GetUserData(&kOmniboxViewMacStateKey));
121 // Helper to make converting url ranges to NSRange easier to
122 // read.
123 NSRange ComponentToNSRange(const url::Component& component) {
124   return NSMakeRange(static_cast<NSInteger>(component.begin),
125                      static_cast<NSInteger>(component.len));
128 }  // namespace
130 // static
131 NSImage* OmniboxViewMac::ImageForResource(int resource_id) {
132   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
133   return rb.GetNativeImageNamed(resource_id).ToNSImage();
136 // static
137 NSColor* OmniboxViewMac::SuggestTextColor() {
138   return [NSColor colorWithCalibratedWhite:0.0 alpha:0.5];
141 OmniboxViewMac::OmniboxViewMac(OmniboxEditController* controller,
142                                Profile* profile,
143                                CommandUpdater* command_updater,
144                                AutocompleteTextField* field)
145     : OmniboxView(
146           controller,
147           make_scoped_ptr(new ChromeOmniboxClient(controller, profile))),
148       profile_(profile),
149       popup_view_(new OmniboxPopupViewMac(this, model(), field)),
150       field_(field),
151       saved_temporary_selection_(NSMakeRange(0, 0)),
152       selection_before_change_(NSMakeRange(0, 0)),
153       marked_range_before_change_(NSMakeRange(0, 0)),
154       delete_was_pressed_(false),
155       delete_at_end_pressed_(false),
156       in_coalesced_update_block_(false),
157       do_coalesced_text_update_(false),
158       do_coalesced_range_update_(false) {
159   [field_ setObserver:this];
161   // Needed so that editing doesn't lose the styling.
162   [field_ setAllowsEditingTextAttributes:YES];
164   // Get the appropriate line height for the font that we use.
165   base::scoped_nsobject<NSLayoutManager> layoutManager(
166       [[NSLayoutManager alloc] init]);
167   [layoutManager setUsesScreenFonts:YES];
170 OmniboxViewMac::~OmniboxViewMac() {
171   // Destroy popup view before this object in case it tries to call us
172   // back in the destructor.
173   popup_view_.reset();
175   // Disconnect from |field_|, it outlives this object.
176   [field_ setObserver:NULL];
179 void OmniboxViewMac::SaveStateToTab(WebContents* tab) {
180   DCHECK(tab);
182   const bool hasFocus = [field_ currentEditor] ? true : false;
184   NSRange range;
185   if (hasFocus) {
186     range = GetSelectedRange();
187   } else {
188     // If we are not focused, there is no selection.  Manufacture
189     // something reasonable in case it starts to matter in the future.
190     range = NSMakeRange(0, GetTextLength());
191   }
193   OmniboxViewMacState* state =
194       new OmniboxViewMacState(model()->GetStateForTabSwitch(), hasFocus, range);
195   StoreStateToTab(tab, state);
198 void OmniboxViewMac::OnTabChanged(const WebContents* web_contents) {
199   const OmniboxViewMacState* state = GetStateFromTab(web_contents);
200   model()->RestoreState(state ? &state->model_state : NULL);
201   // Restore focus and selection if they were present when the tab
202   // was switched away.
203   if (state && state->has_focus) {
204     // TODO(shess): Unfortunately, there is no safe way to update
205     // this because TabStripController -selectTabWithContents:* is
206     // also messing with focus.  Both parties need to agree to
207     // store existing state before anyone tries to setup the new
208     // state.  Anyhow, it would look something like this.
209 #if 0
210     [[field_ window] makeFirstResponder:field_];
211     [[field_ currentEditor] setSelectedRange:state->selection];
212 #endif
213   }
216 void OmniboxViewMac::ResetTabState(WebContents* web_contents) {
217   StoreStateToTab(web_contents, nullptr);
220 void OmniboxViewMac::Update() {
221   if (model()->UpdatePermanentText()) {
222     // Something visibly changed.  Re-enable URL replacement.
223     controller()->GetToolbarModel()->set_url_replacement_enabled(true);
224     model()->UpdatePermanentText();
226     const bool was_select_all = IsSelectAll();
227     NSTextView* text_view =
228         base::mac::ObjCCastStrict<NSTextView>([field_ currentEditor]);
229     const bool was_reversed =
230         [text_view selectionAffinity] == NSSelectionAffinityUpstream;
232     // Restore everything to the baseline look.
233     RevertAll();
235     // Only select all when we have focus.  If we don't have focus, selecting
236     // all is unnecessary since the selection will change on regaining focus,
237     // and can in fact cause artifacts, e.g. if the user is on the NTP and
238     // clicks a link to navigate, causing |was_select_all| to be vacuously true
239     // for the empty omnibox, and we then select all here, leading to the
240     // trailing portion of a long URL being scrolled into view.  We could try
241     // and address cases like this, but it seems better to just not muck with
242     // things when the omnibox isn't focused to begin with.
243     if (was_select_all && model()->has_focus())
244       SelectAll(was_reversed);
245   } else {
246     // TODO(shess): This corresponds to _win and _gtk, except those
247     // guard it with a test for whether the security level changed.
248     // But AFAICT, that can only change if the text changed, and that
249     // code compares the toolbar model security level with the local
250     // security level.  Dig in and figure out why this isn't a no-op
251     // that should go away.
252     EmphasizeURLComponents();
253   }
256 void OmniboxViewMac::OpenMatch(const AutocompleteMatch& match,
257                                WindowOpenDisposition disposition,
258                                const GURL& alternate_nav_url,
259                                const base::string16& pasted_text,
260                                size_t selected_line) {
261   // Coalesce text and selection updates from the following function. If we
262   // don't do this, the user may see intermediate states as brief flickers.
263   in_coalesced_update_block_ = true;
264   OmniboxView::OpenMatch(
265       match, disposition, alternate_nav_url, pasted_text, selected_line);
266   in_coalesced_update_block_ = false;
267   if (do_coalesced_text_update_)
268     SetText(coalesced_text_update_);
269   do_coalesced_text_update_ = false;
270   if (do_coalesced_range_update_)
271     SetSelectedRange(coalesced_range_update_);
272   do_coalesced_range_update_ = false;
275 base::string16 OmniboxViewMac::GetText() const {
276   return base::SysNSStringToUTF16([field_ stringValue]);
279 NSRange OmniboxViewMac::GetSelectedRange() const {
280   return [[field_ currentEditor] selectedRange];
283 NSRange OmniboxViewMac::GetMarkedRange() const {
284   DCHECK([field_ currentEditor]);
285   return [(NSTextView*)[field_ currentEditor] markedRange];
288 void OmniboxViewMac::SetSelectedRange(const NSRange range) {
289   if (in_coalesced_update_block_) {
290     do_coalesced_range_update_ = true;
291     coalesced_range_update_ = range;
292     return;
293   }
295   // This can be called when we don't have focus.  For instance, when
296   // the user clicks the "Go" button.
297   if (model()->has_focus()) {
298     // TODO(shess): If model() thinks we have focus, this should not
299     // be necessary.  Try to convert to DCHECK(IsFirstResponder()).
300     if (![field_ currentEditor]) {
301       [[field_ window] makeFirstResponder:field_];
302     }
304     // TODO(shess): What if it didn't get first responder, and there is
305     // no field editor?  This will do nothing.  Well, at least it won't
306     // crash.  Think of something more productive to do, or prove that
307     // it cannot occur and DCHECK appropriately.
308     [[field_ currentEditor] setSelectedRange:range];
309   }
312 void OmniboxViewMac::SetWindowTextAndCaretPos(const base::string16& text,
313                                               size_t caret_pos,
314                                               bool update_popup,
315                                               bool notify_text_changed) {
316   DCHECK_LE(caret_pos, text.size());
317   SetTextAndSelectedRange(text, NSMakeRange(caret_pos, 0));
319   if (update_popup)
320     UpdatePopup();
322   if (notify_text_changed)
323     TextChanged();
326 void OmniboxViewMac::SetForcedQuery() {
327   // We need to do this first, else |SetSelectedRange()| won't work.
328   FocusLocation(true);
330   const base::string16 current_text(GetText());
331   const size_t start = current_text.find_first_not_of(base::kWhitespaceUTF16);
332   if (start == base::string16::npos || (current_text[start] != '?')) {
333     SetUserText(base::ASCIIToUTF16("?"));
334   } else {
335     NSRange range = NSMakeRange(start + 1, current_text.size() - start - 1);
336     [[field_ currentEditor] setSelectedRange:range];
337   }
340 bool OmniboxViewMac::IsSelectAll() const {
341   if (![field_ currentEditor])
342     return true;
343   const NSRange all_range = NSMakeRange(0, GetTextLength());
344   return NSEqualRanges(all_range, GetSelectedRange());
347 bool OmniboxViewMac::DeleteAtEndPressed() {
348   return delete_at_end_pressed_;
351 void OmniboxViewMac::GetSelectionBounds(base::string16::size_type* start,
352                                         base::string16::size_type* end) const {
353   if (![field_ currentEditor]) {
354     *start = *end = 0;
355     return;
356   }
358   const NSRange selected_range = GetSelectedRange();
359   *start = static_cast<size_t>(selected_range.location);
360   *end = static_cast<size_t>(NSMaxRange(selected_range));
363 void OmniboxViewMac::SelectAll(bool reversed) {
364   DCHECK(!in_coalesced_update_block_);
365   if (!model()->has_focus())
366     return;
368   NSTextView* text_view =
369       base::mac::ObjCCastStrict<NSTextView>([field_ currentEditor]);
370   NSSelectionAffinity affinity =
371       reversed ? NSSelectionAffinityUpstream : NSSelectionAffinityDownstream;
372   NSRange range = NSMakeRange(0, GetTextLength());
374   [text_view setSelectedRange:range affinity:affinity stillSelecting:NO];
377 void OmniboxViewMac::RevertAll() {
378   OmniboxView::RevertAll();
379   [field_ clearUndoChain];
382 void OmniboxViewMac::UpdatePopup() {
383   model()->SetInputInProgress(true);
384   if (!model()->has_focus())
385     return;
387   // Comment copied from OmniboxViewWin::UpdatePopup():
388   // Don't inline autocomplete when:
389   //   * The user is deleting text
390   //   * The caret/selection isn't at the end of the text
391   //   * The user has just pasted in something that replaced all the text
392   //   * The user is trying to compose something in an IME
393   bool prevent_inline_autocomplete = IsImeComposing();
394   NSTextView* editor = (NSTextView*)[field_ currentEditor];
395   if (editor) {
396     if (NSMaxRange([editor selectedRange]) < [[editor textStorage] length])
397       prevent_inline_autocomplete = true;
398   }
400   model()->StartAutocomplete([editor selectedRange].length != 0,
401                             prevent_inline_autocomplete, false);
404 void OmniboxViewMac::CloseOmniboxPopup() {
405   // Call both base class methods.
406   ClosePopup();
407   OmniboxView::CloseOmniboxPopup();
410 void OmniboxViewMac::SetFocus() {
411   FocusLocation(false);
412   model()->SetCaretVisibility(true);
415 void OmniboxViewMac::ApplyCaretVisibility() {
416   [[field_ cell] setHideFocusState:!model()->is_caret_visible()
417                             ofView:field_];
420 void OmniboxViewMac::SetText(const base::string16& display_text) {
421   SetTextInternal(display_text);
424 void OmniboxViewMac::SetTextInternal(const base::string16& display_text) {
425   if (in_coalesced_update_block_) {
426     do_coalesced_text_update_ = true;
427     coalesced_text_update_ = display_text;
428     // Don't do any selection changes, since they apply to the previous text.
429     do_coalesced_range_update_ = false;
430     return;
431   }
433   NSString* ss = base::SysUTF16ToNSString(display_text);
434   NSMutableAttributedString* attributedString =
435       [[[NSMutableAttributedString alloc] initWithString:ss] autorelease];
437   ApplyTextAttributes(display_text, attributedString);
438   [field_ setAttributedStringValue:attributedString];
440   // TODO(shess): This may be an appropriate place to call:
441   //   model()->OnChanged();
442   // In the current implementation, this tells LocationBarViewMac to
443   // mess around with model() and update |field_|.  Unfortunately,
444   // when I look at our peer implementations, it's not entirely clear
445   // to me if this is safe.  SetTextInternal() is sort of an utility method,
446   // and different callers sometimes have different needs.  Research
447   // this issue so that it can be added safely.
449   // TODO(shess): Also, consider whether this code couldn't just
450   // manage things directly.  Windows uses a series of overlaid view
451   // objects to accomplish the hinting stuff that OnChanged() does, so
452   // it makes sense to have it in the controller that lays those
453   // things out.  Mac instead pushes the support into a custom
454   // text-field implementation.
457 void OmniboxViewMac::SetTextAndSelectedRange(const base::string16& display_text,
458                                              const NSRange range) {
459   SetText(display_text);
460   SetSelectedRange(range);
463 void OmniboxViewMac::EmphasizeURLComponents() {
464   NSTextView* editor = (NSTextView*)[field_ currentEditor];
465   // If the autocomplete text field is in editing mode, then we can just change
466   // its attributes through its editor. Otherwise, we simply reset its content.
467   if (editor) {
468     NSTextStorage* storage = [editor textStorage];
469     [storage beginEditing];
471     // Clear the existing attributes from the text storage, then
472     // overlay the appropriate Omnibox attributes.
473     [storage setAttributes:[NSDictionary dictionary]
474                      range:NSMakeRange(0, [storage length])];
475     ApplyTextAttributes(GetText(), storage);
477     [storage endEditing];
479     // This function can be called during the editor's -resignFirstResponder. If
480     // that happens, |storage| and |field_| will not be synced automatically any
481     // more. Calling -stringValue ensures that |field_| reflects the changes to
482     // |storage|.
483     [field_ stringValue];
484   } else {
485     SetText(GetText());
486   }
489 void OmniboxViewMac::ApplyTextStyle(
490     NSMutableAttributedString* attributedString) {
491   [attributedString addAttribute:NSFontAttributeName
492                            value:GetFieldFont(gfx::Font::NORMAL)
493                            range:NSMakeRange(0, [attributedString length])];
495   // Make a paragraph style locking in the standard line height as the maximum,
496   // otherwise the baseline may shift "downwards".
497   base::scoped_nsobject<NSMutableParagraphStyle> paragraph_style(
498       [[NSMutableParagraphStyle alloc] init]);
499   CGFloat line_height = [[field_ cell] lineHeight];
500   [paragraph_style setMaximumLineHeight:line_height];
501   [paragraph_style setMinimumLineHeight:line_height];
502   [paragraph_style setLineBreakMode:NSLineBreakByTruncatingTail];
503   [attributedString addAttribute:NSParagraphStyleAttributeName
504                            value:paragraph_style
505                            range:NSMakeRange(0, [attributedString length])];
508 void OmniboxViewMac::ApplyTextAttributes(
509     const base::string16& display_text,
510     NSMutableAttributedString* attributedString) {
511   NSUInteger as_length = [attributedString length];
512   NSRange as_entire_string = NSMakeRange(0, as_length);
514   ApplyTextStyle(attributedString);
516   // A kinda hacky way to add breaking at periods. This is what Safari does.
517   // This works for IDNs too, despite the "en_US".
518   [attributedString addAttribute:@"NSLanguage"
519                            value:@"en_US_POSIX"
520                            range:as_entire_string];
522   url::Component scheme, host;
523   AutocompleteInput::ParseForEmphasizeComponents(
524       display_text, ChromeAutocompleteSchemeClassifier(profile_), &scheme,
525       &host);
526   bool grey_out_url = display_text.substr(scheme.begin, scheme.len) ==
527       base::UTF8ToUTF16(extensions::kExtensionScheme);
528   if (model()->CurrentTextIsURL() &&
529       (host.is_nonempty() || grey_out_url)) {
530     [attributedString addAttribute:NSForegroundColorAttributeName
531                              value:BaseTextColor()
532                              range:as_entire_string];
534     if (!grey_out_url) {
535       [attributedString addAttribute:NSForegroundColorAttributeName
536                                value:HostTextColor()
537                                range:ComponentToNSRange(host)];
538     }
539   }
541   ChromeToolbarModel* chrome_toolbar_model =
542       static_cast<ChromeToolbarModel*>(controller()->GetToolbarModel());
543   // TODO(shess): GTK has this as a member var, figure out why.
544   // [Could it be to not change if no change?  If so, I'm guessing
545   // AppKit may already handle that.]
546   const connection_security::SecurityLevel security_level =
547       chrome_toolbar_model->GetSecurityLevel(false);
549   // Emphasize the scheme for security UI display purposes (if necessary).
550   if (!model()->user_input_in_progress() && model()->CurrentTextIsURL() &&
551       scheme.is_nonempty() && (security_level != connection_security::NONE)) {
552     NSColor* color;
553     if (security_level == connection_security::EV_SECURE ||
554         security_level == connection_security::SECURE) {
555       color = SecureSchemeColor();
556     } else if (security_level == connection_security::SECURITY_ERROR) {
557       color = SecurityErrorSchemeColor();
558       // Add a strikethrough through the scheme.
559       [attributedString addAttribute:NSStrikethroughStyleAttributeName
560                  value:[NSNumber numberWithInt:NSUnderlineStyleSingle]
561                  range:ComponentToNSRange(scheme)];
562     } else if (security_level == connection_security::SECURITY_WARNING) {
563       color = BaseTextColor();
564     } else {
565       NOTREACHED();
566       color = BaseTextColor();
567     }
568     [attributedString addAttribute:NSForegroundColorAttributeName
569                              value:color
570                              range:ComponentToNSRange(scheme)];
571   }
574 void OmniboxViewMac::OnTemporaryTextMaybeChanged(
575     const base::string16& display_text,
576     bool save_original_selection,
577     bool notify_text_changed) {
578   if (save_original_selection)
579     saved_temporary_selection_ = GetSelectedRange();
581   SetWindowTextAndCaretPos(display_text, display_text.size(), false, false);
582   if (notify_text_changed)
583     model()->OnChanged();
584   [field_ clearUndoChain];
587 bool OmniboxViewMac::OnInlineAutocompleteTextMaybeChanged(
588     const base::string16& display_text,
589     size_t user_text_length) {
590   // TODO(shess): Make sure that this actually works.  The round trip
591   // to native form and back may mean that it's the same but not the
592   // same.
593   if (display_text == GetText())
594     return false;
596   DCHECK_LE(user_text_length, display_text.size());
597   const NSRange range =
598       NSMakeRange(user_text_length, display_text.size() - user_text_length);
599   SetTextAndSelectedRange(display_text, range);
600   model()->OnChanged();
601   [field_ clearUndoChain];
603   return true;
606 void OmniboxViewMac::OnInlineAutocompleteTextCleared() {
609 void OmniboxViewMac::OnRevertTemporaryText() {
610   SetSelectedRange(saved_temporary_selection_);
611   // We got here because the user hit the Escape key. We explicitly don't call
612   // TextChanged(), since OmniboxPopupModel::ResetToDefaultMatch() has already
613   // been called by now, and it would've called TextChanged() if it was
614   // warranted.
617 bool OmniboxViewMac::IsFirstResponder() const {
618   return [field_ currentEditor] != nil ? true : false;
621 void OmniboxViewMac::OnBeforePossibleChange() {
622   // We should only arrive here when the field is focused.
623   DCHECK(IsFirstResponder());
625   selection_before_change_ = GetSelectedRange();
626   text_before_change_ = GetText();
627   marked_range_before_change_ = GetMarkedRange();
630 bool OmniboxViewMac::OnAfterPossibleChange() {
631   // We should only arrive here when the field is focused.
632   DCHECK(IsFirstResponder());
634   const NSRange new_selection(GetSelectedRange());
635   const base::string16 new_text(GetText());
636   const size_t length = new_text.length();
638   const bool selection_differs =
639       (new_selection.length || selection_before_change_.length) &&
640       !NSEqualRanges(new_selection, selection_before_change_);
641   const bool at_end_of_edit = (length == new_selection.location);
642   const bool text_differs = (new_text != text_before_change_) ||
643       !NSEqualRanges(marked_range_before_change_, GetMarkedRange());
645   // When the user has deleted text, we don't allow inline
646   // autocomplete.  This is assumed if the text has gotten shorter AND
647   // the selection has shifted towards the front of the text.  During
648   // normal typing the text will almost always be shorter (as the new
649   // input replaces the autocomplete suggestion), but in that case the
650   // selection point will have moved towards the end of the text.
651   // TODO(shess): In our implementation, we can catch -deleteBackward:
652   // and other methods to provide positive knowledge that a delete
653   // occurred, rather than intuiting it from context.  Consider whether
654   // that would be a stronger approach.
655   const bool just_deleted_text =
656       (length < text_before_change_.length() &&
657        new_selection.location <= selection_before_change_.location);
659   delete_at_end_pressed_ = false;
661   const bool something_changed = model()->OnAfterPossibleChange(
662       text_before_change_, new_text, new_selection.location,
663       NSMaxRange(new_selection), selection_differs, text_differs,
664       just_deleted_text, !IsImeComposing());
666   if (delete_was_pressed_ && at_end_of_edit)
667     delete_at_end_pressed_ = true;
669   // Restyle in case the user changed something.
670   // TODO(shess): I believe there are multiple-redraw cases, here.
671   // Linux watches for something_changed && text_differs, but that
672   // fails for us in case you copy the URL and paste the identical URL
673   // back (we'll lose the styling).
674   TextChanged();
676   delete_was_pressed_ = false;
678   return something_changed;
681 gfx::NativeView OmniboxViewMac::GetNativeView() const {
682   return field_;
685 gfx::NativeView OmniboxViewMac::GetRelativeWindowForPopup() const {
686   // Not used on mac.
687   NOTREACHED();
688   return NULL;
691 void OmniboxViewMac::SetGrayTextAutocompletion(
692     const base::string16& suggest_text) {
693   if (suggest_text == suggest_text_)
694     return;
695   suggest_text_ = suggest_text;
696   [field_ setGrayTextAutocompletion:base::SysUTF16ToNSString(suggest_text)
697                           textColor:SuggestTextColor()];
700 base::string16 OmniboxViewMac::GetGrayTextAutocompletion() const {
701   return suggest_text_;
704 int OmniboxViewMac::GetTextWidth() const {
705   // Not used on mac.
706   NOTREACHED();
707   return 0;
710 int OmniboxViewMac::GetWidth() const {
711   return ceil([field_ bounds].size.width);
714 bool OmniboxViewMac::IsImeComposing() const {
715   return [(NSTextView*)[field_ currentEditor] hasMarkedText];
718 void OmniboxViewMac::OnDidBeginEditing() {
719   // We should only arrive here when the field is focused.
720   DCHECK([field_ currentEditor]);
723 void OmniboxViewMac::OnBeforeChange() {
724   // Capture the current state.
725   OnBeforePossibleChange();
728 void OmniboxViewMac::OnDidChange() {
729   // Figure out what changed and notify the model.
730   OnAfterPossibleChange();
733 void OmniboxViewMac::OnDidEndEditing() {
734   ClosePopup();
737 void OmniboxViewMac::OnInsertText() {
738   // If |insert_char_time_| is not null, there's a pending insert char operation
739   // that hasn't been painted yet. Keep the earlier time.
740   if (insert_char_time_.is_null())
741     insert_char_time_ = base::TimeTicks::Now();
744 void OmniboxViewMac::OnDidDrawRect() {
745   if (!insert_char_time_.is_null()) {
746     UMA_HISTOGRAM_TIMES("Omnibox.CharTypedToRepaintLatency",
747                         base::TimeTicks::Now() - insert_char_time_);
748     insert_char_time_ = base::TimeTicks();
749   }
752 bool OmniboxViewMac::OnDoCommandBySelector(SEL cmd) {
753   if (cmd == @selector(deleteForward:))
754     delete_was_pressed_ = true;
756   if (cmd == @selector(moveDown:)) {
757     model()->OnUpOrDownKeyPressed(1);
758     return true;
759   }
761   if (cmd == @selector(moveUp:)) {
762     model()->OnUpOrDownKeyPressed(-1);
763     return true;
764   }
766   if (model()->popup_model()->IsOpen()) {
767     if (cmd == @selector(insertBacktab:)) {
768       if (model()->popup_model()->selected_line_state() ==
769             OmniboxPopupModel::KEYWORD) {
770         model()->ClearKeyword();
771         return true;
772       } else {
773         model()->OnUpOrDownKeyPressed(-1);
774         return true;
775       }
776     }
778     if ((cmd == @selector(insertTab:) ||
779         cmd == @selector(insertTabIgnoringFieldEditor:)) &&
780         !model()->is_keyword_hint()) {
781       model()->OnUpOrDownKeyPressed(1);
782       return true;
783     }
784   }
786   if (cmd == @selector(moveRight:)) {
787     // Only commit suggested text if the cursor is all the way to the right and
788     // there is no selection.
789     if (suggest_text_.length() > 0 && IsCaretAtEnd()) {
790       model()->CommitSuggestedText();
791       return true;
792     }
793   }
795   if (cmd == @selector(scrollPageDown:)) {
796     model()->OnUpOrDownKeyPressed(model()->result().size());
797     return true;
798   }
800   if (cmd == @selector(scrollPageUp:)) {
801     model()->OnUpOrDownKeyPressed(-model()->result().size());
802     return true;
803   }
805   if (cmd == @selector(cancelOperation:)) {
806     return model()->OnEscapeKeyPressed();
807   }
809   if ((cmd == @selector(insertTab:) ||
810       cmd == @selector(insertTabIgnoringFieldEditor:)) &&
811       model()->is_keyword_hint()) {
812     return model()->AcceptKeyword(ENTERED_KEYWORD_MODE_VIA_TAB);
813   }
815   // |-noop:| is sent when the user presses Cmd+Return. Override the no-op
816   // behavior with the proper WindowOpenDisposition.
817   NSEvent* event = [NSApp currentEvent];
818   if (cmd == @selector(insertNewline:) ||
819      (cmd == @selector(noop:) &&
820       ([event type] == NSKeyDown || [event type] == NSKeyUp) &&
821       [event keyCode] == kVK_Return)) {
822     WindowOpenDisposition disposition =
823         ui::WindowOpenDispositionFromNSEvent(event);
824     model()->AcceptInput(disposition, false);
825     // Opening a URL in a background tab should also revert the omnibox contents
826     // to their original state.  We cannot do a blanket revert in OpenURL()
827     // because middle-clicks also open in a new background tab, but those should
828     // not revert the omnibox text.
829     RevertAll();
830     return true;
831   }
833   // Option-Return
834   if (cmd == @selector(insertNewlineIgnoringFieldEditor:)) {
835     model()->AcceptInput(NEW_FOREGROUND_TAB, false);
836     return true;
837   }
839   // When the user does Control-Enter, the existing content has "www."
840   // prepended and ".com" appended.  model() should already have
841   // received notification when the Control key was depressed, but it
842   // is safe to tell it twice.
843   if (cmd == @selector(insertLineBreak:)) {
844     OnControlKeyChanged(true);
845     WindowOpenDisposition disposition =
846         ui::WindowOpenDispositionFromNSEvent([NSApp currentEvent]);
847     model()->AcceptInput(disposition, false);
848     return true;
849   }
851   if (cmd == @selector(deleteBackward:)) {
852     if (OnBackspacePressed()) {
853       return true;
854     }
855   }
857   if (cmd == @selector(deleteForward:)) {
858     const NSUInteger modifiers = [[NSApp currentEvent] modifierFlags];
859     if ((modifiers & NSShiftKeyMask) != 0) {
860       if (model()->popup_model()->IsOpen()) {
861         model()->popup_model()->TryDeletingCurrentItem();
862         return true;
863       }
864     }
865   }
867   return false;
870 void OmniboxViewMac::OnSetFocus(bool control_down) {
871   model()->OnSetFocus(control_down);
872   controller()->OnSetFocus();
875 void OmniboxViewMac::OnKillFocus() {
876   // Tell the model to reset itself.
877   model()->OnWillKillFocus();
878   model()->OnKillFocus();
881 void OmniboxViewMac::OnMouseDown(NSInteger button_number) {
882   // Restore caret visibility whenever the user clicks in the the omnibox. This
883   // is not always covered by OnSetFocus() because when clicking while the
884   // omnibox has invisible focus does not trigger a new OnSetFocus() call.
885   if (button_number == 0 || button_number == 1)
886     model()->SetCaretVisibility(true);
889 bool OmniboxViewMac::ShouldSelectAllOnMouseDown() {
890   return !controller()->GetToolbarModel()->WouldPerformSearchTermReplacement(
891       false);
894 bool OmniboxViewMac::CanCopy() {
895   const NSRange selection = GetSelectedRange();
896   return selection.length > 0;
899 void OmniboxViewMac::CopyToPasteboard(NSPasteboard* pb) {
900   DCHECK(CanCopy());
902   const NSRange selection = GetSelectedRange();
903   base::string16 text = base::SysNSStringToUTF16(
904       [[field_ stringValue] substringWithRange:selection]);
906   // Copy the URL unless this is the search URL and it's being replaced by the
907   // Extended Instant API.
908   GURL url;
909   bool write_url = false;
910   if (!controller()->GetToolbarModel()->WouldPerformSearchTermReplacement(
911       false)) {
912     model()->AdjustTextForCopy(selection.location, IsSelectAll(), &text, &url,
913                                &write_url);
914   }
916   if (IsSelectAll())
917     UMA_HISTOGRAM_COUNTS(OmniboxEditModel::kCutOrCopyAllTextHistogram, 1);
919   NSString* nstext = base::SysUTF16ToNSString(text);
920   [pb declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
921   [pb setString:nstext forType:NSStringPboardType];
923   if (write_url) {
924     [pb declareURLPasteboardWithAdditionalTypes:[NSArray array] owner:nil];
925     [pb setDataForURL:base::SysUTF8ToNSString(url.spec()) title:nstext];
926   }
929 void OmniboxViewMac::ShowURL() {
930   DCHECK(ShouldEnableShowURL());
931   OmniboxView::ShowURL();
934 void OmniboxViewMac::OnPaste() {
935   // This code currently expects |field_| to be focused.
936   DCHECK([field_ currentEditor]);
938   base::string16 text = GetClipboardText();
939   if (text.empty()) {
940     return;
941   }
942   NSString* s = base::SysUTF16ToNSString(text);
944   // -shouldChangeTextInRange:* and -didChangeText are documented in
945   // NSTextView as things you need to do if you write additional
946   // user-initiated editing functions.  They cause the appropriate
947   // delegate methods to be called.
948   // TODO(shess): It would be nice to separate the Cocoa-specific code
949   // from the Chrome-specific code.
950   NSTextView* editor = static_cast<NSTextView*>([field_ currentEditor]);
951   const NSRange selectedRange = GetSelectedRange();
952   if ([editor shouldChangeTextInRange:selectedRange replacementString:s]) {
953     // Record this paste, so we can do different behavior.
954     model()->OnPaste();
956     // Force a Paste operation to trigger the text_changed code in
957     // OnAfterPossibleChange(), even if identical contents are pasted
958     // into the text box.
959     text_before_change_.clear();
961     [editor replaceCharactersInRange:selectedRange withString:s];
962     [editor didChangeText];
963   }
966 // TODO(dominich): Move to OmniboxView base class? Currently this is defined on
967 // the AutocompleteTextFieldObserver but the logic is shared between all
968 // platforms. Some refactor might be necessary to simplify this. Or at least
969 // this method could call the OmniboxView version.
970 bool OmniboxViewMac::ShouldEnableShowURL() {
971   return controller()->GetToolbarModel()->WouldReplaceURL();
974 bool OmniboxViewMac::CanPasteAndGo() {
975   return model()->CanPasteAndGo(GetClipboardText());
978 int OmniboxViewMac::GetPasteActionStringId() {
979   base::string16 text(GetClipboardText());
980   DCHECK(model()->CanPasteAndGo(text));
981   return model()->IsPasteAndSearch(text) ?
982       IDS_PASTE_AND_SEARCH : IDS_PASTE_AND_GO;
985 void OmniboxViewMac::OnPasteAndGo() {
986   base::string16 text(GetClipboardText());
987   if (model()->CanPasteAndGo(text))
988     model()->PasteAndGo(text);
991 void OmniboxViewMac::OnFrameChanged() {
992   // TODO(shess): UpdatePopupAppearance() is called frequently, so it
993   // should be really cheap, but in this case we could probably make
994   // things even cheaper by refactoring between the popup-placement
995   // code and the matrix-population code.
996   popup_view_->UpdatePopupAppearance();
998   // Give controller a chance to rearrange decorations.
999   model()->OnChanged();
1002 void OmniboxViewMac::ClosePopup() {
1003   OmniboxView::CloseOmniboxPopup();
1006 bool OmniboxViewMac::OnBackspacePressed() {
1007   // Don't intercept if not in keyword search mode.
1008   if (model()->is_keyword_hint() || model()->keyword().empty()) {
1009     return false;
1010   }
1012   // Don't intercept if there is a selection, or the cursor isn't at
1013   // the leftmost position.
1014   const NSRange selection = GetSelectedRange();
1015   if (selection.length > 0 || selection.location > 0) {
1016     return false;
1017   }
1019   // We're showing a keyword and the user pressed backspace at the
1020   // beginning of the text.  Delete the selected keyword.
1021   model()->ClearKeyword();
1022   return true;
1025 NSRange OmniboxViewMac::SelectionRangeForProposedRange(NSRange proposed_range) {
1026   return proposed_range;
1029 void OmniboxViewMac::OnControlKeyChanged(bool pressed) {
1030   model()->OnControlKeyChanged(pressed);
1033 void OmniboxViewMac::FocusLocation(bool select_all) {
1034   if ([field_ isEditable]) {
1035     // If the text field has a field editor, it's the first responder, meaning
1036     // that it's already focused. makeFirstResponder: will select all, so only
1037     // call it if this behavior is desired.
1038     if (select_all || ![field_ currentEditor])
1039       [[field_ window] makeFirstResponder:field_];
1040     DCHECK_EQ([field_ currentEditor], [[field_ window] firstResponder]);
1041   }
1044 // static
1045 NSFont* OmniboxViewMac::GetFieldFont(int style) {
1046   // This value should be kept in sync with InstantPage::InitializeFonts.
1047   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
1048   return rb.GetFontList(ui::ResourceBundle::BaseFont).Derive(1, style)
1049       .GetPrimaryFont().GetNativeFont();
1052 NSFont* OmniboxViewMac::GetLargeFont(int style) {
1053   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
1054   return rb.GetFontList(ui::ResourceBundle::LargeFont)
1055       .Derive(1, style)
1056       .GetPrimaryFont()
1057       .GetNativeFont();
1060 NSFont* OmniboxViewMac::GetSmallFont(int style) {
1061   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
1062   return rb.GetFontList(ui::ResourceBundle::SmallFont)
1063       .Derive(1, style)
1064       .GetPrimaryFont()
1065       .GetNativeFont();
1068 int OmniboxViewMac::GetOmniboxTextLength() const {
1069   return static_cast<int>(GetTextLength());
1072 NSUInteger OmniboxViewMac::GetTextLength() const {
1073   return [field_ currentEditor] ?  [[[field_ currentEditor] string] length] :
1074                                    [[field_ stringValue] length];
1077 bool OmniboxViewMac::IsCaretAtEnd() const {
1078   const NSRange selection = GetSelectedRange();
1079   return NSMaxRange(selection) == GetTextLength();