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