Commit a9574fb4 authored by stkhapugin@chromium.org's avatar stkhapugin@chromium.org Committed by Commit Bot

Shuffle and group methods in OmniboxTextfieldIOS.

Groups textfield methods so that overrides are together and are marked.

Cq-Include-Trybots: master.tryserver.chromium.mac:ios-simulator-cronet;master.tryserver.chromium.mac:ios-simulator-full-configs
Change-Id: If47169cccf774f51b86776e3084ba99283808d30
Bug: 788636
Reviewed-on: https://chromium-review.googlesource.com/758850
Commit-Queue: Stepan Khapugin <stkhapugin@chromium.org>
Reviewed-by: default avatarMark Cogan <marq@chromium.org>
Cr-Commit-Position: refs/heads/master@{#520983}
parent cacf182e
......@@ -28,10 +28,6 @@ typedef enum {
- (instancetype)initWithCoder:(NSCoder*)aDecoder NS_UNAVAILABLE;
// Delegate getter and setter. Overridden to use OmniboxTextFieldDelegate
// instead of UITextFieldDelegate.
- (id<OmniboxTextFieldDelegate>)delegate;
- (void)setDelegate:(id<OmniboxTextFieldDelegate>)delegate;
// Sets the field's text to |text|. If |userTextLength| is less than the length
// of |text|, the excess is displayed as inline autocompleted text. When the
......@@ -64,15 +60,6 @@ typedef enum {
// on older version of iOS.
- (NSString*)markedText;
// Initial touch on the Omnibox triggers a "pre-edit" state. The current
// URL is shown without any insertion point. First character typed replaces
// the URL. A second touch turns on the insertion point. |preEditStaticLabel|
// is normally hidden. In pre-edit state, |preEditStaticLabel| is unhidden
// and displays the URL that will be edited on the second touch.
- (void)enterPreEditState;
- (void)exitPreEditState;
- (BOOL)isPreEditing;
// Returns the current selected text range as an NSRange.
- (NSRange)selectedNSRange;
......@@ -93,21 +80,34 @@ typedef enum {
// Called when animations added by |-animateFadeWithStyle:| can be removed.
- (void)cleanUpFadeAnimations;
// Redeclare the delegate property to be the more specific
// OmniboxTextFieldDelegate.
// New animations API. Currently are behind a flag since they require iOS 10
// APIs to work. They replace all animations above.
- (void)addExpandOmniboxAnimations:(UIViewPropertyAnimator*)animator
API_AVAILABLE(ios(10.0));
- (void)addContractOmniboxAnimations:(UIViewPropertyAnimator*)animator
API_AVAILABLE(ios(10.0));
// Initial touch on the Omnibox triggers a "pre-edit" state. The current
// URL is shown without any insertion point. First character typed replaces
// the URL. A second touch turns on the insertion point. |preEditStaticLabel|
// is normally hidden. In pre-edit state, |preEditStaticLabel| is unhidden
// and displays the URL that will be edited on the second touch.
- (void)enterPreEditState;
- (void)exitPreEditState;
- (BOOL)isPreEditing;
// The delegate for this textfield. Overridden to use OmniboxTextFieldDelegate
// instead of UITextFieldDelegate.
@property(nonatomic, weak) id<OmniboxTextFieldDelegate> delegate;
// Text displayed when in pre-edit state.
@property(nonatomic, strong) NSString* preEditText;
@property(nonatomic) BOOL clearingPreEditText;
@property(nonatomic, strong) UIColor* selectedTextBackgroundColor;
@property(nonatomic, strong) UIColor* placeholderTextColor;
@property(nonatomic, assign) BOOL incognito;
- (void)addExpandOmniboxAnimations:(UIViewPropertyAnimator*)animator
API_AVAILABLE(ios(10.0));
- (void)addContractOmniboxAnimations:(UIViewPropertyAnimator*)animator
API_AVAILABLE(ios(10.0));
@end
// A category for defining new methods that access private ivars.
......
......@@ -101,6 +101,7 @@ NSString* const kOmniboxFadeAnimationKey = @"OmniboxFadeAnimation";
@synthesize placeholderTextColor = _placeholderTextColor;
@synthesize incognito = _incognito;
#pragma mark - Public methods
// Overload to allow for code-based initialization.
- (instancetype)initWithFrame:(CGRect)frame {
return [self initWithFrame:frame
......@@ -156,6 +157,149 @@ NSString* const kOmniboxFadeAnimationKey = @"OmniboxFadeAnimation";
return nil;
}
- (void)setText:(NSAttributedString*)text
userTextLength:(size_t)userTextLength {
DCHECK_LE(userTextLength, [text length]);
NSUInteger autocompleteLength = [text length] - userTextLength;
[self setTextInternal:text autocompleteLength:autocompleteLength];
}
- (void)insertTextWhileEditing:(NSString*)text {
// This method should only be called while editing.
DCHECK([self isFirstResponder]);
if ([self markedTextRange] != nil)
[self unmarkText];
NSRange selectedNSRange = [self selectedNSRange];
if (![self delegate] || [[self delegate] textField:self
shouldChangeCharactersInRange:selectedNSRange
replacementString:text]) {
[self replaceRange:[self selectedTextRange] withText:text];
}
}
- (base::string16)displayedText {
return base::SysNSStringToUTF16([self nsDisplayedText]);
}
- (base::string16)autocompleteText {
DCHECK_LT([[self text] length], [[_selection text] length])
<< "[_selection text] and [self text] are out of sync. "
<< "Please email justincohen@ and rohitrao@ if you see this.";
if (_selection && [[_selection text] length] > [[self text] length]) {
return base::SysNSStringToUTF16(
[[_selection text] substringFromIndex:[[self text] length]]);
}
return base::string16();
}
- (BOOL)hasAutocompleteText {
return !!_selection;
}
- (void)clearAutocompleteText {
if (_selection) {
[_selection removeFromSuperview];
_selection = nil;
[self showTextAndCursor];
}
}
- (NSString*)markedText {
DCHECK([self conformsToProtocol:@protocol(UITextInput)]);
return [self textInRange:[self markedTextRange]];
}
- (NSRange)selectedNSRange {
DCHECK([self isFirstResponder]);
UITextPosition* beginning = [self beginningOfDocument];
UITextRange* selectedRange = [self selectedTextRange];
NSInteger start =
[self offsetFromPosition:beginning toPosition:[selectedRange start]];
NSInteger length = [self offsetFromPosition:[selectedRange start]
toPosition:[selectedRange end]];
return NSMakeRange(start, length);
}
- (NSTextAlignment)bestTextAlignment {
if ([self isFirstResponder]) {
return [self bestAlignmentForText:[self text]];
}
return NSTextAlignmentNatural;
}
// Normally NSTextAlignmentNatural would handle text alignment automatically,
// but there are numerous edge case issues with it, so it's simpler to just
// manually update the text alignment and writing direction of the UITextField.
- (void)updateTextDirection {
// Setting the empty field to Natural seems to let iOS update the cursor
// position when the keyboard language is changed.
if (![self text].length) {
[self setTextAlignment:NSTextAlignmentNatural];
return;
}
NSTextAlignment alignment = [self bestTextAlignment];
[self setTextAlignment:alignment];
if (!base::ios::IsRunningOnIOS11OrLater()) {
// TODO(crbug.com/730461): Remove this entire block once it's been tested
// on trunk.
UITextWritingDirection writingDirection =
alignment == NSTextAlignmentLeft ? UITextWritingDirectionLeftToRight
: UITextWritingDirectionRightToLeft;
[self
setBaseWritingDirection:writingDirection
forRange:
[self
textRangeFromPosition:[self beginningOfDocument]
toPosition:[self endOfDocument]]];
}
}
- (UIColor*)displayedTextColor {
return _displayedTextColor;
}
#pragma mark animations
- (void)animateFadeWithStyle:(OmniboxTextFieldFadeStyle)style {
// Animation values
BOOL isFadingIn = (style == OMNIBOX_TEXT_FIELD_FADE_STYLE_IN);
CGFloat beginOpacity = isFadingIn ? 0.0 : 1.0;
CGFloat endOpacity = isFadingIn ? 1.0 : 0.0;
CAMediaTimingFunction* opacityTiming = ios::material::TimingFunction(
isFadingIn ? ios::material::CurveEaseOut : ios::material::CurveEaseIn);
CFTimeInterval delay = isFadingIn ? ios::material::kDuration8 : 0.0;
CAAnimation* labelAnimation = OpacityAnimationMake(beginOpacity, endOpacity);
labelAnimation.duration =
isFadingIn ? ios::material::kDuration6 : ios::material::kDuration8;
labelAnimation.timingFunction = opacityTiming;
labelAnimation = DelayedAnimationMake(labelAnimation, delay);
CAAnimation* auxillaryViewAnimation =
OpacityAnimationMake(beginOpacity, endOpacity);
auxillaryViewAnimation.duration = ios::material::kDuration8;
auxillaryViewAnimation.timingFunction = opacityTiming;
auxillaryViewAnimation = DelayedAnimationMake(auxillaryViewAnimation, delay);
for (UIView* subview in self.subviews) {
if ([subview isKindOfClass:[UILabel class]]) {
[subview.layer addAnimation:labelAnimation
forKey:kOmniboxFadeAnimationKey];
} else {
[subview.layer addAnimation:auxillaryViewAnimation
forKey:kOmniboxFadeAnimationKey];
}
}
}
- (void)cleanUpFadeAnimations {
RemoveAnimationForKeyFromLayers(kOmniboxFadeAnimationKey,
[self fadeAnimationLayers]);
}
- (void)addExpandOmniboxAnimations:(UIViewPropertyAnimator*)animator
API_AVAILABLE(ios(10.0)) {
__weak OmniboxTextFieldIOS* weakSelf = self;
......@@ -192,68 +336,7 @@ NSString* const kOmniboxFadeAnimationKey = @"OmniboxFadeAnimation";
}];
}
// Enforces that the delegate is an OmniboxTextFieldDelegate.
- (id<OmniboxTextFieldDelegate>)delegate {
id delegate = [super delegate];
DCHECK(delegate == nil ||
[[delegate class]
conformsToProtocol:@protocol(OmniboxTextFieldDelegate)]);
return delegate;
}
// Overridden to require an OmniboxTextFieldDelegate.
- (void)setDelegate:(id<OmniboxTextFieldDelegate>)delegate {
[super setDelegate:delegate];
}
// Exposed for testing.
- (UILabel*)preEditStaticLabel {
return _preEditStaticLabel;
}
- (void)insertTextWhileEditing:(NSString*)text {
// This method should only be called while editing.
DCHECK([self isFirstResponder]);
if ([self markedTextRange] != nil)
[self unmarkText];
NSRange selectedNSRange = [self selectedNSRange];
if (![self delegate] || [[self delegate] textField:self
shouldChangeCharactersInRange:selectedNSRange
replacementString:text]) {
[self replaceRange:[self selectedTextRange] withText:text];
}
}
// Method called when the users touches the text input. This will accept the
// autocompleted text.
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
if ([self isPreEditing]) {
[self exitPreEditState];
[super selectAll:nil];
}
if (!_selection) {
[super touchesBegan:touches withEvent:event];
return;
}
// Only consider a single touch.
UITouch* touch = [touches anyObject];
if (!touch)
return;
// Accept selection.
NSString* newText = [[self nsDisplayedText] copy];
[self clearAutocompleteText];
[self setText:newText];
}
// Gets the bounds of the rect covering the URL.
- (CGRect)preEditLabelRectForBounds:(CGRect)bounds {
return [self editingRectForBounds:self.bounds];
}
#pragma mark pre-edit
// Creates a UILabel based on the current dimension of the text field and
// displays the URL in the UILabel so it appears properly aligned to the URL.
......@@ -295,36 +378,215 @@ NSString* const kOmniboxFadeAnimationKey = @"OmniboxFadeAnimation";
[self addSubview:_preEditStaticLabel];
}
- (NSTextAlignment)bestAlignmentForText:(NSString*)text {
if (text.length) {
NSString* lang = CFBridgingRelease(CFStringTokenizerCopyBestStringLanguage(
(CFStringRef)text, CFRangeMake(0, text.length)));
// Finishes pre-edit state by removing the UILabel with the URL.
- (void)exitPreEditState {
[self setPreEditText:nil];
if (_preEditStaticLabel) {
[_preEditStaticLabel removeFromSuperview];
_preEditStaticLabel = nil;
[self showTextAndCursor];
}
}
if ([NSLocale characterDirectionForLanguage:lang] ==
NSLocaleLanguageDirectionRightToLeft) {
return NSTextAlignmentRight;
// Returns whether we are processing the first touch event on the text field.
- (BOOL)isPreEditing {
return !![self preEditText];
}
#pragma mark - TestingUtilities category
// Exposed for testing.
- (UILabel*)preEditStaticLabel {
return _preEditStaticLabel;
}
#pragma mark - Properties
// Enforces that the delegate is an OmniboxTextFieldDelegate.
- (id<OmniboxTextFieldDelegate>)delegate {
id delegate = [super delegate];
DCHECK(delegate == nil ||
[[delegate class]
conformsToProtocol:@protocol(OmniboxTextFieldDelegate)]);
return delegate;
}
// Overridden to require an OmniboxTextFieldDelegate.
- (void)setDelegate:(id<OmniboxTextFieldDelegate>)delegate {
[super setDelegate:delegate];
}
#pragma mark - Private methods
#pragma mark - UITextField
// Ensures that attributedText always uses the proper style attributes.
- (void)setAttributedText:(NSAttributedString*)attributedText {
NSMutableAttributedString* mutableText = [attributedText mutableCopy];
NSRange entireString = NSMakeRange(0, [mutableText length]);
// Set the font.
[mutableText addAttribute:NSFontAttributeName value:_font range:entireString];
// When editing, use the default text color for all text.
if (self.editing) {
// Hide the text when the |_selection| label is displayed.
UIColor* textColor =
_selection ? [UIColor clearColor] : _displayedTextColor;
[mutableText addAttribute:NSForegroundColorAttributeName
value:textColor
range:entireString];
} else {
NSMutableParagraphStyle* style = [[NSMutableParagraphStyle alloc] init];
// URLs have their text direction set to to LTR (avoids RTL characters
// making the URL render from right to left, as per the URL rendering
// standard described here: https://url.spec.whatwg.org/#url-rendering
[style setBaseWritingDirection:NSWritingDirectionLeftToRight];
// Set linebreak mode to 'clipping' to ensure the text is never elided.
// This is a workaround for iOS 6, where it appears that
// [self.attributedText size] is not wide enough for the string (e.g. a URL
// else ending with '.com' will be elided to end with '.c...'). It appears
// to be off by one point so clipping is acceptable as it doesn't actually
// cut off any of the text.
[style setLineBreakMode:NSLineBreakByClipping];
[mutableText addAttribute:NSParagraphStyleAttributeName
value:style
range:entireString];
}
[super setAttributedText:mutableText];
}
- (void)setPlaceholder:(NSString*)placeholder {
if (placeholder && _placeholderTextColor) {
NSDictionary* attributes =
@{NSForegroundColorAttributeName : _placeholderTextColor};
self.attributedPlaceholder =
[[NSAttributedString alloc] initWithString:placeholder
attributes:attributes];
} else {
[super setPlaceholder:placeholder];
}
}
- (void)setText:(NSString*)text {
NSAttributedString* as = [[NSAttributedString alloc] initWithString:text];
if (self.text.length > 0 && as.length == 0) {
// Remove the fade animations before the subviews are removed.
[self cleanUpFadeAnimations];
}
[self setTextInternal:as autocompleteLength:0];
}
- (CGRect)textRectForBounds:(CGRect)bounds {
CGRect newBounds = [super textRectForBounds:bounds];
LayoutRect textRectLayout =
LayoutRectForRectInBoundingRect(newBounds, bounds);
CGFloat textInset = kTextInsetNoLeftView;
// Shift the text right and reduce the width to create empty space between the
// left view and the omnibox text.
textRectLayout.position.leading += textInset + kTextAreaLeadingOffset;
textRectLayout.size.width -= textInset - kTextAreaLeadingOffset;
if (IsIPadIdiom()) {
if (!IsCompactTablet()) {
// Adjust the width so that the text doesn't overlap with the bookmark and
// voice search buttons which are displayed inside the omnibox.
textRectLayout.size.width += self.rightView.bounds.size.width -
kVoiceSearchButtonWidth - kStarButtonWidth;
}
}
return NSTextAlignmentLeft;
return LayoutRectGetRect(textRectLayout);
}
- (NSTextAlignment)bestTextAlignment {
if ([self isFirstResponder]) {
return [self bestAlignmentForText:[self text]];
- (CGRect)editingRectForBounds:(CGRect)bounds {
CGRect newBounds = [super editingRectForBounds:bounds];
// -editingRectForBounds doesn't account for rightViews that aren't flush
// with the right edge, it just looks at the rightView's width. Account for
// the offset here.
CGFloat rightViewMaxX = CGRectGetMaxX([self rightViewRectForBounds:bounds]);
if (rightViewMaxX)
newBounds.size.width -= bounds.size.width - rightViewMaxX;
LayoutRect editingRectLayout =
LayoutRectForRectInBoundingRect(newBounds, bounds);
editingRectLayout.position.leading += kTextAreaLeadingOffset;
editingRectLayout.position.leading += kTextInset;
editingRectLayout.size.width -= kTextInset + kEditingRectWidthInset;
if (IsIPadIdiom()) {
if (!IsCompactTablet() && !self.rightView) {
// Normally the clear button shrinks the edit box, but if the rightView
// isn't set, shrink behind the mic icons.
editingRectLayout.size.width -= kVoiceSearchButtonWidth;
}
} else {
CGFloat xDiff = editingRectLayout.position.leading - kEditingRectX;
editingRectLayout.position.leading = kEditingRectX;
editingRectLayout.size.width += xDiff;
}
return NSTextAlignmentNatural;
// Don't let the edit rect extend over the clear button. The right view
// is hidden during animations, so fake its width here.
if (self.rightViewMode == UITextFieldViewModeNever)
editingRectLayout.size.width -= self.rightView.bounds.size.width;
newBounds = LayoutRectGetRect(editingRectLayout);
// Position the selection view appropriately.
[_selection setFrame:newBounds];
return newBounds;
}
// Enumerate url components (host, path) and draw each one in different rect.
- (void)drawTextInRect:(CGRect)rect {
if (base::ios::IsRunningOnOrLater(11, 1, 0)) {
// -[UITextField drawTextInRect:] ignores the argument, so we can't do
// anything on 11.1 and up.
[super drawTextInRect:rect];
return;
}
// Save and restore the graphics state because rectForDrawTextInRect may
// apply an image mask to fade out beginning and/or end of the URL.
gfx::ScopedCGContextSaveGState saver(UIGraphicsGetCurrentContext());
[super drawTextInRect:[self rectForDrawTextInRect:rect]];
}
// Overriding this method to offset the rightView property
// (containing a clear text button).
- (CGRect)rightViewRectForBounds:(CGRect)bounds {
// iOS9 added updated RTL support, but only half implemented it for
// UITextField. leftView and rightView were not renamed, but are are correctly
// swapped and treated as leadingView / trailingView. However,
// -leftViewRectForBounds and -rightViewRectForBounds are *not* treated as
// leading and trailing. Hence the swapping below.
if ([self isTextFieldLTR]) {
return [self layoutRightViewForBounds:bounds];
}
return [self layoutLeftViewForBounds:bounds];
}
// Overriding this method to offset the leftView property
// (containing a placeholder image) consistently with omnibox text padding.
- (CGRect)leftViewRectForBounds:(CGRect)bounds {
// iOS9 added updated RTL support, but only half implemented it for
// UITextField. leftView and rightView were not renamed, but are are correctly
// swapped and treated as leadingView / trailingView. However,
// -leftViewRectForBounds and -rightViewRectForBounds are *not* treated as
// leading and trailing. Hence the swapping below.
if ([self isTextFieldLTR]) {
return [self layoutLeftViewForBounds:bounds];
}
return [self layoutRightViewForBounds:bounds];
}
- (NSTextAlignment)preEditTextAlignment {
// If the pre-edit text is wider than the omnibox, right-align the text so it
// ends at the same x coord as the blue selection box.
CGSize textSize =
[_preEditStaticLabel.text cr_pixelAlignedSizeWithFont:_font];
// Note, this does not need to support RTL, as URLs are always LTR.
return textSize.width < _preEditStaticLabel.frame.size.width
? NSTextAlignmentLeft
: NSTextAlignmentRight;
}
#pragma mark - UIView
- (void)layoutSubviews {
[super layoutSubviews];
......@@ -340,44 +602,38 @@ NSString* const kOmniboxFadeAnimationKey = @"OmniboxFadeAnimation";
}
}
// Finishes pre-edit state by removing the UILabel with the URL.
- (void)exitPreEditState {
[self setPreEditText:nil];
if (_preEditStaticLabel) {
[_preEditStaticLabel removeFromSuperview];
_preEditStaticLabel = nil;
[self showTextAndCursor];
}
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event {
// Anything in the narrow bar above OmniboxTextFieldIOS view
// will also activate the text field.
if (point.y < 0)
point.y = 0;
return [super hitTest:point withEvent:event];
}
- (UIColor*)displayedTextColor {
return _displayedTextColor;
}
#pragma mark - UIResponder
// Returns whether we are processing the first touch event on the text field.
- (BOOL)isPreEditing {
return !![self preEditText];
}
// Method called when the users touches the text input. This will accept the
// autocompleted text.
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
if ([self isPreEditing]) {
[self exitPreEditState];
[super selectAll:nil];
}
- (NSString*)nsDisplayedText {
if (_selection)
return [_selection text];
return [self text];
}
if (!_selection) {
[super touchesBegan:touches withEvent:event];
return;
}
- (base::string16)displayedText {
return base::SysNSStringToUTF16([self nsDisplayedText]);
}
// Only consider a single touch.
UITouch* touch = [touches anyObject];
if (!touch)
return;
- (base::string16)autocompleteText {
DCHECK_LT([[self text] length], [[_selection text] length])
<< "[_selection text] and [self text] are out of sync. "
<< "Please email justincohen@ and rohitrao@ if you see this.";
if (_selection && [[_selection text] length] > [[self text] length]) {
return base::SysNSStringToUTF16(
[[_selection text] substringFromIndex:[[self text] length]]);
}
return base::string16();
// Accept selection.
NSString* newText = [[self nsDisplayedText] copy];
[self clearAutocompleteText];
[self setText:newText];
}
- (void)select:(id)sender {
......@@ -399,6 +655,94 @@ NSString* const kOmniboxFadeAnimationKey = @"OmniboxFadeAnimation";
[super selectAll:sender];
}
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
// Disable the "Define" menu item. iOS7 implements this with a private
// selector. Avoid using private APIs by instead doing a string comparison.
if ([NSStringFromSelector(action) hasSuffix:@"define:"]) {
return NO;
}
// Disable the RTL arrow menu item. The omnibox sets alignment based on the
// text in the field, and should not be overridden.
if ([NSStringFromSelector(action) hasPrefix:@"makeTextWritingDirection"]) {
return NO;
}
return [super canPerformAction:action withSender:sender];
}
#pragma mark Copy/Paste
// Overridden to allow for custom omnibox copy behavior. This includes
// preprending http:// to the copied URL if needed.
- (void)copy:(id)sender {
id<OmniboxTextFieldDelegate> delegate = [self delegate];
BOOL handled = NO;
// Must test for the onCopy method, since it's optional.
if ([delegate respondsToSelector:@selector(onCopy)])
handled = [delegate onCopy];
// iOS 4 doesn't expose an API that allows the delegate to handle the copy
// operation, so let the superclass perform the copy if the delegate couldn't.
if (!handled)
[super copy:sender];
}
// Overridden to notify the delegate that a paste is in progress.
- (void)paste:(id)sender {
id delegate = [self delegate];
if ([delegate respondsToSelector:@selector(willPaste)])
[delegate willPaste];
[super paste:sender];
}
#pragma mark UIKeyInput
- (void)deleteBackward {
// Must test for the onDeleteBackward method, since it's optional.
if ([[self delegate] respondsToSelector:@selector(onDeleteBackward)])
[[self delegate] onDeleteBackward];
[super deleteBackward];
}
#pragma mark - helpers
// Gets the bounds of the rect covering the URL.
- (CGRect)preEditLabelRectForBounds:(CGRect)bounds {
return [self editingRectForBounds:self.bounds];
}
- (NSTextAlignment)bestAlignmentForText:(NSString*)text {
if (text.length) {
NSString* lang = CFBridgingRelease(CFStringTokenizerCopyBestStringLanguage(
(CFStringRef)text, CFRangeMake(0, text.length)));
if ([NSLocale characterDirectionForLanguage:lang] ==
NSLocaleLanguageDirectionRightToLeft) {
return NSTextAlignmentRight;
}
}
return NSTextAlignmentLeft;
}
- (NSTextAlignment)preEditTextAlignment {
// If the pre-edit text is wider than the omnibox, right-align the text so it
// ends at the same x coord as the blue selection box.
CGSize textSize =
[_preEditStaticLabel.text cr_pixelAlignedSizeWithFont:_font];
// Note, this does not need to support RTL, as URLs are always LTR.
return textSize.width < _preEditStaticLabel.frame.size.width
? NSTextAlignmentLeft
: NSTextAlignmentRight;
}
- (NSString*)nsDisplayedText {
if (_selection)
return [_selection text];
return [self text];
}
// Creates the SelectedTextLabel if it doesn't already exist and adds it as a
// subview.
- (void)createSelectionViewIfNecessary {
......@@ -414,13 +758,6 @@ NSString* const kOmniboxFadeAnimationKey = @"OmniboxFadeAnimation";
[self hideTextAndCursor];
}
- (void)deleteBackward {
// Must test for the onDeleteBackward method, since it's optional.
if ([[self delegate] respondsToSelector:@selector(onDeleteBackward)])
[[self delegate] onDeleteBackward];
[super deleteBackward];
}
// Helper method used to set the text of this field. Updates the selection view
// to contain the correct inline autocomplete text.
- (void)setTextInternal:(NSAttributedString*)text
......@@ -470,132 +807,24 @@ NSString* const kOmniboxFadeAnimationKey = @"OmniboxFadeAnimation";
(!self.editing || ![self.text isEqualToString:fieldText.string] ||
autocompleteLength == 0);
}
if (updateText) {
self.attributedText = fieldText;
}
// iOS changes the font to .LastResort when some unexpected unicode strings
// are used (e.g. 𝗲𝗺𝗽𝗵𝗮𝘀𝗶𝘀). Setting the NSFontAttributeName in the
// attributed string to -systemFontOfSize fixes part of the problem, but the
// baseline changes so text is out of alignment.
[self setFont:_font];
[self updateTextDirection];
}
- (UIColor*)selectedTextBackgroundColor {
return _selectedTextBackgroundColor ? _selectedTextBackgroundColor
: [UIColor colorWithRed:204.0 / 255
green:221.0 / 255
blue:237.0 / 255
alpha:1.0];
}
// Ensures that attributedText always uses the proper style attributes.
- (void)setAttributedText:(NSAttributedString*)attributedText {
NSMutableAttributedString* mutableText = [attributedText mutableCopy];
NSRange entireString = NSMakeRange(0, [mutableText length]);
// Set the font.
[mutableText addAttribute:NSFontAttributeName value:_font range:entireString];
// When editing, use the default text color for all text.
if (self.editing) {
// Hide the text when the |_selection| label is displayed.
UIColor* textColor =
_selection ? [UIColor clearColor] : _displayedTextColor;
[mutableText addAttribute:NSForegroundColorAttributeName
value:textColor
range:entireString];
} else {
NSMutableParagraphStyle* style = [[NSMutableParagraphStyle alloc] init];
// URLs have their text direction set to to LTR (avoids RTL characters
// making the URL render from right to left, as per the URL rendering
// standard described here: https://url.spec.whatwg.org/#url-rendering
[style setBaseWritingDirection:NSWritingDirectionLeftToRight];
// Set linebreak mode to 'clipping' to ensure the text is never elided.
// This is a workaround for iOS 6, where it appears that
// [self.attributedText size] is not wide enough for the string (e.g. a URL
// else ending with '.com' will be elided to end with '.c...'). It appears
// to be off by one point so clipping is acceptable as it doesn't actually
// cut off any of the text.
[style setLineBreakMode:NSLineBreakByClipping];
[mutableText addAttribute:NSParagraphStyleAttributeName
value:style
range:entireString];
}
[super setAttributedText:mutableText];
}
// Normally NSTextAlignmentNatural would handle text alignment automatically,
// but there are numerous edge case issues with it, so it's simpler to just
// manually update the text alignment and writing direction of the UITextField.
- (void)updateTextDirection {
// Setting the empty field to Natural seems to let iOS update the cursor
// position when the keyboard language is changed.
if (![self text].length) {
[self setTextAlignment:NSTextAlignmentNatural];
return;
}
NSTextAlignment alignment = [self bestTextAlignment];
[self setTextAlignment:alignment];
if (!base::ios::IsRunningOnIOS11OrLater()) {
// TODO(crbug.com/730461): Remove this entire block once it's been tested
// on trunk.
UITextWritingDirection writingDirection =
alignment == NSTextAlignmentLeft ? UITextWritingDirectionLeftToRight
: UITextWritingDirectionRightToLeft;
[self
setBaseWritingDirection:writingDirection
forRange:
[self
textRangeFromPosition:[self beginningOfDocument]
toPosition:[self endOfDocument]]];
}
}
- (void)setPlaceholder:(NSString*)placeholder {
if (placeholder && _placeholderTextColor) {
NSDictionary* attributes =
@{NSForegroundColorAttributeName : _placeholderTextColor};
self.attributedPlaceholder =
[[NSAttributedString alloc] initWithString:placeholder
attributes:attributes];
} else {
[super setPlaceholder:placeholder];
}
}
- (void)setText:(NSString*)text {
NSAttributedString* as = [[NSAttributedString alloc] initWithString:text];
if (self.text.length > 0 && as.length == 0) {
// Remove the fade animations before the subviews are removed.
[self cleanUpFadeAnimations];
if (updateText) {
self.attributedText = fieldText;
}
[self setTextInternal:as autocompleteLength:0];
}
- (void)setText:(NSAttributedString*)text
userTextLength:(size_t)userTextLength {
DCHECK_LE(userTextLength, [text length]);
NSUInteger autocompleteLength = [text length] - userTextLength;
[self setTextInternal:text autocompleteLength:autocompleteLength];
}
- (BOOL)hasAutocompleteText {
return !!_selection;
// iOS changes the font to .LastResort when some unexpected unicode strings
// are used (e.g. 𝗲𝗺𝗽𝗵𝗮𝘀𝗶𝘀). Setting the NSFontAttributeName in the
// attributed string to -systemFontOfSize fixes part of the problem, but the
// baseline changes so text is out of alignment.
[self setFont:_font];
[self updateTextDirection];
}
- (void)clearAutocompleteText {
if (_selection) {
[_selection removeFromSuperview];
_selection = nil;
[self showTextAndCursor];
}
- (UIColor*)selectedTextBackgroundColor {
return _selectedTextBackgroundColor ? _selectedTextBackgroundColor
: [UIColor colorWithRed:204.0 / 255
green:221.0 / 255
blue:237.0 / 255
alpha:1.0];
}
- (BOOL)isColorHidden:(UIColor*)color {
......@@ -621,74 +850,6 @@ NSString* const kOmniboxFadeAnimationKey = @"OmniboxFadeAnimation";
[self setTextColor:[UIColor clearColor]];
}
- (NSString*)markedText {
DCHECK([self conformsToProtocol:@protocol(UITextInput)]);
return [self textInRange:[self markedTextRange]];
}
- (CGRect)textRectForBounds:(CGRect)bounds {
CGRect newBounds = [super textRectForBounds:bounds];
LayoutRect textRectLayout =
LayoutRectForRectInBoundingRect(newBounds, bounds);
CGFloat textInset = kTextInsetNoLeftView;
// Shift the text right and reduce the width to create empty space between the
// left view and the omnibox text.
textRectLayout.position.leading += textInset + kTextAreaLeadingOffset;
textRectLayout.size.width -= textInset - kTextAreaLeadingOffset;
if (IsIPadIdiom()) {
if (!IsCompactTablet()) {
// Adjust the width so that the text doesn't overlap with the bookmark and
// voice search buttons which are displayed inside the omnibox.
textRectLayout.size.width += self.rightView.bounds.size.width -
kVoiceSearchButtonWidth - kStarButtonWidth;
}
}
return LayoutRectGetRect(textRectLayout);
}
- (CGRect)editingRectForBounds:(CGRect)bounds {
CGRect newBounds = [super editingRectForBounds:bounds];
// -editingRectForBounds doesn't account for rightViews that aren't flush
// with the right edge, it just looks at the rightView's width. Account for
// the offset here.
CGFloat rightViewMaxX = CGRectGetMaxX([self rightViewRectForBounds:bounds]);
if (rightViewMaxX)
newBounds.size.width -= bounds.size.width - rightViewMaxX;
LayoutRect editingRectLayout =
LayoutRectForRectInBoundingRect(newBounds, bounds);
editingRectLayout.position.leading += kTextAreaLeadingOffset;
editingRectLayout.position.leading += kTextInset;
editingRectLayout.size.width -= kTextInset + kEditingRectWidthInset;
if (IsIPadIdiom()) {
if (!IsCompactTablet() && !self.rightView) {
// Normally the clear button shrinks the edit box, but if the rightView
// isn't set, shrink behind the mic icons.
editingRectLayout.size.width -= kVoiceSearchButtonWidth;
}
} else {
CGFloat xDiff = editingRectLayout.position.leading - kEditingRectX;
editingRectLayout.position.leading = kEditingRectX;
editingRectLayout.size.width += xDiff;
}
// Don't let the edit rect extend over the clear button. The right view
// is hidden during animations, so fake its width here.
if (self.rightViewMode == UITextFieldViewModeNever)
editingRectLayout.size.width -= self.rightView.bounds.size.width;
newBounds = LayoutRectGetRect(editingRectLayout);
// Position the selection view appropriately.
[_selection setFrame:newBounds];
return newBounds;
}
- (CGRect)rectForDrawTextInRect:(CGRect)rect {
// The goal is to always show the most significant part of the hostname
// (i.e. the end of the TLD).
......@@ -752,27 +913,11 @@ NSString* const kOmniboxFadeAnimationKey = @"OmniboxFadeAnimation";
return rect;
}
// Enumerate url components (host, path) and draw each one in different rect.
- (void)drawTextInRect:(CGRect)rect {
if (base::ios::IsRunningOnOrLater(11, 1, 0)) {
// -[UITextField drawTextInRect:] ignores the argument, so we can't do
// anything on 11.1 and up.
[super drawTextInRect:rect];
return;
}
// Save and restore the graphics state because rectForDrawTextInRect may
// apply an image mask to fade out beginning and/or end of the URL.
gfx::ScopedCGContextSaveGState saver(UIGraphicsGetCurrentContext());
[super drawTextInRect:[self rectForDrawTextInRect:rect]];
}
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event {
// Anything in the narrow bar above OmniboxTextFieldIOS view
// will also activate the text field.
if (point.y < 0)
point.y = 0;
return [super hitTest:point withEvent:event];
- (NSArray*)fadeAnimationLayers {
NSMutableArray* layers = [NSMutableArray array];
for (UIView* subview in self.subviews)
[layers addObject:subview.layer];
return layers;
}
- (BOOL)isTextFieldLTR {
......@@ -781,20 +926,6 @@ NSString* const kOmniboxFadeAnimationKey = @"OmniboxFadeAnimation";
UIUserInterfaceLayoutDirectionLeftToRight;
}
// Overriding this method to offset the rightView property
// (containing a clear text button).
- (CGRect)rightViewRectForBounds:(CGRect)bounds {
// iOS9 added updated RTL support, but only half implemented it for
// UITextField. leftView and rightView were not renamed, but are are correctly
// swapped and treated as leadingView / trailingView. However,
// -leftViewRectForBounds and -rightViewRectForBounds are *not* treated as
// leading and trailing. Hence the swapping below.
if ([self isTextFieldLTR]) {
return [self layoutRightViewForBounds:bounds];
}
return [self layoutLeftViewForBounds:bounds];
}
- (CGRect)layoutRightViewForBounds:(CGRect)bounds {
if ([self rightView]) {
CGSize rightViewSize = self.rightView.bounds.size;
......@@ -817,118 +948,8 @@ NSString* const kOmniboxFadeAnimationKey = @"OmniboxFadeAnimation";
return CGRectZero;
}
// Overriding this method to offset the leftView property
// (containing a placeholder image) consistently with omnibox text padding.
- (CGRect)leftViewRectForBounds:(CGRect)bounds {
// iOS9 added updated RTL support, but only half implemented it for
// UITextField. leftView and rightView were not renamed, but are are correctly
// swapped and treated as leadingView / trailingView. However,
// -leftViewRectForBounds and -rightViewRectForBounds are *not* treated as
// leading and trailing. Hence the swapping below.
if ([self isTextFieldLTR]) {
return [self layoutLeftViewForBounds:bounds];
}
return [self layoutRightViewForBounds:bounds];
}
- (CGRect)layoutLeftViewForBounds:(CGRect)bounds {
return CGRectZero;
}
- (void)animateFadeWithStyle:(OmniboxTextFieldFadeStyle)style {
// Animation values
BOOL isFadingIn = (style == OMNIBOX_TEXT_FIELD_FADE_STYLE_IN);
CGFloat beginOpacity = isFadingIn ? 0.0 : 1.0;
CGFloat endOpacity = isFadingIn ? 1.0 : 0.0;
CAMediaTimingFunction* opacityTiming = ios::material::TimingFunction(
isFadingIn ? ios::material::CurveEaseOut : ios::material::CurveEaseIn);
CFTimeInterval delay = isFadingIn ? ios::material::kDuration8 : 0.0;
CAAnimation* labelAnimation = OpacityAnimationMake(beginOpacity, endOpacity);
labelAnimation.duration =
isFadingIn ? ios::material::kDuration6 : ios::material::kDuration8;
labelAnimation.timingFunction = opacityTiming;
labelAnimation = DelayedAnimationMake(labelAnimation, delay);
CAAnimation* auxillaryViewAnimation =
OpacityAnimationMake(beginOpacity, endOpacity);
auxillaryViewAnimation.duration = ios::material::kDuration8;
auxillaryViewAnimation.timingFunction = opacityTiming;
auxillaryViewAnimation = DelayedAnimationMake(auxillaryViewAnimation, delay);
for (UIView* subview in self.subviews) {
if ([subview isKindOfClass:[UILabel class]]) {
[subview.layer addAnimation:labelAnimation
forKey:kOmniboxFadeAnimationKey];
} else {
[subview.layer addAnimation:auxillaryViewAnimation
forKey:kOmniboxFadeAnimationKey];
}
}
}
- (NSArray*)fadeAnimationLayers {
NSMutableArray* layers = [NSMutableArray array];
for (UIView* subview in self.subviews)
[layers addObject:subview.layer];
return layers;
}
- (void)cleanUpFadeAnimations {
RemoveAnimationForKeyFromLayers(kOmniboxFadeAnimationKey,
[self fadeAnimationLayers]);
}
#pragma mark - Copy/Paste
// Overridden to allow for custom omnibox copy behavior. This includes
// preprending http:// to the copied URL if needed.
- (void)copy:(id)sender {
id<OmniboxTextFieldDelegate> delegate = [self delegate];
BOOL handled = NO;
// Must test for the onCopy method, since it's optional.
if ([delegate respondsToSelector:@selector(onCopy)])
handled = [delegate onCopy];
// iOS 4 doesn't expose an API that allows the delegate to handle the copy
// operation, so let the superclass perform the copy if the delegate couldn't.
if (!handled)
[super copy:sender];
}
// Overridden to notify the delegate that a paste is in progress.
- (void)paste:(id)sender {
id delegate = [self delegate];
if ([delegate respondsToSelector:@selector(willPaste)])
[delegate willPaste];
[super paste:sender];
}
- (NSRange)selectedNSRange {
DCHECK([self isFirstResponder]);
UITextPosition* beginning = [self beginningOfDocument];
UITextRange* selectedRange = [self selectedTextRange];
NSInteger start =
[self offsetFromPosition:beginning toPosition:[selectedRange start]];
NSInteger length = [self offsetFromPosition:[selectedRange start]
toPosition:[selectedRange end]];
return NSMakeRange(start, length);
}
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
// Disable the "Define" menu item. iOS7 implements this with a private
// selector. Avoid using private APIs by instead doing a string comparison.
if ([NSStringFromSelector(action) hasSuffix:@"define:"]) {
return NO;
}
// Disable the RTL arrow menu item. The omnibox sets alignment based on the
// text in the field, and should not be overridden.
if ([NSStringFromSelector(action) hasPrefix:@"makeTextWritingDirection"]) {
return NO;
}
return [super canPerformAction:action withSender:sender];
}
@end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment