| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "ios/chrome/browser/ui/omnibox/omnibox_view_controller.h" |
| |
| #include "base/strings/sys_string_conversions.h" |
| #include "components/omnibox/browser/omnibox_field_trial.h" |
| #include "components/open_from_clipboard/clipboard_recent_content.h" |
| #include "components/strings/grit/components_strings.h" |
| #import "ios/chrome/browser/ui/commands/load_query_commands.h" |
| #import "ios/chrome/browser/ui/omnibox/omnibox_container_view.h" |
| #import "ios/chrome/browser/ui/toolbar/public/omnibox_focuser.h" |
| #import "ios/chrome/browser/ui/toolbar/public/toolbar_constants.h" |
| #include "ios/chrome/browser/ui/ui_feature_flags.h" |
| #include "ios/chrome/browser/ui/util/ui_util.h" |
| #include "ios/chrome/browser/ui/util/uikit_ui_util.h" |
| #import "ios/chrome/browser/ui/util/uikit_ui_util.h" |
| #include "ios/chrome/grit/ios_strings.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| namespace { |
| |
| const CGFloat kClearButtonSize = 28.0f; |
| |
| } // namespace |
| |
| @interface OmniboxViewController () |
| |
| // Override of UIViewController's view with a different type. |
| @property(nonatomic, strong) OmniboxContainerView* view; |
| |
| @property(nonatomic, assign) BOOL incognito; |
| |
| @end |
| |
| @implementation OmniboxViewController |
| @synthesize incognito = _incognito; |
| @synthesize dispatcher = _dispatcher; |
| @synthesize defaultLeadingImage = _defaultLeadingImage; |
| @synthesize emptyTextLeadingImage = _emptyTextLeadingImage; |
| @dynamic view; |
| |
| - (instancetype)initWithIncognito:(BOOL)isIncognito { |
| self = [super init]; |
| if (self) { |
| _incognito = isIncognito; |
| } |
| return self; |
| } |
| |
| #pragma mark - UIViewController |
| |
| - (void)loadView { |
| UIColor* textColor = self.incognito ? [UIColor whiteColor] |
| : [UIColor colorWithWhite:0 alpha:0.7]; |
| UIColor* textFieldTintColor = self.incognito |
| ? [UIColor whiteColor] |
| : UIColorFromRGB(kLocationBarTintBlue); |
| UIColor* iconTintColor = self.incognito |
| ? [UIColor whiteColor] |
| : [UIColor colorWithWhite:0 alpha:0.7]; |
| |
| self.view = [[OmniboxContainerView alloc] |
| initWithFrame:CGRectZero |
| textColor:textColor |
| textFieldTint:textFieldTintColor |
| iconTint:iconTintColor]; |
| self.view.incognito = self.incognito; |
| |
| SetA11yLabelAndUiAutomationName(self.textField, IDS_ACCNAME_LOCATION, |
| @"Address"); |
| } |
| |
| - (void)viewDidLoad { |
| [super viewDidLoad]; |
| |
| // Add Paste and Go option to the editing menu |
| UIMenuController* menu = [UIMenuController sharedMenuController]; |
| if (base::FeatureList::IsEnabled(kCopiedContentBehavior)) { |
| UIMenuItem* visitCopiedLink = [[UIMenuItem alloc] |
| initWithTitle:l10n_util::GetNSString(IDS_IOS_VISIT_COPIED_LINK) |
| action:@selector(visitCopiedLink:)]; |
| UIMenuItem* searchCopiedText = [[UIMenuItem alloc] |
| initWithTitle:l10n_util::GetNSString(IDS_IOS_SEARCH_COPIED_TEXT) |
| action:@selector(searchCopiedText:)]; |
| [menu setMenuItems:@[ visitCopiedLink, searchCopiedText ]]; |
| } else { |
| UIMenuItem* pasteAndGo = [[UIMenuItem alloc] |
| initWithTitle:l10n_util::GetNSString(IDS_IOS_PASTE_AND_GO) |
| action:NSSelectorFromString(@"pasteAndGo:")]; |
| [menu setMenuItems:@[ pasteAndGo ]]; |
| } |
| |
| self.textField.placeholderTextColor = [self placeholderAndClearButtonColor]; |
| self.textField.placeholder = l10n_util::GetNSString(IDS_OMNIBOX_EMPTY_HINT); |
| [self setupClearButton]; |
| |
| // TODO(crbug.com/866446): Use UITextFieldDelegate instead. |
| [[NSNotificationCenter defaultCenter] |
| addObserver:self |
| selector:@selector(textFieldDidBeginEditing) |
| name:UITextFieldTextDidBeginEditingNotification |
| object:self.textField]; |
| [[NSNotificationCenter defaultCenter] |
| addObserver:self |
| selector:@selector(textFieldDidChange) |
| name:UITextFieldTextDidChangeNotification |
| object:self.textField]; |
| } |
| |
| - (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection { |
| [super traitCollectionDidChange:previousTraitCollection]; |
| [self updateLeadingImageVisibility]; |
| } |
| |
| #pragma mark - public methods |
| |
| - (OmniboxTextFieldIOS*)textField { |
| return self.view.textField; |
| } |
| |
| #pragma mark - OmniboxConsumer |
| |
| - (void)updateAutocompleteIcon:(UIImage*)icon { |
| [self.view setLeadingImage:icon]; |
| } |
| |
| #pragma mark - EditViewAnimatee |
| |
| - (void)setLeadingIconFaded:(BOOL)faded { |
| [self.view setLeadingImageAlpha:faded ? 0 : 1]; |
| } |
| |
| - (void)setClearButtonFaded:(BOOL)faded { |
| self.textField.rightView.alpha = faded ? 0 : 1; |
| } |
| |
| #pragma mark - LocationBarOffsetProvider |
| |
| - (CGFloat)xOffsetForString:(NSString*)string { |
| return [self.textField offsetForString:string]; |
| } |
| |
| #pragma mark - private |
| |
| - (void)updateLeadingImageVisibility { |
| [self.view setLeadingImageHidden:!IsRegularXRegularSizeClass(self)]; |
| } |
| |
| // Tint color for the textfield placeholder and the clear button. |
| - (UIColor*)placeholderAndClearButtonColor { |
| return self.incognito ? [UIColor colorWithWhite:1 alpha:0.5] |
| : [UIColor colorWithWhite:0 alpha:0.3]; |
| } |
| |
| #pragma mark notification callbacks |
| |
| // Called on UITextFieldTextDidBeginEditingNotification for self.textField. |
| - (void)textFieldDidBeginEditing { |
| // Update the clear button state. |
| [self updateClearButtonVisibility]; |
| [self.view setLeadingImage:self.textField.text.length |
| ? self.defaultLeadingImage |
| : self.emptyTextLeadingImage]; |
| } |
| |
| - (void)textFieldDidChange { |
| // If the text is empty, update the leading image. |
| if (self.textField.text.length == 0) { |
| [self.view setLeadingImage:self.emptyTextLeadingImage]; |
| } |
| } |
| |
| #pragma mark clear button |
| |
| // Omnibox uses a custom clear button. It has a custom tint and image, but |
| // otherwise it should act exactly like a system button. To achieve this, a |
| // custom button is used as the |rightView|. Textfield's setRightViewMode: is |
| // used to make the button invisible when the textfield is empty; the visibility |
| // is updated on textfield text changes and clear button presses. |
| - (void)setupClearButton { |
| // Do not use the system clear button. Use a custom "right view" instead. |
| // Note that |rightView| is an incorrect name, it's really a trailing view. |
| [self.textField setClearButtonMode:UITextFieldViewModeNever]; |
| [self.textField setRightViewMode:UITextFieldViewModeAlways]; |
| |
| UIButton* clearButton = [UIButton buttonWithType:UIButtonTypeSystem]; |
| clearButton.frame = CGRectMake(0, 0, kClearButtonSize, kClearButtonSize); |
| [clearButton setImage:[self clearButtonIcon] forState:UIControlStateNormal]; |
| [clearButton addTarget:self |
| action:@selector(clearButtonPressed) |
| forControlEvents:UIControlEventTouchUpInside]; |
| self.textField.rightView = clearButton; |
| |
| clearButton.tintColor = [self placeholderAndClearButtonColor]; |
| SetA11yLabelAndUiAutomationName(clearButton, IDS_IOS_ACCNAME_CLEAR_TEXT, |
| @"Clear Text"); |
| |
| // Observe text changes to show the clear button when there is text and hide |
| // it when the textfield is empty. |
| [self.textField addTarget:self |
| action:@selector(textFieldDidChange:) |
| forControlEvents:UIControlEventEditingChanged]; |
| } |
| |
| - (UIImage*)clearButtonIcon { |
| UIImage* image = [[UIImage imageNamed:@"omnibox_clear_icon"] |
| imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; |
| |
| return image; |
| } |
| |
| - (void)clearButtonPressed { |
| // Emulate a system button clear callback. |
| BOOL shouldClear = |
| [self.textField.delegate textFieldShouldClear:self.textField]; |
| if (shouldClear) { |
| [self.textField setText:@""]; |
| // Calling setText: does not trigger UIControlEventEditingChanged, so update |
| // the clear button visibility manually. |
| [self updateClearButtonVisibility]; |
| [self textFieldDidChange]; |
| } |
| } |
| |
| // Called on textField's UIControlEventEditingChanged. |
| - (void)textFieldDidChange:(UITextField*)textField { |
| [self updateClearButtonVisibility]; |
| } |
| |
| // Hides the clear button if the textfield is empty; shows it otherwise. |
| - (void)updateClearButtonVisibility { |
| BOOL hasText = self.textField.text.length > 0; |
| [self.textField setRightViewMode:hasText ? UITextFieldViewModeAlways |
| : UITextFieldViewModeNever]; |
| } |
| |
| #pragma mark - UIMenuItem |
| |
| - (BOOL)canPerformAction:(SEL)action withSender:(id)sender { |
| // Remove with flag kCopiedContentBehavior |
| if (action == @selector(pasteAndGo:)) { |
| DCHECK(!base::FeatureList::IsEnabled(kCopiedContentBehavior)); |
| return UIPasteboard.generalPasteboard.string.length > 0; |
| } |
| ClipboardRecentContent* clipboardRecentContent = |
| ClipboardRecentContent::GetInstance(); |
| if (action == @selector(visitCopiedLink:)) { |
| DCHECK(base::FeatureList::IsEnabled(kCopiedContentBehavior)); |
| return clipboardRecentContent->GetRecentURLFromClipboard().has_value(); |
| } |
| if (action == @selector(searchCopiedText:)) { |
| DCHECK(base::FeatureList::IsEnabled(kCopiedContentBehavior)); |
| return !clipboardRecentContent->GetRecentURLFromClipboard().has_value() && |
| clipboardRecentContent->GetRecentTextFromClipboard().has_value(); |
| } |
| return NO; |
| } |
| |
| - (void)visitCopiedLink:(id)sender { |
| [self pasteAndGo:sender]; |
| } |
| |
| - (void)searchCopiedText:(id)sender { |
| [self pasteAndGo:sender]; |
| } |
| |
| // Both actions are performed the same, but need to be enabled differently, |
| // so we need two different selectors. |
| - (void)pasteAndGo:(id)sender { |
| NSString* query; |
| if (base::FeatureList::IsEnabled(kCopiedContentBehavior)) { |
| ClipboardRecentContent* clipboardRecentContent = |
| ClipboardRecentContent::GetInstance(); |
| if (base::Optional<GURL> optional_url = |
| clipboardRecentContent->GetRecentURLFromClipboard()) { |
| query = base::SysUTF8ToNSString(optional_url.value().spec()); |
| } else if (base::Optional<base::string16> optional_text = |
| clipboardRecentContent->GetRecentTextFromClipboard()) { |
| query = base::SysUTF16ToNSString(optional_text.value()); |
| } |
| } else { |
| query = UIPasteboard.generalPasteboard.string; |
| } |
| [self.dispatcher loadQuery:query immediately:YES]; |
| [self.dispatcher cancelOmniboxEdit]; |
| } |
| |
| @end |