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/autocomplete_input.h"
15 #include "chrome/browser/autocomplete/autocomplete_match.h"
16 #include "chrome/browser/browser_process.h"
17 #include "chrome/browser/search/search.h"
18 #include "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_cell.h"
19 #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_editor.h"
20 #include "chrome/browser/ui/cocoa/omnibox/omnibox_popup_view_mac.h"
21 #include "chrome/browser/ui/omnibox/omnibox_edit_controller.h"
22 #include "chrome/browser/ui/omnibox/omnibox_popup_model.h"
23 #include "chrome/browser/ui/toolbar/toolbar_model.h"
24 #include "content/public/browser/web_contents.h"
25 #include "extensions/common/constants.h"
26 #include "grit/generated_resources.h"
27 #include "grit/theme_resources.h"
28 #import "third_party/mozilla/NSPasteboard+Utils.h"
29 #import "ui/base/cocoa/cocoa_base_utils.h"
30 #include "ui/base/clipboard/clipboard.h"
31 #include "ui/base/resource/resource_bundle.h"
32 #include "ui/gfx/font.h"
33 #include "ui/gfx/font_list.h"
34 #include "ui/gfx/geometry/rect.h"
36 using content::WebContents;
38 // Focus-handling between |field_| and model() is a bit subtle.
39 // Other platforms detect change of focus, which is inconvenient
40 // without subclassing NSTextField (even with a subclass, the use of a
41 // field editor may complicate things).
43 // model() doesn't actually do anything when it gains focus, it just
44 // initializes. Visible activity happens only after the user edits.
45 // NSTextField delegate receives messages around starting and ending
46 // edits, so that suffices to catch focus changes. Since all calls
47 // into model() start from OmniboxViewMac, in the worst case
48 // we can add code to sync up the sense of focus as needed.
50 // I've added DCHECK(IsFirstResponder()) in the places which I believe
51 // should only be reachable when |field_| is being edited. If these
52 // fire, it probably means someone unexpected is calling into
55 // Other platforms don't appear to have the sense of "key window" that
56 // Mac does (I believe their fields lose focus when the window loses
57 // focus). Rather than modifying focus outside the control's edit
58 // scope, when the window resigns key the autocomplete popup is
59 // closed. model() still believes it has focus, and the popup will
60 // be regenerated on the user's next edit. That seems to match how
61 // things work on other platforms.
65 // TODO(shess): This is ugly, find a better way. Using it right now
66 // so that I can crib from gtk and still be able to see that I'm using
67 // the same values easily.
68 NSColor* ColorWithRGBBytes(int rr, int gg, int bb) {
72 return [NSColor colorWithCalibratedRed:static_cast<float>(rr)/255.0
73 green:static_cast<float>(gg)/255.0
74 blue:static_cast<float>(bb)/255.0
78 NSColor* HostTextColor() {
79 return [NSColor blackColor];
81 NSColor* BaseTextColor() {
82 return [NSColor darkGrayColor];
84 NSColor* SecureSchemeColor() {
85 return ColorWithRGBBytes(0x07, 0x95, 0x00);
87 NSColor* SecurityErrorSchemeColor() {
88 return ColorWithRGBBytes(0xa2, 0x00, 0x00);
91 const char kOmniboxViewMacStateKey[] = "OmniboxViewMacState";
93 // Store's the model and view state across tab switches.
94 struct OmniboxViewMacState : public base::SupportsUserData::Data {
95 OmniboxViewMacState(const OmniboxEditModel::State model_state,
97 const NSRange& selection)
98 : model_state(model_state),
100 selection(selection) {
102 virtual ~OmniboxViewMacState() {}
104 const OmniboxEditModel::State model_state;
105 const bool has_focus;
106 const NSRange selection;
109 // Accessors for storing and getting the state from the tab.
110 void StoreStateToTab(WebContents* tab,
111 OmniboxViewMacState* state) {
112 tab->SetUserData(kOmniboxViewMacStateKey, state);
114 const OmniboxViewMacState* GetStateFromTab(const WebContents* tab) {
115 return static_cast<OmniboxViewMacState*>(
116 tab->GetUserData(&kOmniboxViewMacStateKey));
119 // Helper to make converting url ranges to NSRange easier to
121 NSRange ComponentToNSRange(const url::Component& component) {
122 return NSMakeRange(static_cast<NSInteger>(component.begin),
123 static_cast<NSInteger>(component.len));
129 NSImage* OmniboxViewMac::ImageForResource(int resource_id) {
130 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
131 return rb.GetNativeImageNamed(resource_id).ToNSImage();
135 NSColor* OmniboxViewMac::SuggestTextColor() {
136 return [NSColor colorWithCalibratedWhite:0.0 alpha:0.5];
139 OmniboxViewMac::OmniboxViewMac(OmniboxEditController* controller,
141 CommandUpdater* command_updater,
142 AutocompleteTextField* field)
143 : OmniboxView(profile, controller, command_updater),
144 popup_view_(new OmniboxPopupViewMac(this, model(), field)),
146 saved_temporary_selection_(NSMakeRange(0, 0)),
147 selection_before_change_(NSMakeRange(0, 0)),
148 marked_range_before_change_(NSMakeRange(0, 0)),
149 delete_was_pressed_(false),
150 delete_at_end_pressed_(false),
151 in_coalesced_update_block_(false),
152 do_coalesced_text_update_(false),
153 do_coalesced_range_update_(false) {
154 [field_ setObserver:this];
156 // Needed so that editing doesn't lose the styling.
157 [field_ setAllowsEditingTextAttributes:YES];
159 // Get the appropriate line height for the font that we use.
160 base::scoped_nsobject<NSLayoutManager> layoutManager(
161 [[NSLayoutManager alloc] init]);
162 [layoutManager setUsesScreenFonts:YES];
165 OmniboxViewMac::~OmniboxViewMac() {
166 // Destroy popup view before this object in case it tries to call us
167 // back in the destructor.
170 // Disconnect from |field_|, it outlives this object.
171 [field_ setObserver:NULL];
174 void OmniboxViewMac::SaveStateToTab(WebContents* tab) {
177 const bool hasFocus = [field_ currentEditor] ? true : false;
181 range = GetSelectedRange();
183 // If we are not focussed, there is no selection. Manufacture
184 // something reasonable in case it starts to matter in the future.
185 range = NSMakeRange(0, GetTextLength());
188 OmniboxViewMacState* state =
189 new OmniboxViewMacState(model()->GetStateForTabSwitch(), hasFocus, range);
190 StoreStateToTab(tab, state);
193 void OmniboxViewMac::OnTabChanged(const WebContents* web_contents) {
194 const OmniboxViewMacState* state = GetStateFromTab(web_contents);
195 model()->RestoreState(state ? &state->model_state : NULL);
196 // Restore focus and selection if they were present when the tab
197 // was switched away.
198 if (state && state->has_focus) {
199 // TODO(shess): Unfortunately, there is no safe way to update
200 // this because TabStripController -selectTabWithContents:* is
201 // also messing with focus. Both parties need to agree to
202 // store existing state before anyone tries to setup the new
203 // state. Anyhow, it would look something like this.
205 [[field_ window] makeFirstResponder:field_];
206 [[field_ currentEditor] setSelectedRange:state->selection];
211 void OmniboxViewMac::Update() {
212 if (chrome::ShouldDisplayOriginChip()) {
213 NSDictionary* placeholder_attributes = @{
214 NSFontAttributeName : GetFieldFont(gfx::Font::NORMAL),
215 NSForegroundColorAttributeName : [NSColor disabledControlTextColor]
217 base::scoped_nsobject<NSMutableAttributedString> placeholder_text(
218 [[NSMutableAttributedString alloc]
219 initWithString:base::SysUTF16ToNSString(GetHintText())
220 attributes:placeholder_attributes]);
221 [[field_ cell] setPlaceholderAttributedString:placeholder_text];
223 if (model()->UpdatePermanentText()) {
224 // Something visibly changed. Re-enable URL replacement.
225 controller()->GetToolbarModel()->set_url_replacement_enabled(true);
226 model()->UpdatePermanentText();
228 // Restore everything to the baseline look.
231 // TODO(shess): Figure out how this case is used, to make sure
232 // we're getting the selection and popup right.
234 // TODO(shess): This corresponds to _win and _gtk, except those
235 // guard it with a test for whether the security level changed.
236 // But AFAICT, that can only change if the text changed, and that
237 // code compares the toolbar model security level with the local
238 // security level. Dig in and figure out why this isn't a no-op
239 // that should go away.
240 EmphasizeURLComponents();
244 void OmniboxViewMac::OpenMatch(const AutocompleteMatch& match,
245 WindowOpenDisposition disposition,
246 const GURL& alternate_nav_url,
247 const base::string16& pasted_text,
248 size_t selected_line) {
249 // Coalesce text and selection updates from the following function. If we
250 // don't do this, the user may see intermediate states as brief flickers.
251 in_coalesced_update_block_ = true;
252 OmniboxView::OpenMatch(
253 match, disposition, alternate_nav_url, pasted_text, selected_line);
254 in_coalesced_update_block_ = false;
255 if (do_coalesced_text_update_)
256 SetText(coalesced_text_update_);
257 do_coalesced_text_update_ = false;
258 if (do_coalesced_range_update_)
259 SetSelectedRange(coalesced_range_update_);
260 do_coalesced_range_update_ = false;
263 base::string16 OmniboxViewMac::GetText() const {
264 return base::SysNSStringToUTF16([field_ stringValue]);
267 NSRange OmniboxViewMac::GetSelectedRange() const {
268 return [[field_ currentEditor] selectedRange];
271 NSRange OmniboxViewMac::GetMarkedRange() const {
272 DCHECK([field_ currentEditor]);
273 return [(NSTextView*)[field_ currentEditor] markedRange];
276 void OmniboxViewMac::SetSelectedRange(const NSRange range) {
277 if (in_coalesced_update_block_) {
278 do_coalesced_range_update_ = true;
279 coalesced_range_update_ = range;
283 // This can be called when we don't have focus. For instance, when
284 // the user clicks the "Go" button.
285 if (model()->has_focus()) {
286 // TODO(shess): If model() thinks we have focus, this should not
287 // be necessary. Try to convert to DCHECK(IsFirstResponder()).
288 if (![field_ currentEditor]) {
289 [[field_ window] makeFirstResponder:field_];
292 // TODO(shess): What if it didn't get first responder, and there is
293 // no field editor? This will do nothing. Well, at least it won't
294 // crash. Think of something more productive to do, or prove that
295 // it cannot occur and DCHECK appropriately.
296 [[field_ currentEditor] setSelectedRange:range];
300 void OmniboxViewMac::SetWindowTextAndCaretPos(const base::string16& text,
303 bool notify_text_changed) {
304 DCHECK_LE(caret_pos, text.size());
305 SetTextAndSelectedRange(text, NSMakeRange(caret_pos, 0));
310 if (notify_text_changed)
314 void OmniboxViewMac::SetForcedQuery() {
315 // We need to do this first, else |SetSelectedRange()| won't work.
318 const base::string16 current_text(GetText());
319 const size_t start = current_text.find_first_not_of(base::kWhitespaceUTF16);
320 if (start == base::string16::npos || (current_text[start] != '?')) {
321 SetUserText(base::ASCIIToUTF16("?"));
323 NSRange range = NSMakeRange(start + 1, current_text.size() - start - 1);
324 [[field_ currentEditor] setSelectedRange:range];
328 bool OmniboxViewMac::IsSelectAll() const {
329 if (![field_ currentEditor])
331 const NSRange all_range = NSMakeRange(0, GetTextLength());
332 return NSEqualRanges(all_range, GetSelectedRange());
335 bool OmniboxViewMac::DeleteAtEndPressed() {
336 return delete_at_end_pressed_;
339 void OmniboxViewMac::GetSelectionBounds(base::string16::size_type* start,
340 base::string16::size_type* end) const {
341 if (![field_ currentEditor]) {
346 const NSRange selected_range = GetSelectedRange();
347 *start = static_cast<size_t>(selected_range.location);
348 *end = static_cast<size_t>(NSMaxRange(selected_range));
351 void OmniboxViewMac::SelectAll(bool reversed) {
352 // TODO(shess): Figure out what |reversed| implies. The gtk version
353 // has it imply inverting the selection front to back, but I don't
354 // even know if that makes sense for Mac.
356 // TODO(shess): Verify that we should be stealing focus at this
358 SetSelectedRange(NSMakeRange(0, GetTextLength()));
361 void OmniboxViewMac::RevertAll() {
362 OmniboxView::RevertAll();
363 [field_ clearUndoChain];
366 void OmniboxViewMac::UpdatePopup() {
367 model()->SetInputInProgress(true);
368 if (!model()->has_focus())
371 // Comment copied from OmniboxViewWin::UpdatePopup():
372 // Don't inline autocomplete when:
373 // * The user is deleting text
374 // * The caret/selection isn't at the end of the text
375 // * The user has just pasted in something that replaced all the text
376 // * The user is trying to compose something in an IME
377 bool prevent_inline_autocomplete = IsImeComposing();
378 NSTextView* editor = (NSTextView*)[field_ currentEditor];
380 if (NSMaxRange([editor selectedRange]) < [[editor textStorage] length])
381 prevent_inline_autocomplete = true;
384 model()->StartAutocomplete([editor selectedRange].length != 0,
385 prevent_inline_autocomplete);
388 void OmniboxViewMac::CloseOmniboxPopup() {
389 // Call both base class methods.
391 OmniboxView::CloseOmniboxPopup();
394 void OmniboxViewMac::SetFocus() {
395 FocusLocation(false);
396 model()->SetCaretVisibility(true);
399 void OmniboxViewMac::ApplyCaretVisibility() {
400 [[field_ cell] setHideFocusState:!model()->is_caret_visible()
404 void OmniboxViewMac::SetText(const base::string16& display_text) {
405 SetTextInternal(display_text);
408 void OmniboxViewMac::SetTextInternal(const base::string16& display_text) {
409 if (in_coalesced_update_block_) {
410 do_coalesced_text_update_ = true;
411 coalesced_text_update_ = display_text;
412 // Don't do any selection changes, since they apply to the previous text.
413 do_coalesced_range_update_ = false;
417 NSString* ss = base::SysUTF16ToNSString(display_text);
418 NSMutableAttributedString* as =
419 [[[NSMutableAttributedString alloc] initWithString:ss] autorelease];
421 ApplyTextAttributes(display_text, as);
422 [field_ setAttributedStringValue:as];
424 // TODO(shess): This may be an appropriate place to call:
425 // model()->OnChanged();
426 // In the current implementation, this tells LocationBarViewMac to
427 // mess around with model() and update |field_|. Unfortunately,
428 // when I look at our peer implementations, it's not entirely clear
429 // to me if this is safe. SetTextInternal() is sort of an utility method,
430 // and different callers sometimes have different needs. Research
431 // this issue so that it can be added safely.
433 // TODO(shess): Also, consider whether this code couldn't just
434 // manage things directly. Windows uses a series of overlaid view
435 // objects to accomplish the hinting stuff that OnChanged() does, so
436 // it makes sense to have it in the controller that lays those
437 // things out. Mac instead pushes the support into a custom
438 // text-field implementation.
441 void OmniboxViewMac::SetTextAndSelectedRange(const base::string16& display_text,
442 const NSRange range) {
443 SetText(display_text);
444 SetSelectedRange(range);
447 void OmniboxViewMac::EmphasizeURLComponents() {
448 NSTextView* editor = (NSTextView*)[field_ currentEditor];
449 // If the autocomplete text field is in editing mode, then we can just change
450 // its attributes through its editor. Otherwise, we simply reset its content.
452 NSTextStorage* storage = [editor textStorage];
453 [storage beginEditing];
455 // Clear the existing attributes from the text storage, then
456 // overlay the appropriate Omnibox attributes.
457 [storage setAttributes:[NSDictionary dictionary]
458 range:NSMakeRange(0, [storage length])];
459 ApplyTextAttributes(GetText(), storage);
461 [storage endEditing];
463 // This function can be called during the editor's -resignFirstResponder. If
464 // that happens, |storage| and |field_| will not be synced automatically any
465 // more. Calling -stringValue ensures that |field_| reflects the changes to
467 [field_ stringValue];
473 void OmniboxViewMac::ApplyTextAttributes(const base::string16& display_text,
474 NSMutableAttributedString* as) {
475 NSUInteger as_length = [as length];
476 NSRange as_entire_string = NSMakeRange(0, as_length);
478 [as addAttribute:NSFontAttributeName value:GetFieldFont(gfx::Font::NORMAL)
479 range:as_entire_string];
481 // A kinda hacky way to add breaking at periods. This is what Safari does.
482 // This works for IDNs too, despite the "en_US".
483 [as addAttribute:@"NSLanguage" value:@"en_US_POSIX"
484 range:as_entire_string];
486 // Make a paragraph style locking in the standard line height as the maximum,
487 // otherwise the baseline may shift "downwards".
488 base::scoped_nsobject<NSMutableParagraphStyle> paragraph_style(
489 [[NSMutableParagraphStyle alloc] init]);
490 CGFloat line_height = [[field_ cell] lineHeight];
491 [paragraph_style setMaximumLineHeight:line_height];
492 [paragraph_style setMinimumLineHeight:line_height];
493 [paragraph_style setLineBreakMode:NSLineBreakByTruncatingTail];
494 [as addAttribute:NSParagraphStyleAttributeName value:paragraph_style
495 range:as_entire_string];
497 url::Component scheme, host;
498 AutocompleteInput::ParseForEmphasizeComponents(
499 display_text, &scheme, &host);
500 bool grey_out_url = display_text.substr(scheme.begin, scheme.len) ==
501 base::UTF8ToUTF16(extensions::kExtensionScheme);
502 if (model()->CurrentTextIsURL() &&
503 (host.is_nonempty() || grey_out_url)) {
504 [as addAttribute:NSForegroundColorAttributeName value:BaseTextColor()
505 range:as_entire_string];
508 [as addAttribute:NSForegroundColorAttributeName value:HostTextColor()
509 range:ComponentToNSRange(host)];
513 // TODO(shess): GTK has this as a member var, figure out why.
514 // [Could it be to not change if no change? If so, I'm guessing
515 // AppKit may already handle that.]
516 const ToolbarModel::SecurityLevel security_level =
517 controller()->GetToolbarModel()->GetSecurityLevel(false);
519 // Emphasize the scheme for security UI display purposes (if necessary).
520 if (!model()->user_input_in_progress() && model()->CurrentTextIsURL() &&
521 scheme.is_nonempty() && (security_level != ToolbarModel::NONE)) {
523 if (security_level == ToolbarModel::EV_SECURE ||
524 security_level == ToolbarModel::SECURE) {
525 color = SecureSchemeColor();
526 } else if (security_level == ToolbarModel::SECURITY_ERROR) {
527 color = SecurityErrorSchemeColor();
528 // Add a strikethrough through the scheme.
529 [as addAttribute:NSStrikethroughStyleAttributeName
530 value:[NSNumber numberWithInt:NSUnderlineStyleSingle]
531 range:ComponentToNSRange(scheme)];
532 } else if (security_level == ToolbarModel::SECURITY_WARNING) {
533 color = BaseTextColor();
536 color = BaseTextColor();
538 [as addAttribute:NSForegroundColorAttributeName value:color
539 range:ComponentToNSRange(scheme)];
543 void OmniboxViewMac::OnTemporaryTextMaybeChanged(
544 const base::string16& display_text,
545 bool save_original_selection,
546 bool notify_text_changed) {
547 if (save_original_selection)
548 saved_temporary_selection_ = GetSelectedRange();
550 SetWindowTextAndCaretPos(display_text, display_text.size(), false, false);
551 if (notify_text_changed)
552 model()->OnChanged();
553 [field_ clearUndoChain];
556 bool OmniboxViewMac::OnInlineAutocompleteTextMaybeChanged(
557 const base::string16& display_text,
558 size_t user_text_length) {
559 // TODO(shess): Make sure that this actually works. The round trip
560 // to native form and back may mean that it's the same but not the
562 if (display_text == GetText())
565 DCHECK_LE(user_text_length, display_text.size());
566 const NSRange range =
567 NSMakeRange(user_text_length, display_text.size() - user_text_length);
568 SetTextAndSelectedRange(display_text, range);
569 model()->OnChanged();
570 [field_ clearUndoChain];
575 void OmniboxViewMac::OnInlineAutocompleteTextCleared() {
578 void OmniboxViewMac::OnRevertTemporaryText() {
579 SetSelectedRange(saved_temporary_selection_);
580 // We got here because the user hit the Escape key. We explicitly don't call
581 // TextChanged(), since OmniboxPopupModel::ResetToDefaultMatch() has already
582 // been called by now, and it would've called TextChanged() if it was
586 bool OmniboxViewMac::IsFirstResponder() const {
587 return [field_ currentEditor] != nil ? true : false;
590 void OmniboxViewMac::OnBeforePossibleChange() {
591 // We should only arrive here when the field is focussed.
592 DCHECK(IsFirstResponder());
594 selection_before_change_ = GetSelectedRange();
595 text_before_change_ = GetText();
596 marked_range_before_change_ = GetMarkedRange();
599 bool OmniboxViewMac::OnAfterPossibleChange() {
600 // We should only arrive here when the field is focussed.
601 DCHECK(IsFirstResponder());
603 const NSRange new_selection(GetSelectedRange());
604 const base::string16 new_text(GetText());
605 const size_t length = new_text.length();
607 const bool selection_differs =
608 (new_selection.length || selection_before_change_.length) &&
609 !NSEqualRanges(new_selection, selection_before_change_);
610 const bool at_end_of_edit = (length == new_selection.location);
611 const bool text_differs = (new_text != text_before_change_) ||
612 !NSEqualRanges(marked_range_before_change_, GetMarkedRange());
614 // When the user has deleted text, we don't allow inline
615 // autocomplete. This is assumed if the text has gotten shorter AND
616 // the selection has shifted towards the front of the text. During
617 // normal typing the text will almost always be shorter (as the new
618 // input replaces the autocomplete suggestion), but in that case the
619 // selection point will have moved towards the end of the text.
620 // TODO(shess): In our implementation, we can catch -deleteBackward:
621 // and other methods to provide positive knowledge that a delete
622 // occured, rather than intuiting it from context. Consider whether
623 // that would be a stronger approach.
624 const bool just_deleted_text =
625 (length < text_before_change_.length() &&
626 new_selection.location <= selection_before_change_.location);
628 delete_at_end_pressed_ = false;
630 const bool something_changed = model()->OnAfterPossibleChange(
631 text_before_change_, new_text, new_selection.location,
632 NSMaxRange(new_selection), selection_differs, text_differs,
633 just_deleted_text, !IsImeComposing());
635 if (delete_was_pressed_ && at_end_of_edit)
636 delete_at_end_pressed_ = true;
638 // Restyle in case the user changed something.
639 // TODO(shess): I believe there are multiple-redraw cases, here.
640 // Linux watches for something_changed && text_differs, but that
641 // fails for us in case you copy the URL and paste the identical URL
642 // back (we'll lose the styling).
645 delete_was_pressed_ = false;
647 return something_changed;
650 gfx::NativeView OmniboxViewMac::GetNativeView() const {
654 gfx::NativeView OmniboxViewMac::GetRelativeWindowForPopup() const {
660 void OmniboxViewMac::SetGrayTextAutocompletion(
661 const base::string16& suggest_text) {
662 if (suggest_text == suggest_text_)
664 suggest_text_ = suggest_text;
665 [field_ setGrayTextAutocompletion:base::SysUTF16ToNSString(suggest_text)
666 textColor:SuggestTextColor()];
669 base::string16 OmniboxViewMac::GetGrayTextAutocompletion() const {
670 return suggest_text_;
673 int OmniboxViewMac::GetTextWidth() const {
679 int OmniboxViewMac::GetWidth() const {
680 return ceil([field_ bounds].size.width);
683 bool OmniboxViewMac::IsImeComposing() const {
684 return [(NSTextView*)[field_ currentEditor] hasMarkedText];
687 void OmniboxViewMac::OnDidBeginEditing() {
688 // We should only arrive here when the field is focussed.
689 DCHECK([field_ currentEditor]);
692 void OmniboxViewMac::OnBeforeChange() {
693 // Capture the current state.
694 OnBeforePossibleChange();
697 void OmniboxViewMac::OnDidChange() {
698 // Figure out what changed and notify the model.
699 OnAfterPossibleChange();
702 void OmniboxViewMac::OnDidEndEditing() {
706 bool OmniboxViewMac::OnDoCommandBySelector(SEL cmd) {
707 if (cmd == @selector(deleteForward:))
708 delete_was_pressed_ = true;
710 if (cmd == @selector(moveDown:)) {
711 model()->OnUpOrDownKeyPressed(1);
715 if (cmd == @selector(moveUp:)) {
716 model()->OnUpOrDownKeyPressed(-1);
720 if (model()->popup_model()->IsOpen()) {
721 if (cmd == @selector(insertBacktab:)) {
722 if (model()->popup_model()->selected_line_state() ==
723 OmniboxPopupModel::KEYWORD) {
724 model()->ClearKeyword(GetText());
727 model()->OnUpOrDownKeyPressed(-1);
732 if ((cmd == @selector(insertTab:) ||
733 cmd == @selector(insertTabIgnoringFieldEditor:)) &&
734 !model()->is_keyword_hint()) {
735 model()->OnUpOrDownKeyPressed(1);
740 if (cmd == @selector(moveRight:)) {
741 // Only commit suggested text if the cursor is all the way to the right and
742 // there is no selection.
743 if (suggest_text_.length() > 0 && IsCaretAtEnd()) {
744 model()->CommitSuggestedText();
749 if (cmd == @selector(scrollPageDown:)) {
750 model()->OnUpOrDownKeyPressed(model()->result().size());
754 if (cmd == @selector(scrollPageUp:)) {
755 model()->OnUpOrDownKeyPressed(-model()->result().size());
759 if (cmd == @selector(cancelOperation:)) {
760 return model()->OnEscapeKeyPressed();
763 if ((cmd == @selector(insertTab:) ||
764 cmd == @selector(insertTabIgnoringFieldEditor:)) &&
765 model()->is_keyword_hint()) {
766 return model()->AcceptKeyword(ENTERED_KEYWORD_MODE_VIA_TAB);
769 // |-noop:| is sent when the user presses Cmd+Return. Override the no-op
770 // behavior with the proper WindowOpenDisposition.
771 NSEvent* event = [NSApp currentEvent];
772 if (cmd == @selector(insertNewline:) ||
773 (cmd == @selector(noop:) &&
774 ([event type] == NSKeyDown || [event type] == NSKeyUp) &&
775 [event keyCode] == kVK_Return)) {
776 WindowOpenDisposition disposition =
777 ui::WindowOpenDispositionFromNSEvent(event);
778 model()->AcceptInput(disposition, false);
779 // Opening a URL in a background tab should also revert the omnibox contents
780 // to their original state. We cannot do a blanket revert in OpenURL()
781 // because middle-clicks also open in a new background tab, but those should
782 // not revert the omnibox text.
788 if (cmd == @selector(insertNewlineIgnoringFieldEditor:)) {
789 model()->AcceptInput(NEW_FOREGROUND_TAB, false);
793 // When the user does Control-Enter, the existing content has "www."
794 // prepended and ".com" appended. model() should already have
795 // received notification when the Control key was depressed, but it
796 // is safe to tell it twice.
797 if (cmd == @selector(insertLineBreak:)) {
798 OnControlKeyChanged(true);
799 WindowOpenDisposition disposition =
800 ui::WindowOpenDispositionFromNSEvent([NSApp currentEvent]);
801 model()->AcceptInput(disposition, false);
805 if (cmd == @selector(deleteBackward:)) {
806 if (OnBackspacePressed()) {
811 if (cmd == @selector(deleteForward:)) {
812 const NSUInteger modifiers = [[NSApp currentEvent] modifierFlags];
813 if ((modifiers & NSShiftKeyMask) != 0) {
814 if (model()->popup_model()->IsOpen()) {
815 model()->popup_model()->TryDeletingCurrentItem();
824 void OmniboxViewMac::OnSetFocus(bool control_down) {
825 model()->OnSetFocus(control_down);
826 controller()->OnSetFocus();
828 HandleOriginChipMouseRelease();
831 void OmniboxViewMac::OnKillFocus() {
832 // Tell the model to reset itself.
833 model()->OnWillKillFocus(NULL);
834 model()->OnKillFocus();
839 void OmniboxViewMac::OnMouseDown(NSInteger button_number) {
840 // Restore caret visibility whenever the user clicks in the the omnibox. This
841 // is not always covered by OnSetFocus() because when clicking while the
842 // omnibox has invisible focus does not trigger a new OnSetFocus() call.
843 if (button_number == 0 || button_number == 1)
844 model()->SetCaretVisibility(true);
847 bool OmniboxViewMac::ShouldSelectAllOnMouseDown() {
848 return !controller()->GetToolbarModel()->WouldPerformSearchTermReplacement(
852 bool OmniboxViewMac::CanCopy() {
853 const NSRange selection = GetSelectedRange();
854 return selection.length > 0;
857 void OmniboxViewMac::CopyToPasteboard(NSPasteboard* pb) {
860 const NSRange selection = GetSelectedRange();
861 base::string16 text = base::SysNSStringToUTF16(
862 [[field_ stringValue] substringWithRange:selection]);
864 // Copy the URL unless this is the search URL and it's being replaced by the
865 // Extended Instant API.
867 bool write_url = false;
868 if (!controller()->GetToolbarModel()->WouldPerformSearchTermReplacement(
870 model()->AdjustTextForCopy(selection.location, IsSelectAll(), &text, &url,
875 UMA_HISTOGRAM_COUNTS(OmniboxEditModel::kCutOrCopyAllTextHistogram, 1);
877 NSString* nstext = base::SysUTF16ToNSString(text);
878 [pb declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
879 [pb setString:nstext forType:NSStringPboardType];
882 [pb declareURLPasteboardWithAdditionalTypes:[NSArray array] owner:nil];
883 [pb setDataForURL:base::SysUTF8ToNSString(url.spec()) title:nstext];
887 void OmniboxViewMac::ShowURL() {
888 DCHECK(ShouldEnableShowURL());
889 OmniboxView::ShowURL();
892 void OmniboxViewMac::OnPaste() {
893 // This code currently expects |field_| to be focussed.
894 DCHECK([field_ currentEditor]);
896 base::string16 text = GetClipboardText();
900 NSString* s = base::SysUTF16ToNSString(text);
902 // -shouldChangeTextInRange:* and -didChangeText are documented in
903 // NSTextView as things you need to do if you write additional
904 // user-initiated editing functions. They cause the appropriate
905 // delegate methods to be called.
906 // TODO(shess): It would be nice to separate the Cocoa-specific code
907 // from the Chrome-specific code.
908 NSTextView* editor = static_cast<NSTextView*>([field_ currentEditor]);
909 const NSRange selectedRange = GetSelectedRange();
910 if ([editor shouldChangeTextInRange:selectedRange replacementString:s]) {
911 // Record this paste, so we can do different behavior.
914 // Force a Paste operation to trigger the text_changed code in
915 // OnAfterPossibleChange(), even if identical contents are pasted
916 // into the text box.
917 text_before_change_.clear();
919 [editor replaceCharactersInRange:selectedRange withString:s];
920 [editor didChangeText];
924 // TODO(dominich): Move to OmniboxView base class? Currently this is defined on
925 // the AutocompleteTextFieldObserver but the logic is shared between all
926 // platforms. Some refactor might be necessary to simplify this. Or at least
927 // this method could call the OmniboxView version.
928 bool OmniboxViewMac::ShouldEnableShowURL() {
929 return controller()->GetToolbarModel()->WouldReplaceURL();
932 bool OmniboxViewMac::CanPasteAndGo() {
933 return model()->CanPasteAndGo(GetClipboardText());
936 int OmniboxViewMac::GetPasteActionStringId() {
937 base::string16 text(GetClipboardText());
938 DCHECK(model()->CanPasteAndGo(text));
939 return model()->IsPasteAndSearch(text) ?
940 IDS_PASTE_AND_SEARCH : IDS_PASTE_AND_GO;
943 void OmniboxViewMac::OnPasteAndGo() {
944 base::string16 text(GetClipboardText());
945 if (model()->CanPasteAndGo(text))
946 model()->PasteAndGo(text);
949 void OmniboxViewMac::OnFrameChanged() {
950 // TODO(shess): UpdatePopupAppearance() is called frequently, so it
951 // should be really cheap, but in this case we could probably make
952 // things even cheaper by refactoring between the popup-placement
953 // code and the matrix-population code.
954 popup_view_->UpdatePopupAppearance();
956 // Give controller a chance to rearrange decorations.
957 model()->OnChanged();
960 void OmniboxViewMac::ClosePopup() {
961 OmniboxView::CloseOmniboxPopup();
964 bool OmniboxViewMac::OnBackspacePressed() {
965 // Don't intercept if not in keyword search mode.
966 if (model()->is_keyword_hint() || model()->keyword().empty()) {
970 // Don't intercept if there is a selection, or the cursor isn't at
971 // the leftmost position.
972 const NSRange selection = GetSelectedRange();
973 if (selection.length > 0 || selection.location > 0) {
977 // We're showing a keyword and the user pressed backspace at the
978 // beginning of the text. Delete the selected keyword.
979 model()->ClearKeyword(GetText());
983 NSRange OmniboxViewMac::SelectionRangeForProposedRange(NSRange proposed_range) {
984 return proposed_range;
987 void OmniboxViewMac::OnControlKeyChanged(bool pressed) {
988 model()->OnControlKeyChanged(pressed);
991 void OmniboxViewMac::FocusLocation(bool select_all) {
992 if ([field_ isEditable]) {
993 // If the text field has a field editor, it's the first responder, meaning
994 // that it's already focused. makeFirstResponder: will select all, so only
995 // call it if this behavior is desired.
996 if (select_all || ![field_ currentEditor])
997 [[field_ window] makeFirstResponder:field_];
998 DCHECK_EQ([field_ currentEditor], [[field_ window] firstResponder]);
1003 NSFont* OmniboxViewMac::GetFieldFont(int style) {
1004 // This value should be kept in sync with InstantPage::InitializeFonts.
1005 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
1006 return rb.GetFontList(ui::ResourceBundle::BaseFont).Derive(1, style)
1007 .GetPrimaryFont().GetNativeFont();
1010 int OmniboxViewMac::GetOmniboxTextLength() const {
1011 return static_cast<int>(GetTextLength());
1014 NSUInteger OmniboxViewMac::GetTextLength() const {
1015 return [field_ currentEditor] ? [[[field_ currentEditor] string] length] :
1016 [[field_ stringValue] length];
1019 bool OmniboxViewMac::IsCaretAtEnd() const {
1020 const NSRange selection = GetSelectedRange();
1021 return NSMaxRange(selection) == GetTextLength();