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/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"
37 using content::WebContents;
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).
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.
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
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.
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) {
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
79 NSColor* HostTextColor() {
80 return [NSColor blackColor];
82 NSColor* BaseTextColor() {
83 return [NSColor darkGrayColor];
85 NSColor* SecureSchemeColor() {
86 return ColorWithRGBBytes(0x07, 0x95, 0x00);
88 NSColor* SecurityErrorSchemeColor() {
89 return ColorWithRGBBytes(0xa2, 0x00, 0x00);
92 const char kOmniboxViewMacStateKey[] = "OmniboxViewMacState";
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,
98 const NSRange& selection)
99 : model_state(model_state),
100 has_focus(has_focus),
101 selection(selection) {
103 ~OmniboxViewMacState() override {}
105 const OmniboxEditModel::State model_state;
106 const bool has_focus;
107 const NSRange selection;
110 // Accessors for storing and getting the state from the tab.
111 void StoreStateToTab(WebContents* tab,
112 OmniboxViewMacState* state) {
113 tab->SetUserData(kOmniboxViewMacStateKey, state);
115 const OmniboxViewMacState* GetStateFromTab(const WebContents* tab) {
116 return static_cast<OmniboxViewMacState*>(
117 tab->GetUserData(&kOmniboxViewMacStateKey));
120 // Helper to make converting url ranges to NSRange easier to
122 NSRange ComponentToNSRange(const url::Component& component) {
123 return NSMakeRange(static_cast<NSInteger>(component.begin),
124 static_cast<NSInteger>(component.len));
130 NSImage* OmniboxViewMac::ImageForResource(int resource_id) {
131 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
132 return rb.GetNativeImageNamed(resource_id).ToNSImage();
136 NSColor* OmniboxViewMac::SuggestTextColor() {
137 return [NSColor colorWithCalibratedWhite:0.0 alpha:0.5];
140 OmniboxViewMac::OmniboxViewMac(OmniboxEditController* controller,
142 CommandUpdater* command_updater,
143 AutocompleteTextField* field)
144 : OmniboxView(profile, controller, command_updater),
145 popup_view_(new OmniboxPopupViewMac(this, model(), 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];
157 // Needed so that editing doesn't lose the styling.
158 [field_ setAllowsEditingTextAttributes:YES];
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];
166 OmniboxViewMac::~OmniboxViewMac() {
167 // Destroy popup view before this object in case it tries to call us
168 // back in the destructor.
171 // Disconnect from |field_|, it outlives this object.
172 [field_ setObserver:NULL];
175 void OmniboxViewMac::SaveStateToTab(WebContents* tab) {
178 const bool hasFocus = [field_ currentEditor] ? true : false;
182 range = GetSelectedRange();
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());
189 OmniboxViewMacState* state =
190 new OmniboxViewMacState(model()->GetStateForTabSwitch(), hasFocus, range);
191 StoreStateToTab(tab, state);
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.
206 [[field_ window] makeFirstResponder:field_];
207 [[field_ currentEditor] setSelectedRange:state->selection];
212 void OmniboxViewMac::Update() {
213 UpdatePlaceholderText();
215 if (model()->UpdatePermanentText()) {
216 // Something visibly changed. Re-enable URL replacement.
217 controller()->GetToolbarModel()->set_url_replacement_enabled(true);
218 model()->UpdatePermanentText();
220 // Restore everything to the baseline look.
223 // TODO(shess): Figure out how this case is used, to make sure
224 // we're getting the selection and popup right.
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();
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]
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];
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;
270 base::string16 OmniboxViewMac::GetText() const {
271 return base::SysNSStringToUTF16([field_ stringValue]);
274 NSRange OmniboxViewMac::GetSelectedRange() const {
275 return [[field_ currentEditor] selectedRange];
278 NSRange OmniboxViewMac::GetMarkedRange() const {
279 DCHECK([field_ currentEditor]);
280 return [(NSTextView*)[field_ currentEditor] markedRange];
283 void OmniboxViewMac::SetSelectedRange(const NSRange range) {
284 if (in_coalesced_update_block_) {
285 do_coalesced_range_update_ = true;
286 coalesced_range_update_ = range;
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_];
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];
307 void OmniboxViewMac::SetWindowTextAndCaretPos(const base::string16& text,
310 bool notify_text_changed) {
311 DCHECK_LE(caret_pos, text.size());
312 SetTextAndSelectedRange(text, NSMakeRange(caret_pos, 0));
317 if (notify_text_changed)
321 void OmniboxViewMac::SetForcedQuery() {
322 // We need to do this first, else |SetSelectedRange()| won't work.
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("?"));
330 NSRange range = NSMakeRange(start + 1, current_text.size() - start - 1);
331 [[field_ currentEditor] setSelectedRange:range];
335 bool OmniboxViewMac::IsSelectAll() const {
336 if (![field_ currentEditor])
338 const NSRange all_range = NSMakeRange(0, GetTextLength());
339 return NSEqualRanges(all_range, GetSelectedRange());
342 bool OmniboxViewMac::DeleteAtEndPressed() {
343 return delete_at_end_pressed_;
346 void OmniboxViewMac::GetSelectionBounds(base::string16::size_type* start,
347 base::string16::size_type* end) const {
348 if (![field_ currentEditor]) {
353 const NSRange selected_range = GetSelectedRange();
354 *start = static_cast<size_t>(selected_range.location);
355 *end = static_cast<size_t>(NSMaxRange(selected_range));
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.
363 // TODO(shess): Verify that we should be stealing focus at this
365 SetSelectedRange(NSMakeRange(0, GetTextLength()));
368 void OmniboxViewMac::RevertAll() {
369 OmniboxView::RevertAll();
370 [field_ clearUndoChain];
373 void OmniboxViewMac::UpdatePopup() {
374 model()->SetInputInProgress(true);
375 if (!model()->has_focus())
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];
387 if (NSMaxRange([editor selectedRange]) < [[editor textStorage] length])
388 prevent_inline_autocomplete = true;
391 model()->StartAutocomplete([editor selectedRange].length != 0,
392 prevent_inline_autocomplete);
395 void OmniboxViewMac::CloseOmniboxPopup() {
396 // Call both base class methods.
398 OmniboxView::CloseOmniboxPopup();
401 void OmniboxViewMac::SetFocus() {
402 FocusLocation(false);
403 model()->SetCaretVisibility(true);
406 void OmniboxViewMac::ApplyCaretVisibility() {
407 [[field_ cell] setHideFocusState:!model()->is_caret_visible()
411 void OmniboxViewMac::SetText(const base::string16& display_text) {
412 SetTextInternal(display_text);
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;
424 NSString* ss = base::SysUTF16ToNSString(display_text);
425 NSMutableAttributedString* as =
426 [[[NSMutableAttributedString alloc] initWithString:ss] autorelease];
428 ApplyTextAttributes(display_text, as);
429 [field_ setAttributedStringValue:as];
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.
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.
448 void OmniboxViewMac::SetTextAndSelectedRange(const base::string16& display_text,
449 const NSRange range) {
450 SetText(display_text);
451 SetSelectedRange(range);
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.
459 NSTextStorage* storage = [editor textStorage];
460 [storage beginEditing];
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);
468 [storage endEditing];
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
474 [field_ stringValue];
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);
485 [as addAttribute:NSFontAttributeName value:GetFieldFont(gfx::Font::NORMAL)
486 range:as_entire_string];
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];
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];
504 url::Component scheme, host;
505 AutocompleteInput::ParseForEmphasizeComponents(
506 display_text, ChromeAutocompleteSchemeClassifier(profile()),
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];
516 [as addAttribute:NSForegroundColorAttributeName value:HostTextColor()
517 range:ComponentToNSRange(host)];
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);
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)) {
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();
544 color = BaseTextColor();
546 [as addAttribute:NSForegroundColorAttributeName value:color
547 range:ComponentToNSRange(scheme)];
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();
558 SetWindowTextAndCaretPos(display_text, display_text.size(), false, false);
559 if (notify_text_changed)
560 model()->OnChanged();
561 [field_ clearUndoChain];
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
570 if (display_text == GetText())
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];
583 void OmniboxViewMac::OnInlineAutocompleteTextCleared() {
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
594 bool OmniboxViewMac::IsFirstResponder() const {
595 return [field_ currentEditor] != nil ? true : false;
598 void OmniboxViewMac::OnBeforePossibleChange() {
599 // We should only arrive here when the field is focussed.
600 DCHECK(IsFirstResponder());
602 selection_before_change_ = GetSelectedRange();
603 text_before_change_ = GetText();
604 marked_range_before_change_ = GetMarkedRange();
607 bool OmniboxViewMac::OnAfterPossibleChange() {
608 // We should only arrive here when the field is focussed.
609 DCHECK(IsFirstResponder());
611 const NSRange new_selection(GetSelectedRange());
612 const base::string16 new_text(GetText());
613 const size_t length = new_text.length();
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());
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);
636 delete_at_end_pressed_ = false;
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());
643 if (delete_was_pressed_ && at_end_of_edit)
644 delete_at_end_pressed_ = true;
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).
653 delete_was_pressed_ = false;
655 return something_changed;
658 gfx::NativeView OmniboxViewMac::GetNativeView() const {
662 gfx::NativeView OmniboxViewMac::GetRelativeWindowForPopup() const {
668 void OmniboxViewMac::SetGrayTextAutocompletion(
669 const base::string16& suggest_text) {
670 if (suggest_text == suggest_text_)
672 suggest_text_ = suggest_text;
673 [field_ setGrayTextAutocompletion:base::SysUTF16ToNSString(suggest_text)
674 textColor:SuggestTextColor()];
677 base::string16 OmniboxViewMac::GetGrayTextAutocompletion() const {
678 return suggest_text_;
681 int OmniboxViewMac::GetTextWidth() const {
687 int OmniboxViewMac::GetWidth() const {
688 return ceil([field_ bounds].size.width);
691 bool OmniboxViewMac::IsImeComposing() const {
692 return [(NSTextView*)[field_ currentEditor] hasMarkedText];
695 void OmniboxViewMac::OnDidBeginEditing() {
696 // We should only arrive here when the field is focussed.
697 DCHECK([field_ currentEditor]);
700 void OmniboxViewMac::OnBeforeChange() {
701 // Capture the current state.
702 OnBeforePossibleChange();
705 void OmniboxViewMac::OnDidChange() {
706 // Figure out what changed and notify the model.
707 OnAfterPossibleChange();
710 void OmniboxViewMac::OnDidEndEditing() {
714 bool OmniboxViewMac::OnDoCommandBySelector(SEL cmd) {
715 if (cmd == @selector(deleteForward:))
716 delete_was_pressed_ = true;
718 if (cmd == @selector(moveDown:)) {
719 model()->OnUpOrDownKeyPressed(1);
723 if (cmd == @selector(moveUp:)) {
724 model()->OnUpOrDownKeyPressed(-1);
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());
735 model()->OnUpOrDownKeyPressed(-1);
740 if ((cmd == @selector(insertTab:) ||
741 cmd == @selector(insertTabIgnoringFieldEditor:)) &&
742 !model()->is_keyword_hint()) {
743 model()->OnUpOrDownKeyPressed(1);
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();
757 if (cmd == @selector(scrollPageDown:)) {
758 model()->OnUpOrDownKeyPressed(model()->result().size());
762 if (cmd == @selector(scrollPageUp:)) {
763 model()->OnUpOrDownKeyPressed(-model()->result().size());
767 if (cmd == @selector(cancelOperation:)) {
768 return model()->OnEscapeKeyPressed();
771 if ((cmd == @selector(insertTab:) ||
772 cmd == @selector(insertTabIgnoringFieldEditor:)) &&
773 model()->is_keyword_hint()) {
774 return model()->AcceptKeyword(ENTERED_KEYWORD_MODE_VIA_TAB);
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.
796 if (cmd == @selector(insertNewlineIgnoringFieldEditor:)) {
797 model()->AcceptInput(NEW_FOREGROUND_TAB, false);
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);
813 if (cmd == @selector(deleteBackward:)) {
814 if (OnBackspacePressed()) {
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();
832 void OmniboxViewMac::OnSetFocus(bool control_down) {
833 model()->OnSetFocus(control_down);
834 controller()->OnSetFocus();
836 HandleOriginChipMouseRelease();
839 void OmniboxViewMac::OnKillFocus() {
840 // Tell the model to reset itself.
841 model()->OnWillKillFocus(NULL);
842 model()->OnKillFocus();
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);
855 bool OmniboxViewMac::ShouldSelectAllOnMouseDown() {
856 return !controller()->GetToolbarModel()->WouldPerformSearchTermReplacement(
860 bool OmniboxViewMac::CanCopy() {
861 const NSRange selection = GetSelectedRange();
862 return selection.length > 0;
865 void OmniboxViewMac::CopyToPasteboard(NSPasteboard* pb) {
868 const NSRange selection = GetSelectedRange();
869 base::string16 text = base::SysNSStringToUTF16(
870 [[field_ stringValue] substringWithRange:selection]);
872 // Copy the URL unless this is the search URL and it's being replaced by the
873 // Extended Instant API.
875 bool write_url = false;
876 if (!controller()->GetToolbarModel()->WouldPerformSearchTermReplacement(
878 model()->AdjustTextForCopy(selection.location, IsSelectAll(), &text, &url,
883 UMA_HISTOGRAM_COUNTS(OmniboxEditModel::kCutOrCopyAllTextHistogram, 1);
885 NSString* nstext = base::SysUTF16ToNSString(text);
886 [pb declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
887 [pb setString:nstext forType:NSStringPboardType];
890 [pb declareURLPasteboardWithAdditionalTypes:[NSArray array] owner:nil];
891 [pb setDataForURL:base::SysUTF8ToNSString(url.spec()) title:nstext];
895 void OmniboxViewMac::ShowURL() {
896 DCHECK(ShouldEnableShowURL());
897 OmniboxView::ShowURL();
900 void OmniboxViewMac::OnPaste() {
901 // This code currently expects |field_| to be focussed.
902 DCHECK([field_ currentEditor]);
904 base::string16 text = GetClipboardText();
908 NSString* s = base::SysUTF16ToNSString(text);
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.
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();
927 [editor replaceCharactersInRange:selectedRange withString:s];
928 [editor didChangeText];
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();
940 bool OmniboxViewMac::CanPasteAndGo() {
941 return model()->CanPasteAndGo(GetClipboardText());
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;
951 void OmniboxViewMac::OnPasteAndGo() {
952 base::string16 text(GetClipboardText());
953 if (model()->CanPasteAndGo(text))
954 model()->PasteAndGo(text);
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();
964 // Give controller a chance to rearrange decorations.
965 model()->OnChanged();
968 void OmniboxViewMac::ClosePopup() {
969 OmniboxView::CloseOmniboxPopup();
972 bool OmniboxViewMac::OnBackspacePressed() {
973 // Don't intercept if not in keyword search mode.
974 if (model()->is_keyword_hint() || model()->keyword().empty()) {
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) {
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());
991 NSRange OmniboxViewMac::SelectionRangeForProposedRange(NSRange proposed_range) {
992 return proposed_range;
995 void OmniboxViewMac::OnControlKeyChanged(bool pressed) {
996 model()->OnControlKeyChanged(pressed);
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]);
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();
1018 int OmniboxViewMac::GetOmniboxTextLength() const {
1019 return static_cast<int>(GetTextLength());
1022 NSUInteger OmniboxViewMac::GetTextLength() const {
1023 return [field_ currentEditor] ? [[[field_ currentEditor] string] length] :
1024 [[field_ stringValue] length];
1027 bool OmniboxViewMac::IsCaretAtEnd() const {
1028 const NSRange selection = GetSelectedRange();
1029 return NSMaxRange(selection) == GetTextLength();