1 // Copyright (c) 2010 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/autocomplete/autocomplete_edit_view_mac.h"
7 #include <Carbon/Carbon.h> // kVK_Return
9 #include "app/clipboard/clipboard.h"
10 #include "app/clipboard/scoped_clipboard_writer.h"
11 #include "app/resource_bundle.h"
12 #include "base/nsimage_cache_mac.h"
13 #include "base/string_util.h"
14 #include "base/sys_string_conversions.h"
15 #include "base/utf_string_conversions.h"
16 #include "chrome/browser/autocomplete/autocomplete_edit.h"
17 #include "chrome/browser/autocomplete/autocomplete_match.h"
18 #include "chrome/browser/autocomplete/autocomplete_popup_model.h"
19 #include "chrome/browser/autocomplete/autocomplete_popup_view_mac.h"
20 #include "chrome/browser/browser_process.h"
21 #include "chrome/browser/tab_contents/tab_contents.h"
22 #include "chrome/browser/ui/cocoa/event_utils.h"
23 #include "chrome/browser/ui/toolbar/toolbar_model.h"
24 #include "grit/generated_resources.h"
25 #include "grit/theme_resources.h"
26 #include "net/base/escape.h"
27 #import "third_party/mozilla/NSPasteboard+Utils.h"
29 // Focus-handling between |field_| and |model_| is a bit subtle.
30 // Other platforms detect change of focus, which is inconvenient
31 // without subclassing NSTextField (even with a subclass, the use of a
32 // field editor may complicate things).
34 // |model_| doesn't actually do anything when it gains focus, it just
35 // initializes. Visible activity happens only after the user edits.
36 // NSTextField delegate receives messages around starting and ending
37 // edits, so that suffices to catch focus changes. Since all calls
38 // into |model_| start from AutocompleteEditViewMac, in the worst case
39 // we can add code to sync up the sense of focus as needed.
41 // I've added DCHECK(IsFirstResponder()) in the places which I believe
42 // should only be reachable when |field_| is being edited. If these
43 // fire, it probably means someone unexpected is calling into
46 // Other platforms don't appear to have the sense of "key window" that
47 // Mac does (I believe their fields lose focus when the window loses
48 // focus). Rather than modifying focus outside the control's edit
49 // scope, when the window resigns key the autocomplete popup is
50 // closed. |model_| still believes it has focus, and the popup will
51 // be regenerated on the user's next edit. That seems to match how
52 // things work on other platforms.
56 // TODO(shess): This is ugly, find a better way. Using it right now
57 // so that I can crib from gtk and still be able to see that I'm using
58 // the same values easily.
59 NSColor* ColorWithRGBBytes(int rr, int gg, int bb) {
63 return [NSColor colorWithCalibratedRed:static_cast<float>(rr)/255.0
64 green:static_cast<float>(gg)/255.0
65 blue:static_cast<float>(bb)/255.0
69 NSColor* HostTextColor() {
70 return [NSColor blackColor];
72 NSColor* BaseTextColor() {
73 return [NSColor darkGrayColor];
75 NSColor* SuggestTextColor() {
76 return [NSColor grayColor];
78 NSColor* SecureSchemeColor() {
79 return ColorWithRGBBytes(0x07, 0x95, 0x00);
81 NSColor* SecurityErrorSchemeColor() {
82 return ColorWithRGBBytes(0xa2, 0x00, 0x00);
85 // Store's the model and view state across tab switches.
86 struct AutocompleteEditViewMacState {
87 AutocompleteEditViewMacState(const AutocompleteEditModel::State model_state,
88 const bool has_focus, const NSRange& selection)
89 : model_state(model_state),
91 selection(selection) {
94 const AutocompleteEditModel::State model_state;
96 const NSRange selection;
99 // Returns a lazily initialized property bag accessor for saving our
100 // state in a TabContents. When constructed |accessor| generates a
101 // globally-unique id used to index into the per-tab PropertyBag used
102 // to store the state data.
103 PropertyAccessor<AutocompleteEditViewMacState>* GetStateAccessor() {
104 static PropertyAccessor<AutocompleteEditViewMacState> accessor;
108 // Accessors for storing and getting the state from the tab.
109 void StoreStateToTab(TabContents* tab,
110 const AutocompleteEditViewMacState& state) {
111 GetStateAccessor()->SetProperty(tab->property_bag(), state);
113 const AutocompleteEditViewMacState* GetStateFromTab(const TabContents* tab) {
114 return GetStateAccessor()->GetProperty(tab->property_bag());
117 // Helper to make converting url_parse ranges to NSRange easier to
119 NSRange ComponentToNSRange(const url_parse::Component& component) {
120 return NSMakeRange(static_cast<NSInteger>(component.begin),
121 static_cast<NSInteger>(component.len));
127 NSImage* AutocompleteEditViewMac::ImageForResource(int resource_id) {
128 NSString* image_name = nil;
130 switch(resource_id) {
131 // From the autocomplete popup, or the star icon at the RHS of the
133 case IDR_STAR: image_name = @"star.pdf"; break;
134 case IDR_STAR_LIT: image_name = @"star_lit.pdf"; break;
136 // Values from |AutocompleteMatch::TypeToIcon()|.
137 case IDR_OMNIBOX_SEARCH: image_name = @"omnibox_search.pdf"; break;
138 case IDR_OMNIBOX_HTTP: image_name = @"omnibox_http.pdf"; break;
139 case IDR_OMNIBOX_HISTORY: image_name = @"omnibox_history.pdf"; break;
140 case IDR_OMNIBOX_MORE: image_name = @"omnibox_more.pdf"; break;
142 // Values from |ToolbarModel::GetIcon()|.
143 case IDR_OMNIBOX_HTTPS_VALID:
144 image_name = @"omnibox_https_valid.pdf"; break;
145 case IDR_OMNIBOX_HTTPS_WARNING:
146 image_name = @"omnibox_https_warning.pdf"; break;
147 case IDR_OMNIBOX_HTTPS_INVALID:
148 image_name = @"omnibox_https_invalid.pdf"; break;
152 if (NSImage* image = nsimage_cache::ImageNamed(image_name)) {
156 << "Missing image for " << base::SysNSStringToUTF8(image_name);
160 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
161 return rb.GetNativeImageNamed(resource_id);
164 AutocompleteEditViewMac::AutocompleteEditViewMac(
165 AutocompleteEditController* controller,
166 ToolbarModel* toolbar_model,
168 CommandUpdater* command_updater,
169 AutocompleteTextField* field)
170 : model_(new AutocompleteEditModel(this, controller, profile)),
171 popup_view_(new AutocompletePopupViewMac(this, model_.get(), profile,
173 controller_(controller),
174 toolbar_model_(toolbar_model),
175 command_updater_(command_updater),
177 suggest_text_length_(0),
178 delete_was_pressed_(false),
179 delete_at_end_pressed_(false),
182 DCHECK(toolbar_model);
184 DCHECK(command_updater);
186 [field_ setObserver:this];
188 // Needed so that editing doesn't lose the styling.
189 [field_ setAllowsEditingTextAttributes:YES];
191 // Get the appropriate line height for the font that we use.
192 scoped_nsobject<NSLayoutManager>
193 layoutManager([[NSLayoutManager alloc] init]);
194 [layoutManager setUsesScreenFonts:YES];
195 line_height_ = [layoutManager defaultLineHeightForFont:GetFieldFont()];
196 DCHECK(line_height_ > 0);
199 AutocompleteEditViewMac::~AutocompleteEditViewMac() {
200 // Destroy popup view before this object in case it tries to call us
201 // back in the destructor. Likewise for destroying the model before
206 // Disconnect from |field_|, it outlives this object.
207 [field_ setObserver:NULL];
210 void AutocompleteEditViewMac::SaveStateToTab(TabContents* tab) {
213 const bool hasFocus = [field_ currentEditor] ? true : false;
217 range = GetSelectedRange();
219 // If we are not focussed, there is no selection. Manufacture
220 // something reasonable in case it starts to matter in the future.
221 range = NSMakeRange(0, [[field_ stringValue] length]);
224 AutocompleteEditViewMacState state(model_->GetStateForTabSwitch(),
226 StoreStateToTab(tab, state);
229 void AutocompleteEditViewMac::Update(
230 const TabContents* tab_for_state_restoring) {
231 // TODO(shess): It seems like if the tab is non-NULL, then this code
232 // shouldn't need to be called at all. When coded that way, I find
233 // that the field isn't always updated correctly. Figure out why
234 // this is. Maybe this method should be refactored into more
236 const bool user_visible =
237 model_->UpdatePermanentText(toolbar_model_->GetText());
239 if (tab_for_state_restoring) {
242 const AutocompleteEditViewMacState* state =
243 GetStateFromTab(tab_for_state_restoring);
245 // Should restore the user's text via SetUserText().
246 model_->RestoreState(state->model_state);
248 // Restore focus and selection if they were present when the tab
249 // was switched away.
250 if (state->has_focus) {
251 // TODO(shess): Unfortunately, there is no safe way to update
252 // this because TabStripController -selectTabWithContents:* is
253 // also messing with focus. Both parties need to agree to
254 // store existing state before anyone tries to setup the new
255 // state. Anyhow, it would look something like this.
257 [[field_ window] makeFirstResponder:field_];
258 [[field_ currentEditor] setSelectedRange:state->selection];
262 } else if (user_visible) {
263 // Restore everything to the baseline look.
265 // TODO(shess): Figure out how this case is used, to make sure
266 // we're getting the selection and popup right.
269 // TODO(shess): This corresponds to _win and _gtk, except those
270 // guard it with a test for whether the security level changed.
271 // But AFAICT, that can only change if the text changed, and that
272 // code compares the toolbar_model_ security level with the local
273 // security level. Dig in and figure out why this isn't a no-op
274 // that should go away.
275 EmphasizeURLComponents();
279 void AutocompleteEditViewMac::OpenURL(const GURL& url,
280 WindowOpenDisposition disposition,
281 PageTransition::Type transition,
282 const GURL& alternate_nav_url,
283 size_t selected_line,
284 const std::wstring& keyword) {
285 // TODO(shess): Why is the caller passing an invalid url in the
286 // first place? Make sure that case isn't being dropped on the
288 if (!url.is_valid()) {
292 model_->OpenURL(url, disposition, transition, alternate_nav_url,
293 selected_line, keyword);
296 std::wstring AutocompleteEditViewMac::GetText() const {
297 return base::SysNSStringToWide(GetNonSuggestTextSubstring());
300 bool AutocompleteEditViewMac::IsEditingOrEmpty() const {
301 return model_->user_input_in_progress() ||
302 ([[field_ stringValue] length] == 0);
305 int AutocompleteEditViewMac::GetIcon() const {
306 return IsEditingOrEmpty() ?
307 AutocompleteMatch::TypeToIcon(model_->CurrentTextType()) :
308 toolbar_model_->GetIcon();
311 void AutocompleteEditViewMac::SetUserText(const std::wstring& text) {
312 SetUserText(text, text, true);
315 void AutocompleteEditViewMac::SetUserText(const std::wstring& text,
316 const std::wstring& display_text,
318 model_->SetUserText(text);
319 // TODO(shess): TODO below from gtk.
320 // TODO(deanm): something about selection / focus change here.
321 SetText(display_text);
325 controller_->OnChanged();
328 NSRange AutocompleteEditViewMac::GetSelectedRange() const {
329 DCHECK([field_ currentEditor]);
330 return [[field_ currentEditor] selectedRange];
333 void AutocompleteEditViewMac::SetSelectedRange(const NSRange range) {
334 // This can be called when we don't have focus. For instance, when
335 // the user clicks the "Go" button.
336 if (model_->has_focus()) {
337 // TODO(shess): If |model_| thinks we have focus, this should not
338 // be necessary. Try to convert to DCHECK(IsFirstResponder()).
339 if (![field_ currentEditor]) {
340 [[field_ window] makeFirstResponder:field_];
343 // TODO(shess): What if it didn't get first responder, and there is
344 // no field editor? This will do nothing. Well, at least it won't
345 // crash. Think of something more productive to do, or prove that
346 // it cannot occur and DCHECK appropriately.
347 [[field_ currentEditor] setSelectedRange:range];
351 void AutocompleteEditViewMac::SetWindowTextAndCaretPos(const std::wstring& text,
353 DCHECK_LE(caret_pos, text.size());
354 SetTextAndSelectedRange(text, NSMakeRange(caret_pos, caret_pos));
357 void AutocompleteEditViewMac::SetForcedQuery() {
358 // We need to do this first, else |SetSelectedRange()| won't work.
361 const std::wstring current_text(GetText());
362 const size_t start = current_text.find_first_not_of(kWhitespaceWide);
363 if (start == std::wstring::npos || (current_text[start] != '?')) {
366 NSRange range = NSMakeRange(start + 1, current_text.size() - start - 1);
367 [[field_ currentEditor] setSelectedRange:range];
371 bool AutocompleteEditViewMac::IsSelectAll() {
372 if (![field_ currentEditor])
374 const NSRange all_range = NSMakeRange(0, GetText().length());
375 return NSEqualRanges(all_range, GetSelectedRange());
378 bool AutocompleteEditViewMac::DeleteAtEndPressed() {
379 return delete_at_end_pressed_;
382 void AutocompleteEditViewMac::GetSelectionBounds(std::wstring::size_type* start,
383 std::wstring::size_type* end) {
384 if (![field_ currentEditor]) {
389 const NSRange selected_range = GetSelectedRange();
390 *start = static_cast<size_t>(selected_range.location);
391 *end = static_cast<size_t>(NSMaxRange(selected_range));
394 void AutocompleteEditViewMac::SelectAll(bool reversed) {
395 // TODO(shess): Figure out what |reversed| implies. The gtk version
396 // has it imply inverting the selection front to back, but I don't
397 // even know if that makes sense for Mac.
399 // TODO(shess): Verify that we should be stealing focus at this
401 SetSelectedRange(NSMakeRange(0, GetText().length()));
404 void AutocompleteEditViewMac::RevertAll() {
408 // TODO(shess): This should be a no-op, the results from GetText()
409 // could only get there via UpdateAndStyleText() in the first place.
410 // Dig into where this code can be called from and see if this line
412 EmphasizeURLComponents();
413 controller_->OnChanged();
414 [field_ clearUndoChain];
417 void AutocompleteEditViewMac::UpdatePopup() {
418 model_->SetInputInProgress(true);
419 if (!model_->has_focus())
422 // Comment copied from AutocompleteEditViewWin::UpdatePopup():
423 // Don't inline autocomplete when:
424 // * The user is deleting text
425 // * The caret/selection isn't at the end of the text
426 // * The user has just pasted in something that replaced all the text
427 // * The user is trying to compose something in an IME
428 bool prevent_inline_autocomplete = false;
429 NSTextView* editor = (NSTextView*)[field_ currentEditor];
431 if ([editor hasMarkedText])
432 prevent_inline_autocomplete = true;
434 if (NSMaxRange([editor selectedRange]) <
435 [[editor textStorage] length] - suggest_text_length_) {
436 prevent_inline_autocomplete = true;
440 model_->StartAutocomplete([editor selectedRange].length != 0,
441 prevent_inline_autocomplete);
444 void AutocompleteEditViewMac::ClosePopup() {
445 if (popup_view_->GetModel()->IsOpen())
446 controller_->OnAutocompleteWillClosePopup();
448 popup_view_->GetModel()->StopAutocomplete();
451 void AutocompleteEditViewMac::SetFocus() {
454 void AutocompleteEditViewMac::SetSuggestText(const string16& suggest_text) {
455 NSString* text = GetNonSuggestTextSubstring();
456 bool needs_update = (suggest_text_length_ > 0);
458 // Append the new suggest text.
459 suggest_text_length_ = suggest_text.length();
460 if (suggest_text_length_ > 0) {
461 text = [text stringByAppendingString:base::SysUTF16ToNSString(
467 NSRange current_range = GetSelectedRange();
468 SetTextInternal(base::SysNSStringToWide(text));
469 if (NSMaxRange(current_range) <= [text length] - suggest_text_length_)
470 SetSelectedRange(current_range);
472 SetSelectedRange(NSMakeRange([text length] - suggest_text_length_, 0));
476 bool AutocompleteEditViewMac::CommitSuggestText() {
477 if (suggest_text_length_ == 0)
480 suggest_text_length_ = 0;
481 // Call SetText() to force a redraw and move the cursor to the end.
483 model()->FinalizeInstantQuery(GetText());
487 void AutocompleteEditViewMac::SetText(const std::wstring& display_text) {
488 // If we are setting the text directly, there cannot be any suggest text.
489 suggest_text_length_ = 0;
490 SetTextInternal(display_text);
493 void AutocompleteEditViewMac::SetTextInternal(
494 const std::wstring& display_text) {
495 NSString* ss = base::SysWideToNSString(display_text);
496 NSMutableAttributedString* as =
497 [[[NSMutableAttributedString alloc] initWithString:ss] autorelease];
499 ApplyTextAttributes(display_text, as);
501 [field_ setAttributedStringValue:as];
503 // TODO(shess): This may be an appropriate place to call:
504 // controller_->OnChanged();
505 // In the current implementation, this tells LocationBarViewMac to
506 // mess around with |model_| and update |field_|. Unfortunately,
507 // when I look at our peer implementations, it's not entirely clear
508 // to me if this is safe. SetTextInternal() is sort of an utility method,
509 // and different callers sometimes have different needs. Research
510 // this issue so that it can be added safely.
512 // TODO(shess): Also, consider whether this code couldn't just
513 // manage things directly. Windows uses a series of overlaid view
514 // objects to accomplish the hinting stuff that OnChanged() does, so
515 // it makes sense to have it in the controller that lays those
516 // things out. Mac instead pushes the support into a custom
517 // text-field implementation.
520 void AutocompleteEditViewMac::SetTextAndSelectedRange(
521 const std::wstring& display_text, const NSRange range) {
522 SetText(display_text);
523 SetSelectedRange(range);
526 NSString* AutocompleteEditViewMac::GetNonSuggestTextSubstring() const {
527 NSString* text = [field_ stringValue];
528 if (suggest_text_length_ > 0) {
529 NSUInteger length = [text length];
531 DCHECK_LE(suggest_text_length_, length);
532 text = [text substringToIndex:(length - suggest_text_length_)];
537 void AutocompleteEditViewMac::EmphasizeURLComponents() {
538 NSTextView* editor = (NSTextView*)[field_ currentEditor];
539 // If the autocomplete text field is in editing mode, then we can just change
540 // its attributes through its editor. Otherwise, we simply reset its content.
542 NSTextStorage* storage = [editor textStorage];
543 [storage beginEditing];
545 // Clear the existing attributes from the text storage, then
546 // overlay the appropriate Omnibox attributes.
547 [storage setAttributes:[NSDictionary dictionary]
548 range:NSMakeRange(0, [storage length])];
549 ApplyTextAttributes(GetText(), storage);
551 [storage endEditing];
557 void AutocompleteEditViewMac::ApplyTextAttributes(
558 const std::wstring& display_text, NSMutableAttributedString* as) {
559 [as addAttribute:NSFontAttributeName value:GetFieldFont()
560 range:NSMakeRange(0, [as length])];
562 // Make a paragraph style locking in the standard line height as the maximum,
563 // otherwise the baseline may shift "downwards".
564 scoped_nsobject<NSMutableParagraphStyle>
565 paragraph_style([[NSMutableParagraphStyle alloc] init]);
566 [paragraph_style setMaximumLineHeight:line_height_];
567 [as addAttribute:NSParagraphStyleAttributeName value:paragraph_style
568 range:NSMakeRange(0, [as length])];
570 // Grey out the suggest text.
571 [as addAttribute:NSForegroundColorAttributeName value:SuggestTextColor()
572 range:NSMakeRange([as length] - suggest_text_length_,
573 suggest_text_length_)];
575 url_parse::Component scheme, host;
576 AutocompleteInput::ParseForEmphasizeComponents(
577 display_text, model_->GetDesiredTLD(), &scheme, &host);
578 const bool emphasize = model_->CurrentTextIsURL() && (host.len > 0);
580 [as addAttribute:NSForegroundColorAttributeName value:BaseTextColor()
581 range:NSMakeRange(0, [as length])];
583 [as addAttribute:NSForegroundColorAttributeName value:HostTextColor()
584 range:ComponentToNSRange(host)];
587 // TODO(shess): GTK has this as a member var, figure out why.
588 // [Could it be to not change if no change? If so, I'm guessing
589 // AppKit may already handle that.]
590 const ToolbarModel::SecurityLevel security_level =
591 toolbar_model_->GetSecurityLevel();
593 // Emphasize the scheme for security UI display purposes (if necessary).
594 if (!model_->user_input_in_progress() && scheme.is_nonempty() &&
595 (security_level != ToolbarModel::NONE)) {
597 if (security_level == ToolbarModel::EV_SECURE ||
598 security_level == ToolbarModel::SECURE) {
599 color = SecureSchemeColor();
600 } else if (security_level == ToolbarModel::SECURITY_ERROR) {
601 color = SecurityErrorSchemeColor();
602 // Add a strikethrough through the scheme.
603 [as addAttribute:NSStrikethroughStyleAttributeName
604 value:[NSNumber numberWithInt:NSUnderlineStyleSingle]
605 range:ComponentToNSRange(scheme)];
606 } else if (security_level == ToolbarModel::SECURITY_WARNING) {
607 color = BaseTextColor();
610 color = BaseTextColor();
612 [as addAttribute:NSForegroundColorAttributeName value:color
613 range:ComponentToNSRange(scheme)];
617 void AutocompleteEditViewMac::OnTemporaryTextMaybeChanged(
618 const std::wstring& display_text, bool save_original_selection) {
619 if (save_original_selection)
620 saved_temporary_selection_ = GetSelectedRange();
622 suggest_text_length_ = 0;
623 SetWindowTextAndCaretPos(display_text, display_text.size());
624 controller_->OnChanged();
625 [field_ clearUndoChain];
628 bool AutocompleteEditViewMac::OnInlineAutocompleteTextMaybeChanged(
629 const std::wstring& display_text, size_t user_text_length) {
630 // TODO(shess): Make sure that this actually works. The round trip
631 // to native form and back may mean that it's the same but not the
633 if (display_text == GetText()) {
637 DCHECK_LE(user_text_length, display_text.size());
638 const NSRange range =
639 NSMakeRange(user_text_length, display_text.size() - user_text_length);
640 SetTextAndSelectedRange(display_text, range);
641 controller_->OnChanged();
642 [field_ clearUndoChain];
647 void AutocompleteEditViewMac::OnRevertTemporaryText() {
648 SetSelectedRange(saved_temporary_selection_);
651 bool AutocompleteEditViewMac::IsFirstResponder() const {
652 return [field_ currentEditor] != nil ? true : false;
655 void AutocompleteEditViewMac::OnBeforePossibleChange() {
656 // We should only arrive here when the field is focussed.
657 DCHECK(IsFirstResponder());
659 selection_before_change_ = GetSelectedRange();
660 text_before_change_ = GetText();
663 bool AutocompleteEditViewMac::OnAfterPossibleChange() {
664 // We should only arrive here when the field is focussed.
665 DCHECK(IsFirstResponder());
667 const NSRange new_selection(GetSelectedRange());
668 const std::wstring new_text(GetText());
669 const size_t length = new_text.length();
671 const bool selection_differs = !NSEqualRanges(new_selection,
672 selection_before_change_);
673 const bool at_end_of_edit = (length == new_selection.location);
674 const bool text_differs = (new_text != text_before_change_);
676 // When the user has deleted text, we don't allow inline
677 // autocomplete. This is assumed if the text has gotten shorter AND
678 // the selection has shifted towards the front of the text. During
679 // normal typing the text will almost always be shorter (as the new
680 // input replaces the autocomplete suggestion), but in that case the
681 // selection point will have moved towards the end of the text.
682 // TODO(shess): In our implementation, we can catch -deleteBackward:
683 // and other methods to provide positive knowledge that a delete
684 // occured, rather than intuiting it from context. Consider whether
685 // that would be a stronger approach.
686 const bool just_deleted_text =
687 (length < text_before_change_.length() &&
688 new_selection.location <= selection_before_change_.location);
690 delete_at_end_pressed_ = false;
692 const bool something_changed = model_->OnAfterPossibleChange(new_text,
693 selection_differs, text_differs, just_deleted_text, at_end_of_edit);
695 if (delete_was_pressed_ && at_end_of_edit)
696 delete_at_end_pressed_ = true;
698 // Restyle in case the user changed something.
699 // TODO(shess): I believe there are multiple-redraw cases, here.
700 // Linux watches for something_changed && text_differs, but that
701 // fails for us in case you copy the URL and paste the identical URL
702 // back (we'll lose the styling).
703 EmphasizeURLComponents();
704 controller_->OnChanged();
706 delete_was_pressed_ = false;
708 return something_changed;
711 gfx::NativeView AutocompleteEditViewMac::GetNativeView() const {
715 CommandUpdater* AutocompleteEditViewMac::GetCommandUpdater() {
716 return command_updater_;
719 void AutocompleteEditViewMac::OnDidBeginEditing() {
720 // We should only arrive here when the field is focussed.
721 DCHECK([field_ currentEditor]);
723 // Capture the current state.
724 OnBeforePossibleChange();
727 void AutocompleteEditViewMac::OnDidChange() {
728 // Figure out what changed and notify the model_.
729 OnAfterPossibleChange();
731 // Then capture the new state.
732 OnBeforePossibleChange();
735 void AutocompleteEditViewMac::OnDidEndEditing() {
739 bool AutocompleteEditViewMac::OnDoCommandBySelector(SEL cmd) {
740 // We should only arrive here when the field is focussed.
741 DCHECK(IsFirstResponder());
743 if (cmd != @selector(moveRight:) &&
744 cmd != @selector(insertTab:) &&
745 cmd != @selector(insertTabIgnoringFieldEditor:)) {
746 // Reset the suggest text for any change other than key right or tab.
747 // TODO(rohitrao): This is here to prevent complications when editing text.
748 // See if this can be removed.
749 SetSuggestText(string16());
752 if (cmd == @selector(deleteForward:))
753 delete_was_pressed_ = true;
755 // Don't intercept up/down-arrow if the popup isn't open.
756 if (popup_view_->IsOpen()) {
757 if (cmd == @selector(moveDown:)) {
758 model_->OnUpOrDownKeyPressed(1);
762 if (cmd == @selector(moveUp:)) {
763 model_->OnUpOrDownKeyPressed(-1);
768 if (cmd == @selector(moveRight:)) {
769 // Only commit suggested text if the cursor is all the way to the right and
770 // there is no selection.
771 NSRange range = GetSelectedRange();
772 if (range.length == 0 &&
773 suggest_text_length_ > 0 &&
774 (range.location + suggest_text_length_ ==
775 [[field_ stringValue] length])) {
776 controller_->OnCommitSuggestedText(GetText());
781 if (cmd == @selector(scrollPageDown:)) {
782 model_->OnUpOrDownKeyPressed(model_->result().size());
786 if (cmd == @selector(scrollPageUp:)) {
787 model_->OnUpOrDownKeyPressed(-model_->result().size());
791 if (cmd == @selector(cancelOperation:)) {
792 return model_->OnEscapeKeyPressed();
795 if (cmd == @selector(insertTab:) ||
796 cmd == @selector(insertTabIgnoringFieldEditor:)) {
797 if (model_->is_keyword_hint() && !model_->keyword().empty()) {
798 model_->AcceptKeyword();
802 if (suggest_text_length_ > 0) {
803 controller_->OnCommitSuggestedText(GetText());
808 // |-noop:| is sent when the user presses Cmd+Return. Override the no-op
809 // behavior with the proper WindowOpenDisposition.
810 NSEvent* event = [NSApp currentEvent];
811 if (cmd == @selector(insertNewline:) ||
812 (cmd == @selector(noop:) && [event keyCode] == kVK_Return)) {
813 WindowOpenDisposition disposition =
814 event_utils::WindowOpenDispositionFromNSEvent(event);
815 model_->AcceptInput(disposition, false);
816 // Opening a URL in a background tab should also revert the omnibox contents
817 // to their original state. We cannot do a blanket revert in OpenURL()
818 // because middle-clicks also open in a new background tab, but those should
819 // not revert the omnibox text.
825 if (cmd == @selector(insertNewlineIgnoringFieldEditor:)) {
826 model_->AcceptInput(NEW_FOREGROUND_TAB, false);
830 // When the user does Control-Enter, the existing content has "www."
831 // prepended and ".com" appended. |model_| should already have
832 // received notification when the Control key was depressed, but it
833 // is safe to tell it twice.
834 if (cmd == @selector(insertLineBreak:)) {
835 OnControlKeyChanged(true);
836 WindowOpenDisposition disposition =
837 event_utils::WindowOpenDispositionFromNSEvent([NSApp currentEvent]);
838 model_->AcceptInput(disposition, false);
842 if (cmd == @selector(deleteBackward:)) {
843 if (OnBackspacePressed()) {
848 if (cmd == @selector(deleteForward:)) {
849 const NSUInteger modifiers = [[NSApp currentEvent] modifierFlags];
850 if ((modifiers & NSShiftKeyMask) != 0) {
851 if (popup_view_->IsOpen()) {
852 popup_view_->GetModel()->TryDeletingCurrentItem();
858 // Capture the state before the operation changes the content.
859 // TODO(shess): Determine if this is always redundent WRT the call
860 // in -controlTextDidChange:.
861 OnBeforePossibleChange();
865 void AutocompleteEditViewMac::OnSetFocus(bool control_down) {
866 model_->OnSetFocus(control_down);
867 controller_->OnSetFocus();
870 void AutocompleteEditViewMac::OnKillFocus() {
871 // Tell the model to reset itself.
872 controller_->OnAutocompleteLosingFocus(NULL);
873 model_->OnKillFocus();
874 controller_->OnKillFocus();
877 bool AutocompleteEditViewMac::CanCopy() {
878 const NSRange selection = GetSelectedRange();
879 return selection.length > 0;
882 void AutocompleteEditViewMac::CopyToPasteboard(NSPasteboard* pb) {
885 const NSRange selection = GetSelectedRange();
886 std::wstring text = base::SysNSStringToWide(
887 [[field_ stringValue] substringWithRange:selection]);
890 bool write_url = false;
891 model_->AdjustTextForCopy(selection.location, IsSelectAll(), &text, &url,
894 NSString* nstext = base::SysWideToNSString(text);
895 [pb declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
896 [pb setString:nstext forType:NSStringPboardType];
899 [pb declareURLPasteboardWithAdditionalTypes:[NSArray array] owner:nil];
900 [pb setDataForURL:base::SysUTF8ToNSString(url.spec()) title:nstext];
904 void AutocompleteEditViewMac::OnPaste() {
905 // This code currently expects |field_| to be focussed.
906 DCHECK([field_ currentEditor]);
908 std::wstring text = GetClipboardText(g_browser_process->clipboard());
912 NSString* s = base::SysWideToNSString(text);
914 // -shouldChangeTextInRange:* and -didChangeText are documented in
915 // NSTextView as things you need to do if you write additional
916 // user-initiated editing functions. They cause the appropriate
917 // delegate methods to be called.
918 // TODO(shess): It would be nice to separate the Cocoa-specific code
919 // from the Chrome-specific code.
920 NSTextView* editor = static_cast<NSTextView*>([field_ currentEditor]);
921 const NSRange selectedRange = GetSelectedRange();
922 if ([editor shouldChangeTextInRange:selectedRange replacementString:s]) {
923 // If this paste will be replacing all the text, record that, so
924 // we can do different behaviors in such a case.
926 model_->on_paste_replacing_all();
928 // Force a Paste operation to trigger the text_changed code in
929 // OnAfterPossibleChange(), even if identical contents are pasted
930 // into the text box.
931 text_before_change_.clear();
933 [editor replaceCharactersInRange:selectedRange withString:s];
934 [editor didChangeText];
938 bool AutocompleteEditViewMac::CanPasteAndGo() {
940 model_->CanPasteAndGo(GetClipboardText(g_browser_process->clipboard()));
943 int AutocompleteEditViewMac::GetPasteActionStringId() {
944 DCHECK(CanPasteAndGo());
946 // Use PASTE_AND_SEARCH as the default fallback (although the DCHECK above
947 // should never trigger).
948 if (!model_->is_paste_and_search())
949 return IDS_PASTE_AND_GO;
951 return IDS_PASTE_AND_SEARCH;
954 void AutocompleteEditViewMac::OnPasteAndGo() {
956 model_->PasteAndGo();
959 void AutocompleteEditViewMac::OnFrameChanged() {
960 // TODO(shess): UpdatePopupAppearance() is called frequently, so it
961 // should be really cheap, but in this case we could probably make
962 // things even cheaper by refactoring between the popup-placement
963 // code and the matrix-population code.
964 popup_view_->UpdatePopupAppearance();
965 model_->PopupBoundsChangedTo(popup_view_->GetTargetBounds());
967 // Give controller a chance to rearrange decorations.
968 controller_->OnChanged();
971 bool AutocompleteEditViewMac::OnBackspacePressed() {
972 // Don't intercept if not in keyword search mode.
973 if (model_->is_keyword_hint() || model_->keyword().empty()) {
977 // Don't intercept if there is a selection, or the cursor isn't at
978 // the leftmost position.
979 const NSRange selection = GetSelectedRange();
980 if (selection.length > 0 || selection.location > 0) {
984 // We're showing a keyword and the user pressed backspace at the
985 // beginning of the text. Delete the selected keyword.
986 model_->ClearKeyword(GetText());
990 NSRange AutocompleteEditViewMac::SelectionRangeForProposedRange(
991 NSRange proposed_range) {
992 // Should never call this function unless editing is in progress.
993 DCHECK([field_ currentEditor]);
995 if (![field_ currentEditor])
996 return proposed_range;
998 // Do not use [field_ stringValue] here, as that forces a sync between the
999 // field and the editor. This sync will end up setting the selection, which
1000 // in turn calls this method, leading to an infinite loop. Instead, retrieve
1001 // the current string value directly from the editor.
1002 size_t text_length = [[[field_ currentEditor] string] length];
1004 // Cannot select suggested text.
1005 size_t max = text_length - suggest_text_length_;
1006 NSUInteger start = proposed_range.location;
1007 NSUInteger end = proposed_range.location + proposed_range.length;
1015 return NSMakeRange(start, end - start);
1018 void AutocompleteEditViewMac::OnControlKeyChanged(bool pressed) {
1019 model_->OnControlKeyChanged(pressed);
1022 void AutocompleteEditViewMac::FocusLocation(bool select_all) {
1023 if ([field_ isEditable]) {
1024 // If the text field has a field editor, it's the first responder, meaning
1025 // that it's already focused. makeFirstResponder: will select all, so only
1026 // call it if this behavior is desired.
1027 if (select_all || ![field_ currentEditor])
1028 [[field_ window] makeFirstResponder:field_];
1029 DCHECK_EQ([field_ currentEditor], [[field_ window] firstResponder]);
1033 // TODO(shess): Copied from autocomplete_edit_view_win.cc. Could this
1034 // be pushed into the model?
1035 std::wstring AutocompleteEditViewMac::GetClipboardText(Clipboard* clipboard) {
1036 // autocomplete_edit_view_win.cc assumes this can never happen, we
1040 if (clipboard->IsFormatAvailable(Clipboard::GetPlainTextWFormatType(),
1041 Clipboard::BUFFER_STANDARD)) {
1043 clipboard->ReadText(Clipboard::BUFFER_STANDARD, &text16);
1045 // Note: Unlike in the find popup and textfield view, here we completely
1046 // remove whitespace strings containing newlines. We assume users are
1047 // most likely pasting in URLs that may have been split into multiple
1048 // lines in terminals, email programs, etc., and so linebreaks indicate
1049 // completely bogus whitespace that would just cause the input to be
1051 return CollapseWhitespace(UTF16ToWide(text16), true);
1054 // Try bookmark format.
1056 // It is tempting to try bookmark format first, but the URL we get out of a
1057 // bookmark has been cannonicalized via GURL. This means if a user copies
1058 // and pastes from the URL bar to itself, the text will get fixed up and
1059 // cannonicalized, which is not what the user expects. By pasting in this
1060 // order, we are sure to paste what the user copied.
1061 if (clipboard->IsFormatAvailable(Clipboard::GetUrlWFormatType(),
1062 Clipboard::BUFFER_STANDARD)) {
1063 std::string url_str;
1064 clipboard->ReadBookmark(NULL, &url_str);
1065 // pass resulting url string through GURL to normalize
1067 if (url.is_valid()) {
1068 return UTF8ToWide(url.spec());
1072 return std::wstring();
1076 NSFont* AutocompleteEditViewMac::GetFieldFont() {
1077 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
1078 return rb.GetFont(ResourceBundle::BaseFont).GetNativeFont();