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