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/strings/string_util.h"
11 #include "base/strings/sys_string_conversions.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "chrome/browser/autocomplete/autocomplete_input.h"
14 #include "chrome/browser/autocomplete/autocomplete_match.h"
15 #include "chrome/browser/browser_process.h"
16 #include "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_cell.h"
17 #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_editor.h"
18 #include "chrome/browser/ui/cocoa/omnibox/omnibox_popup_view_mac.h"
19 #include "chrome/browser/ui/omnibox/omnibox_edit_controller.h"
20 #include "chrome/browser/ui/omnibox/omnibox_popup_model.h"
21 #include "chrome/browser/ui/toolbar/toolbar_model.h"
22 #include "content/public/browser/web_contents.h"
23 #include "extensions/common/constants.h"
24 #include "grit/generated_resources.h"
25 #include "grit/theme_resources.h"
26 #import "third_party/mozilla/NSPasteboard+Utils.h"
27 #include "ui/base/clipboard/clipboard.h"
28 #import "ui/base/cocoa/cocoa_event_utils.h"
29 #include "ui/base/resource/resource_bundle.h"
30 #include "ui/gfx/rect.h"
32 using content::WebContents;
34 // Focus-handling between |field_| and model() is a bit subtle.
35 // Other platforms detect change of focus, which is inconvenient
36 // without subclassing NSTextField (even with a subclass, the use of a
37 // field editor may complicate things).
39 // model() doesn't actually do anything when it gains focus, it just
40 // initializes. Visible activity happens only after the user edits.
41 // NSTextField delegate receives messages around starting and ending
42 // edits, so that suffices to catch focus changes. Since all calls
43 // into model() start from OmniboxViewMac, in the worst case
44 // we can add code to sync up the sense of focus as needed.
46 // I've added DCHECK(IsFirstResponder()) in the places which I believe
47 // should only be reachable when |field_| is being edited. If these
48 // fire, it probably means someone unexpected is calling into
51 // Other platforms don't appear to have the sense of "key window" that
52 // Mac does (I believe their fields lose focus when the window loses
53 // focus). Rather than modifying focus outside the control's edit
54 // scope, when the window resigns key the autocomplete popup is
55 // closed. model() still believes it has focus, and the popup will
56 // be regenerated on the user's next edit. That seems to match how
57 // things work on other platforms.
61 // TODO(shess): This is ugly, find a better way. Using it right now
62 // so that I can crib from gtk and still be able to see that I'm using
63 // the same values easily.
64 NSColor* ColorWithRGBBytes(int rr, int gg, int bb) {
68 return [NSColor colorWithCalibratedRed:static_cast<float>(rr)/255.0
69 green:static_cast<float>(gg)/255.0
70 blue:static_cast<float>(bb)/255.0
74 NSColor* HostTextColor() {
75 return [NSColor blackColor];
77 NSColor* BaseTextColor() {
78 return [NSColor darkGrayColor];
80 NSColor* SecureSchemeColor() {
81 return ColorWithRGBBytes(0x07, 0x95, 0x00);
83 NSColor* SecurityErrorSchemeColor() {
84 return ColorWithRGBBytes(0xa2, 0x00, 0x00);
87 const char kOmniboxViewMacStateKey[] = "OmniboxViewMacState";
89 // Store's the model and view state across tab switches.
90 struct OmniboxViewMacState : public base::SupportsUserData::Data {
91 OmniboxViewMacState(const OmniboxEditModel::State model_state,
93 const NSRange& selection)
94 : model_state(model_state),
96 selection(selection) {
98 virtual ~OmniboxViewMacState() {}
100 const OmniboxEditModel::State model_state;
101 const bool has_focus;
102 const NSRange selection;
105 // Accessors for storing and getting the state from the tab.
106 void StoreStateToTab(WebContents* tab,
107 OmniboxViewMacState* state) {
108 tab->SetUserData(kOmniboxViewMacStateKey, state);
110 const OmniboxViewMacState* GetStateFromTab(const WebContents* tab) {
111 return static_cast<OmniboxViewMacState*>(
112 tab->GetUserData(&kOmniboxViewMacStateKey));
115 // Helper to make converting url_parse ranges to NSRange easier to
117 NSRange ComponentToNSRange(const url_parse::Component& component) {
118 return NSMakeRange(static_cast<NSInteger>(component.begin),
119 static_cast<NSInteger>(component.len));
125 NSImage* OmniboxViewMac::ImageForResource(int resource_id) {
126 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
127 return rb.GetNativeImageNamed(resource_id).ToNSImage();
131 NSColor* OmniboxViewMac::SuggestTextColor() {
132 return [NSColor colorWithCalibratedWhite:0.0 alpha:0.5];
135 OmniboxViewMac::OmniboxViewMac(OmniboxEditController* controller,
137 CommandUpdater* command_updater,
138 AutocompleteTextField* field)
139 : OmniboxView(profile, controller, command_updater),
140 popup_view_(new OmniboxPopupViewMac(this, model(), field)),
142 saved_temporary_selection_(NSMakeRange(0, 0)),
143 selection_before_change_(NSMakeRange(0, 0)),
144 marked_range_before_change_(NSMakeRange(0, 0)),
145 delete_was_pressed_(false),
146 delete_at_end_pressed_(false) {
147 [field_ setObserver:this];
149 // Needed so that editing doesn't lose the styling.
150 [field_ setAllowsEditingTextAttributes:YES];
152 // Get the appropriate line height for the font that we use.
153 base::scoped_nsobject<NSLayoutManager> layoutManager(
154 [[NSLayoutManager alloc] init]);
155 [layoutManager setUsesScreenFonts:YES];
158 OmniboxViewMac::~OmniboxViewMac() {
159 // Destroy popup view before this object in case it tries to call us
160 // back in the destructor.
163 // Disconnect from |field_|, it outlives this object.
164 [field_ setObserver:NULL];
167 void OmniboxViewMac::SaveStateToTab(WebContents* tab) {
170 const bool hasFocus = [field_ currentEditor] ? true : false;
174 range = GetSelectedRange();
176 // If we are not focussed, there is no selection. Manufacture
177 // something reasonable in case it starts to matter in the future.
178 range = NSMakeRange(0, GetTextLength());
181 OmniboxViewMacState* state =
182 new OmniboxViewMacState(model()->GetStateForTabSwitch(), hasFocus, range);
183 StoreStateToTab(tab, state);
186 void OmniboxViewMac::OnTabChanged(const WebContents* web_contents) {
187 const OmniboxViewMacState* state = GetStateFromTab(web_contents);
188 model()->RestoreState(state ? &state->model_state : NULL);
189 // Restore focus and selection if they were present when the tab
190 // was switched away.
191 if (state && state->has_focus) {
192 // TODO(shess): Unfortunately, there is no safe way to update
193 // this because TabStripController -selectTabWithContents:* is
194 // also messing with focus. Both parties need to agree to
195 // store existing state before anyone tries to setup the new
196 // state. Anyhow, it would look something like this.
198 [[field_ window] makeFirstResponder:field_];
199 [[field_ currentEditor] setSelectedRange:state->selection];
204 void OmniboxViewMac::Update() {
205 if (model()->UpdatePermanentText()) {
206 // Something visibly changed. Re-enable search term replacement.
207 controller()->GetToolbarModel()->set_search_term_replacement_enabled(true);
208 model()->UpdatePermanentText();
210 // Restore everything to the baseline look.
213 // TODO(shess): Figure out how this case is used, to make sure
214 // we're getting the selection and popup right.
216 // TODO(shess): This corresponds to _win and _gtk, except those
217 // guard it with a test for whether the security level changed.
218 // But AFAICT, that can only change if the text changed, and that
219 // code compares the toolbar model security level with the local
220 // security level. Dig in and figure out why this isn't a no-op
221 // that should go away.
222 EmphasizeURLComponents();
226 string16 OmniboxViewMac::GetText() const {
227 return base::SysNSStringToUTF16([field_ stringValue]);
230 NSRange OmniboxViewMac::GetSelectedRange() const {
231 return [[field_ currentEditor] selectedRange];
234 NSRange OmniboxViewMac::GetMarkedRange() const {
235 DCHECK([field_ currentEditor]);
236 return [(NSTextView*)[field_ currentEditor] markedRange];
239 void OmniboxViewMac::SetSelectedRange(const NSRange range) {
240 // This can be called when we don't have focus. For instance, when
241 // the user clicks the "Go" button.
242 if (model()->has_focus()) {
243 // TODO(shess): If model() thinks we have focus, this should not
244 // be necessary. Try to convert to DCHECK(IsFirstResponder()).
245 if (![field_ currentEditor]) {
246 [[field_ window] makeFirstResponder:field_];
249 // TODO(shess): What if it didn't get first responder, and there is
250 // no field editor? This will do nothing. Well, at least it won't
251 // crash. Think of something more productive to do, or prove that
252 // it cannot occur and DCHECK appropriately.
253 [[field_ currentEditor] setSelectedRange:range];
257 void OmniboxViewMac::SetWindowTextAndCaretPos(const string16& text,
260 bool notify_text_changed) {
261 DCHECK_LE(caret_pos, text.size());
262 SetTextAndSelectedRange(text, NSMakeRange(caret_pos, 0));
267 if (notify_text_changed)
271 void OmniboxViewMac::SetForcedQuery() {
272 // We need to do this first, else |SetSelectedRange()| won't work.
275 const string16 current_text(GetText());
276 const size_t start = current_text.find_first_not_of(kWhitespaceUTF16);
277 if (start == string16::npos || (current_text[start] != '?')) {
278 SetUserText(ASCIIToUTF16("?"));
280 NSRange range = NSMakeRange(start + 1, current_text.size() - start - 1);
281 [[field_ currentEditor] setSelectedRange:range];
285 bool OmniboxViewMac::IsSelectAll() const {
286 if (![field_ currentEditor])
288 const NSRange all_range = NSMakeRange(0, GetTextLength());
289 return NSEqualRanges(all_range, GetSelectedRange());
292 bool OmniboxViewMac::DeleteAtEndPressed() {
293 return delete_at_end_pressed_;
296 void OmniboxViewMac::GetSelectionBounds(string16::size_type* start,
297 string16::size_type* end) const {
298 if (![field_ currentEditor]) {
303 const NSRange selected_range = GetSelectedRange();
304 *start = static_cast<size_t>(selected_range.location);
305 *end = static_cast<size_t>(NSMaxRange(selected_range));
308 void OmniboxViewMac::SelectAll(bool reversed) {
309 // TODO(shess): Figure out what |reversed| implies. The gtk version
310 // has it imply inverting the selection front to back, but I don't
311 // even know if that makes sense for Mac.
313 // TODO(shess): Verify that we should be stealing focus at this
315 SetSelectedRange(NSMakeRange(0, GetTextLength()));
318 void OmniboxViewMac::RevertAll() {
319 OmniboxView::RevertAll();
320 [field_ clearUndoChain];
323 void OmniboxViewMac::UpdatePopup() {
324 model()->SetInputInProgress(true);
325 if (!model()->has_focus())
328 // Comment copied from OmniboxViewWin::UpdatePopup():
329 // Don't inline autocomplete when:
330 // * The user is deleting text
331 // * The caret/selection isn't at the end of the text
332 // * The user has just pasted in something that replaced all the text
333 // * The user is trying to compose something in an IME
334 bool prevent_inline_autocomplete = IsImeComposing();
335 NSTextView* editor = (NSTextView*)[field_ currentEditor];
337 if (NSMaxRange([editor selectedRange]) < [[editor textStorage] length])
338 prevent_inline_autocomplete = true;
341 model()->StartAutocomplete([editor selectedRange].length != 0,
342 prevent_inline_autocomplete);
345 void OmniboxViewMac::CloseOmniboxPopup() {
346 // Call both base class methods.
348 OmniboxView::CloseOmniboxPopup();
351 void OmniboxViewMac::SetFocus() {
352 FocusLocation(false);
353 model()->SetCaretVisibility(true);
356 void OmniboxViewMac::ApplyCaretVisibility() {
357 [[field_ cell] setHideFocusState:!model()->is_caret_visible()
361 void OmniboxViewMac::SetText(const string16& display_text) {
362 SetTextInternal(display_text);
365 void OmniboxViewMac::SetTextInternal(const string16& display_text) {
366 NSString* ss = base::SysUTF16ToNSString(display_text);
367 NSMutableAttributedString* as =
368 [[[NSMutableAttributedString alloc] initWithString:ss] autorelease];
370 ApplyTextAttributes(display_text, as);
371 [field_ setAttributedStringValue:as];
373 // TODO(shess): This may be an appropriate place to call:
374 // model()->OnChanged();
375 // In the current implementation, this tells LocationBarViewMac to
376 // mess around with model() and update |field_|. Unfortunately,
377 // when I look at our peer implementations, it's not entirely clear
378 // to me if this is safe. SetTextInternal() is sort of an utility method,
379 // and different callers sometimes have different needs. Research
380 // this issue so that it can be added safely.
382 // TODO(shess): Also, consider whether this code couldn't just
383 // manage things directly. Windows uses a series of overlaid view
384 // objects to accomplish the hinting stuff that OnChanged() does, so
385 // it makes sense to have it in the controller that lays those
386 // things out. Mac instead pushes the support into a custom
387 // text-field implementation.
390 void OmniboxViewMac::SetTextAndSelectedRange(const string16& display_text,
391 const NSRange range) {
392 SetText(display_text);
393 SetSelectedRange(range);
396 void OmniboxViewMac::EmphasizeURLComponents() {
397 NSTextView* editor = (NSTextView*)[field_ currentEditor];
398 // If the autocomplete text field is in editing mode, then we can just change
399 // its attributes through its editor. Otherwise, we simply reset its content.
401 NSTextStorage* storage = [editor textStorage];
402 [storage beginEditing];
404 // Clear the existing attributes from the text storage, then
405 // overlay the appropriate Omnibox attributes.
406 [storage setAttributes:[NSDictionary dictionary]
407 range:NSMakeRange(0, [storage length])];
408 ApplyTextAttributes(GetText(), storage);
410 [storage endEditing];
416 void OmniboxViewMac::ApplyTextAttributes(const string16& display_text,
417 NSMutableAttributedString* as) {
418 NSUInteger as_length = [as length];
419 NSRange as_entire_string = NSMakeRange(0, as_length);
421 [as addAttribute:NSFontAttributeName value:GetFieldFont()
422 range:as_entire_string];
424 // A kinda hacky way to add breaking at periods. This is what Safari does.
425 // This works for IDNs too, despite the "en_US".
426 [as addAttribute:@"NSLanguage" value:@"en_US_POSIX"
427 range:as_entire_string];
429 // Make a paragraph style locking in the standard line height as the maximum,
430 // otherwise the baseline may shift "downwards".
431 base::scoped_nsobject<NSMutableParagraphStyle> paragraph_style(
432 [[NSMutableParagraphStyle alloc] init]);
433 CGFloat line_height = [[field_ cell] lineHeight];
434 [paragraph_style setMaximumLineHeight:line_height];
435 [paragraph_style setMinimumLineHeight:line_height];
436 [paragraph_style setLineBreakMode:NSLineBreakByTruncatingTail];
437 [as addAttribute:NSParagraphStyleAttributeName value:paragraph_style
438 range:as_entire_string];
440 url_parse::Component scheme, host;
441 AutocompleteInput::ParseForEmphasizeComponents(
442 display_text, &scheme, &host);
443 bool grey_out_url = display_text.substr(scheme.begin, scheme.len) ==
444 UTF8ToUTF16(extensions::kExtensionScheme);
445 if (model()->CurrentTextIsURL() &&
446 (host.is_nonempty() || grey_out_url)) {
447 [as addAttribute:NSForegroundColorAttributeName value:BaseTextColor()
448 range:as_entire_string];
451 [as addAttribute:NSForegroundColorAttributeName value:HostTextColor()
452 range:ComponentToNSRange(host)];
456 // TODO(shess): GTK has this as a member var, figure out why.
457 // [Could it be to not change if no change? If so, I'm guessing
458 // AppKit may already handle that.]
459 const ToolbarModel::SecurityLevel security_level =
460 controller()->GetToolbarModel()->GetSecurityLevel(false);
462 // Emphasize the scheme for security UI display purposes (if necessary).
463 if (!model()->user_input_in_progress() && model()->CurrentTextIsURL() &&
464 scheme.is_nonempty() && (security_level != ToolbarModel::NONE)) {
466 if (security_level == ToolbarModel::EV_SECURE ||
467 security_level == ToolbarModel::SECURE) {
468 color = SecureSchemeColor();
469 } else if (security_level == ToolbarModel::SECURITY_ERROR) {
470 color = SecurityErrorSchemeColor();
471 // Add a strikethrough through the scheme.
472 [as addAttribute:NSStrikethroughStyleAttributeName
473 value:[NSNumber numberWithInt:NSUnderlineStyleSingle]
474 range:ComponentToNSRange(scheme)];
475 } else if (security_level == ToolbarModel::SECURITY_WARNING) {
476 color = BaseTextColor();
479 color = BaseTextColor();
481 [as addAttribute:NSForegroundColorAttributeName value:color
482 range:ComponentToNSRange(scheme)];
486 void OmniboxViewMac::OnTemporaryTextMaybeChanged(const string16& display_text,
487 bool save_original_selection,
488 bool notify_text_changed) {
489 if (save_original_selection)
490 saved_temporary_selection_ = GetSelectedRange();
492 SetWindowTextAndCaretPos(display_text, display_text.size(), false, false);
493 if (notify_text_changed)
494 model()->OnChanged();
495 [field_ clearUndoChain];
498 bool OmniboxViewMac::OnInlineAutocompleteTextMaybeChanged(
499 const string16& display_text,
500 size_t user_text_length) {
501 // TODO(shess): Make sure that this actually works. The round trip
502 // to native form and back may mean that it's the same but not the
504 if (display_text == GetText())
507 DCHECK_LE(user_text_length, display_text.size());
508 const NSRange range =
509 NSMakeRange(user_text_length, display_text.size() - user_text_length);
510 SetTextAndSelectedRange(display_text, range);
511 model()->OnChanged();
512 [field_ clearUndoChain];
517 void OmniboxViewMac::OnRevertTemporaryText() {
518 SetSelectedRange(saved_temporary_selection_);
519 // We got here because the user hit the Escape key. We explicitly don't call
520 // TextChanged(), since OmniboxPopupModel::ResetToDefaultMatch() has already
521 // been called by now, and it would've called TextChanged() if it was
525 bool OmniboxViewMac::IsFirstResponder() const {
526 return [field_ currentEditor] != nil ? true : false;
529 void OmniboxViewMac::OnBeforePossibleChange() {
530 // We should only arrive here when the field is focussed.
531 DCHECK(IsFirstResponder());
533 selection_before_change_ = GetSelectedRange();
534 text_before_change_ = GetText();
535 marked_range_before_change_ = GetMarkedRange();
538 bool OmniboxViewMac::OnAfterPossibleChange() {
539 // We should only arrive here when the field is focussed.
540 DCHECK(IsFirstResponder());
542 const NSRange new_selection(GetSelectedRange());
543 const string16 new_text(GetText());
544 const size_t length = new_text.length();
546 const bool selection_differs =
547 (new_selection.length || selection_before_change_.length) &&
548 !NSEqualRanges(new_selection, selection_before_change_);
549 const bool at_end_of_edit = (length == new_selection.location);
550 const bool text_differs = (new_text != text_before_change_) ||
551 !NSEqualRanges(marked_range_before_change_, GetMarkedRange());
553 // When the user has deleted text, we don't allow inline
554 // autocomplete. This is assumed if the text has gotten shorter AND
555 // the selection has shifted towards the front of the text. During
556 // normal typing the text will almost always be shorter (as the new
557 // input replaces the autocomplete suggestion), but in that case the
558 // selection point will have moved towards the end of the text.
559 // TODO(shess): In our implementation, we can catch -deleteBackward:
560 // and other methods to provide positive knowledge that a delete
561 // occured, rather than intuiting it from context. Consider whether
562 // that would be a stronger approach.
563 const bool just_deleted_text =
564 (length < text_before_change_.length() &&
565 new_selection.location <= selection_before_change_.location);
567 delete_at_end_pressed_ = false;
569 const bool something_changed = model()->OnAfterPossibleChange(
570 text_before_change_, new_text, new_selection.location,
571 NSMaxRange(new_selection), selection_differs, text_differs,
572 just_deleted_text, !IsImeComposing());
574 if (delete_was_pressed_ && at_end_of_edit)
575 delete_at_end_pressed_ = true;
577 // Restyle in case the user changed something.
578 // TODO(shess): I believe there are multiple-redraw cases, here.
579 // Linux watches for something_changed && text_differs, but that
580 // fails for us in case you copy the URL and paste the identical URL
581 // back (we'll lose the styling).
584 delete_was_pressed_ = false;
586 return something_changed;
589 gfx::NativeView OmniboxViewMac::GetNativeView() const {
593 gfx::NativeView OmniboxViewMac::GetRelativeWindowForPopup() const {
599 void OmniboxViewMac::SetGrayTextAutocompletion(const string16& suggest_text) {
600 if (suggest_text == suggest_text_)
602 suggest_text_ = suggest_text;
603 [field_ setGrayTextAutocompletion:base::SysUTF16ToNSString(suggest_text)
604 textColor:SuggestTextColor()];
607 string16 OmniboxViewMac::GetGrayTextAutocompletion() const {
608 return suggest_text_;
611 int OmniboxViewMac::TextWidth() const {
617 bool OmniboxViewMac::IsImeComposing() const {
618 return [(NSTextView*)[field_ currentEditor] hasMarkedText];
621 void OmniboxViewMac::OnDidBeginEditing() {
622 // We should only arrive here when the field is focussed.
623 DCHECK([field_ currentEditor]);
626 void OmniboxViewMac::OnBeforeChange() {
627 // Capture the current state.
628 OnBeforePossibleChange();
631 void OmniboxViewMac::OnDidChange() {
632 // Figure out what changed and notify the model.
633 OnAfterPossibleChange();
636 void OmniboxViewMac::OnDidEndEditing() {
640 bool OmniboxViewMac::OnDoCommandBySelector(SEL cmd) {
641 if (cmd == @selector(deleteForward:))
642 delete_was_pressed_ = true;
644 if (cmd == @selector(moveDown:)) {
645 model()->OnUpOrDownKeyPressed(1);
649 if (cmd == @selector(moveUp:)) {
650 model()->OnUpOrDownKeyPressed(-1);
654 if (model()->popup_model()->IsOpen()) {
655 if (cmd == @selector(insertBacktab:)) {
656 if (model()->popup_model()->selected_line_state() ==
657 OmniboxPopupModel::KEYWORD) {
658 model()->ClearKeyword(GetText());
661 model()->OnUpOrDownKeyPressed(-1);
666 if ((cmd == @selector(insertTab:) ||
667 cmd == @selector(insertTabIgnoringFieldEditor:)) &&
668 !model()->is_keyword_hint()) {
669 model()->OnUpOrDownKeyPressed(1);
674 if (cmd == @selector(moveRight:)) {
675 // Only commit suggested text if the cursor is all the way to the right and
676 // there is no selection.
677 if (suggest_text_.length() > 0 && IsCaretAtEnd()) {
678 model()->CommitSuggestedText();
683 if (cmd == @selector(scrollPageDown:)) {
684 model()->OnUpOrDownKeyPressed(model()->result().size());
688 if (cmd == @selector(scrollPageUp:)) {
689 model()->OnUpOrDownKeyPressed(-model()->result().size());
693 if (cmd == @selector(cancelOperation:)) {
694 return model()->OnEscapeKeyPressed();
697 if ((cmd == @selector(insertTab:) ||
698 cmd == @selector(insertTabIgnoringFieldEditor:)) &&
699 model()->is_keyword_hint()) {
700 return model()->AcceptKeyword(ENTERED_KEYWORD_MODE_VIA_TAB);
703 // |-noop:| is sent when the user presses Cmd+Return. Override the no-op
704 // behavior with the proper WindowOpenDisposition.
705 NSEvent* event = [NSApp currentEvent];
706 if (cmd == @selector(insertNewline:) ||
707 (cmd == @selector(noop:) &&
708 ([event type] == NSKeyDown || [event type] == NSKeyUp) &&
709 [event keyCode] == kVK_Return)) {
710 WindowOpenDisposition disposition =
711 ui::WindowOpenDispositionFromNSEvent(event);
712 model()->AcceptInput(disposition, false);
713 // Opening a URL in a background tab should also revert the omnibox contents
714 // to their original state. We cannot do a blanket revert in OpenURL()
715 // because middle-clicks also open in a new background tab, but those should
716 // not revert the omnibox text.
722 if (cmd == @selector(insertNewlineIgnoringFieldEditor:)) {
723 model()->AcceptInput(NEW_FOREGROUND_TAB, false);
727 // When the user does Control-Enter, the existing content has "www."
728 // prepended and ".com" appended. model() should already have
729 // received notification when the Control key was depressed, but it
730 // is safe to tell it twice.
731 if (cmd == @selector(insertLineBreak:)) {
732 OnControlKeyChanged(true);
733 WindowOpenDisposition disposition =
734 ui::WindowOpenDispositionFromNSEvent([NSApp currentEvent]);
735 model()->AcceptInput(disposition, false);
739 if (cmd == @selector(deleteBackward:)) {
740 if (OnBackspacePressed()) {
745 if (cmd == @selector(deleteForward:)) {
746 const NSUInteger modifiers = [[NSApp currentEvent] modifierFlags];
747 if ((modifiers & NSShiftKeyMask) != 0) {
748 if (model()->popup_model()->IsOpen()) {
749 model()->popup_model()->TryDeletingCurrentItem();
758 void OmniboxViewMac::OnSetFocus(bool control_down) {
759 model()->OnSetFocus(control_down);
760 controller()->OnSetFocus();
763 void OmniboxViewMac::OnKillFocus() {
764 // Tell the model to reset itself.
765 model()->OnWillKillFocus(NULL);
766 model()->OnKillFocus();
769 void OmniboxViewMac::OnMouseDown(NSInteger button_number) {
770 // Restore caret visibility whenever the user clicks in the the omnibox. This
771 // is not always covered by OnSetFocus() because when clicking while the
772 // omnibox has invisible focus does not trigger a new OnSetFocus() call.
773 if (button_number == 0 || button_number == 1)
774 model()->SetCaretVisibility(true);
777 bool OmniboxViewMac::ShouldSelectAllOnMouseDown() {
778 return !controller()->GetToolbarModel()->WouldPerformSearchTermReplacement(
782 bool OmniboxViewMac::CanCopy() {
783 const NSRange selection = GetSelectedRange();
784 return selection.length > 0;
787 void OmniboxViewMac::CopyToPasteboard(NSPasteboard* pb) {
790 const NSRange selection = GetSelectedRange();
791 string16 text = base::SysNSStringToUTF16(
792 [[field_ stringValue] substringWithRange:selection]);
794 // Copy the URL unless this is the search URL and it's being replaced by the
795 // Extended Instant API.
797 bool write_url = false;
798 if (!controller()->GetToolbarModel()->WouldPerformSearchTermReplacement(
800 model()->AdjustTextForCopy(selection.location, IsSelectAll(), &text, &url,
804 NSString* nstext = base::SysUTF16ToNSString(text);
805 [pb declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
806 [pb setString:nstext forType:NSStringPboardType];
809 [pb declareURLPasteboardWithAdditionalTypes:[NSArray array] owner:nil];
810 [pb setDataForURL:base::SysUTF8ToNSString(url.spec()) title:nstext];
814 void OmniboxViewMac::ShowURL() {
815 DCHECK(ShouldEnableShowURL());
816 OmniboxView::ShowURL();
819 void OmniboxViewMac::OnPaste() {
820 // This code currently expects |field_| to be focussed.
821 DCHECK([field_ currentEditor]);
823 string16 text = GetClipboardText();
827 NSString* s = base::SysUTF16ToNSString(text);
829 // -shouldChangeTextInRange:* and -didChangeText are documented in
830 // NSTextView as things you need to do if you write additional
831 // user-initiated editing functions. They cause the appropriate
832 // delegate methods to be called.
833 // TODO(shess): It would be nice to separate the Cocoa-specific code
834 // from the Chrome-specific code.
835 NSTextView* editor = static_cast<NSTextView*>([field_ currentEditor]);
836 const NSRange selectedRange = GetSelectedRange();
837 if ([editor shouldChangeTextInRange:selectedRange replacementString:s]) {
838 // Record this paste, so we can do different behavior.
841 // Force a Paste operation to trigger the text_changed code in
842 // OnAfterPossibleChange(), even if identical contents are pasted
843 // into the text box.
844 text_before_change_.clear();
846 [editor replaceCharactersInRange:selectedRange withString:s];
847 [editor didChangeText];
851 // TODO(dominich): Move to OmniboxView base class? Currently this is defined on
852 // the AutocompleteTextFieldObserver but the logic is shared between all
853 // platforms. Some refactor might be necessary to simplify this. Or at least
854 // this method could call the OmniboxView version.
855 bool OmniboxViewMac::ShouldEnableShowURL() {
856 return controller()->GetToolbarModel()->WouldPerformSearchTermReplacement(
860 bool OmniboxViewMac::CanPasteAndGo() {
861 return model()->CanPasteAndGo(GetClipboardText());
864 int OmniboxViewMac::GetPasteActionStringId() {
865 string16 text(GetClipboardText());
866 DCHECK(model()->CanPasteAndGo(text));
867 return model()->IsPasteAndSearch(text) ?
868 IDS_PASTE_AND_SEARCH : IDS_PASTE_AND_GO;
871 void OmniboxViewMac::OnPasteAndGo() {
872 string16 text(GetClipboardText());
873 if (model()->CanPasteAndGo(text))
874 model()->PasteAndGo(text);
877 void OmniboxViewMac::OnFrameChanged() {
878 // TODO(shess): UpdatePopupAppearance() is called frequently, so it
879 // should be really cheap, but in this case we could probably make
880 // things even cheaper by refactoring between the popup-placement
881 // code and the matrix-population code.
882 popup_view_->UpdatePopupAppearance();
884 // Give controller a chance to rearrange decorations.
885 model()->OnChanged();
888 void OmniboxViewMac::ClosePopup() {
889 OmniboxView::CloseOmniboxPopup();
892 bool OmniboxViewMac::OnBackspacePressed() {
893 // Don't intercept if not in keyword search mode.
894 if (model()->is_keyword_hint() || model()->keyword().empty()) {
898 // Don't intercept if there is a selection, or the cursor isn't at
899 // the leftmost position.
900 const NSRange selection = GetSelectedRange();
901 if (selection.length > 0 || selection.location > 0) {
905 // We're showing a keyword and the user pressed backspace at the
906 // beginning of the text. Delete the selected keyword.
907 model()->ClearKeyword(GetText());
911 NSRange OmniboxViewMac::SelectionRangeForProposedRange(NSRange proposed_range) {
912 return proposed_range;
915 void OmniboxViewMac::OnControlKeyChanged(bool pressed) {
916 model()->OnControlKeyChanged(pressed);
919 void OmniboxViewMac::FocusLocation(bool select_all) {
920 if ([field_ isEditable]) {
921 // If the text field has a field editor, it's the first responder, meaning
922 // that it's already focused. makeFirstResponder: will select all, so only
923 // call it if this behavior is desired.
924 if (select_all || ![field_ currentEditor])
925 [[field_ window] makeFirstResponder:field_];
926 DCHECK_EQ([field_ currentEditor], [[field_ window] firstResponder]);
931 NSFont* OmniboxViewMac::GetFieldFont() {
932 // This value should be kept in sync with InstantPage::InitializeFonts.
933 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
934 return rb.GetFont(ResourceBundle::BaseFont).DeriveFont(1).GetNativeFont();
937 int OmniboxViewMac::GetOmniboxTextLength() const {
938 return static_cast<int>(GetTextLength());
941 NSUInteger OmniboxViewMac::GetTextLength() const {
942 return [field_ currentEditor] ? [[[field_ currentEditor] string] length] :
943 [[field_ stringValue] length];
946 bool OmniboxViewMac::IsCaretAtEnd() const {
947 const NSRange selection = GetSelectedRange();
948 return NSMaxRange(selection) == GetTextLength();