Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(363)

Unified Diff: ios/chrome/browser/ui/infobars/infobar_view.mm

Issue 2590473002: Upstream Chrome on iOS source code [5/11]. (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: ios/chrome/browser/ui/infobars/infobar_view.mm
diff --git a/ios/chrome/browser/ui/infobars/infobar_view.mm b/ios/chrome/browser/ui/infobars/infobar_view.mm
new file mode 100644
index 0000000000000000000000000000000000000000..7248230b466f752345b418e3629180de1d7bcdf4
--- /dev/null
+++ b/ios/chrome/browser/ui/infobars/infobar_view.mm
@@ -0,0 +1,952 @@
+// Copyright (c) 2012 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/infobars/infobar_view.h"
+
+#import <CoreGraphics/CoreGraphics.h>
+#import <QuartzCore/QuartzCore.h>
+
+#include "base/format_macros.h"
+#include "base/i18n/rtl.h"
+#include "base/ios/weak_nsobject.h"
+#include "base/logging.h"
+#include "base/mac/foundation_util.h"
+#include "base/strings/sys_string_conversions.h"
+#include "components/strings/grit/components_strings.h"
+#import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h"
+#include "ios/chrome/browser/ui/ui_util.h"
+#import "ios/chrome/browser/ui/uikit_ui_util.h"
+#import "ios/chrome/browser/ui/util/label_link_controller.h"
+#import "ios/public/provider/chrome/browser/ui/infobar_view_delegate.h"
+#import "ios/third_party/material_components_ios/src/components/Buttons/src/MaterialButtons.h"
+#import "ios/third_party/material_components_ios/src/components/Typography/src/MaterialTypography.h"
+#include "ui/base/l10n/l10n_util.h"
+#import "ui/gfx/ios/NSString+CrStringDrawing.h"
+#import "ui/gfx/ios/uikit_util.h"
+#include "url/gurl.h"
+
+namespace {
+
+const char kChromeInfobarURL[] = "chromeinternal://infobar/";
+
+// UX configuration constants for the shadow/rounded corners on the icon.
+const CGFloat kBaseSizeForEffects = 57.0;
+const CGFloat kCornerRadius = 10.0;
+const CGFloat kShadowVerticalOffset = 1.0;
+const CGFloat kShadowOpacity = 0.5;
+const CGFloat kShadowRadius = 0.8;
+
+// UX configuration for the layout of items.
+const CGFloat kLeftMarginOnFirstLineWhenIconAbsent = 20.0;
+const CGFloat kMinimumSpaceBetweenRightAndLeftAlignedWidgets = 30.0;
+const CGFloat kRightMargin = 10.0;
+const CGFloat kSpaceBetweenWidgets = 10.0;
+const CGFloat kCloseButtonInnerPadding = 16.0;
+const CGFloat kButtonHeight = 36.0;
+const CGFloat kButtonMargin = 16.0;
+const CGFloat kExtraButtonMarginOnSingleLine = 8.0;
+const CGFloat kButtonSpacing = 8.0;
+const CGFloat kButtonWidthUnits = 8.0;
+const CGFloat kButtonsTopMargin = kCloseButtonInnerPadding;
+const CGFloat kCloseButtonLeftMargin = 16.0;
+const CGFloat kLabelLineSpacing = 5.0;
+const CGFloat kLabelMarginBottom = 22.0;
+const CGFloat kExtraMarginBetweenLabelAndButton = 8.0;
+const CGFloat kLabelMarginTop = kButtonsTopMargin + 5.0; // Baseline lowered.
+const CGFloat kMinimumInfobarHeight = 68.0;
+
+const int kButton2TitleColor = 0x4285f4;
+
+enum InfoBarButtonPosition { ON_FIRST_LINE, CENTER, LEFT, RIGHT };
+
+} // namespace
+
+// UIView containing a switch and a label.
+@interface SwitchView : BidiContainerView
+
+// Initialize the view's label with |labelText|.
+- (id)initWithLabel:(NSString*)labelText isOn:(BOOL)isOn;
+
+// Specifies the object, action, and tag used when the switch is toggled.
+- (void)setTag:(NSInteger)tag target:(id)target action:(SEL)action;
+
+// Returns the height taken by the view constrained by a width of |width|.
+// If |layout| is yes, it sets the frame of the label and the switch to fit
+// |width|.
+- (CGFloat)heightRequiredForSwitchWithWidth:(CGFloat)width layout:(BOOL)layout;
+
+// Returns the preferred width. A smaller width requires eliding the text.
+- (CGFloat)preferredWidth;
+
+@end
+
+@implementation SwitchView {
+ base::scoped_nsobject<UILabel> label_;
+ base::scoped_nsobject<UISwitch> switch_;
+ CGFloat preferredTotalWidth_;
+ CGFloat preferredLabelWidth_;
+}
+
+- (id)initWithLabel:(NSString*)labelText isOn:(BOOL)isOn {
+ // Creates switch and label.
+ base::scoped_nsobject<UILabel> tempLabel(
+ [[UILabel alloc] initWithFrame:CGRectZero]);
+ [tempLabel setTextAlignment:NSTextAlignmentNatural];
+ [tempLabel setFont:[MDCTypography body1Font]];
+ [tempLabel setText:labelText];
+ [tempLabel setBackgroundColor:[UIColor clearColor]];
+ [tempLabel setLineBreakMode:NSLineBreakByWordWrapping];
+ [tempLabel setNumberOfLines:0];
+ [tempLabel setAdjustsFontSizeToFitWidth:NO];
+ base::scoped_nsobject<UISwitch> tempSwitch(
+ [[UISwitch alloc] initWithFrame:CGRectZero]);
+ [tempSwitch setExclusiveTouch:YES];
+ [tempSwitch setAccessibilityLabel:labelText];
+ [tempSwitch setOnTintColor:[[MDCPalette cr_bluePalette] tint500]];
+ [tempSwitch setOn:isOn];
+
+ // Computes the size and initializes the view.
+ CGSize maxSize = CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX);
+ CGSize labelSize =
+ [[tempLabel text] cr_boundingSizeWithSize:maxSize font:[tempLabel font]];
+ CGSize switchSize = [tempSwitch frame].size;
+ CGRect frameRect = CGRectMake(
+ 0, 0, labelSize.width + kSpaceBetweenWidgets + switchSize.width,
+ std::max(labelSize.height, switchSize.height));
+ self = [super initWithFrame:frameRect];
+ if (!self)
+ return nil;
+ label_.reset([tempLabel retain]);
+ switch_.reset([tempSwitch retain]);
+
+ // Sets the position of the label and the switch. The label is left aligned
+ // and the switch is right aligned. Both are vertically centered.
+ CGRect labelFrame =
+ CGRectMake(0, (self.frame.size.height - labelSize.height) / 2,
+ labelSize.width, labelSize.height);
+ CGRect switchFrame =
+ CGRectMake(self.frame.size.width - switchSize.width,
+ (self.frame.size.height - switchSize.height) / 2,
+ switchSize.width, switchSize.height);
+
+ labelFrame = AlignRectOriginAndSizeToPixels(labelFrame);
+ switchFrame = AlignRectOriginAndSizeToPixels(switchFrame);
+
+ [label_ setFrame:labelFrame];
+ [switch_ setFrame:switchFrame];
+ preferredTotalWidth_ = CGRectGetMaxX(switchFrame);
+ preferredLabelWidth_ = CGRectGetMaxX(labelFrame);
+
+ [self addSubview:label_];
+ [self addSubview:switch_];
+ return self;
+}
+
+- (void)setTag:(NSInteger)tag target:(id)target action:(SEL)action {
+ [switch_ setTag:tag];
+ [switch_ addTarget:target
+ action:action
+ forControlEvents:UIControlEventValueChanged];
+}
+
+- (CGFloat)heightRequiredForSwitchWithWidth:(CGFloat)width layout:(BOOL)layout {
+ CGFloat widthLeftForLabel =
+ width - [switch_ frame].size.width - kSpaceBetweenWidgets;
+ CGSize maxSize = CGSizeMake(widthLeftForLabel, CGFLOAT_MAX);
+ CGSize labelSize =
+ [[label_ text] cr_boundingSizeWithSize:maxSize font:[label_ font]];
+ CGFloat viewHeight = std::max(labelSize.height, [switch_ frame].size.height);
+ if (layout) {
+ // Lays out the label and the switch to fit in {width, viewHeight}.
+ CGRect newLabelFrame;
+ newLabelFrame.origin.x = 0;
+ newLabelFrame.origin.y = (viewHeight - labelSize.height) / 2;
+ newLabelFrame.size = labelSize;
+ newLabelFrame = AlignRectOriginAndSizeToPixels(newLabelFrame);
+ [label_ setFrame:newLabelFrame];
+ CGRect newSwitchFrame;
+ newSwitchFrame.origin.x =
+ CGRectGetMaxX(newLabelFrame) + kSpaceBetweenWidgets;
+ newSwitchFrame.origin.y = (viewHeight - [switch_ frame].size.height) / 2;
+ newSwitchFrame.size = [switch_ frame].size;
+ newSwitchFrame = AlignRectOriginAndSizeToPixels(newSwitchFrame);
+ [switch_ setFrame:newSwitchFrame];
+ }
+ return viewHeight;
+}
+
+- (CGFloat)preferredWidth {
+ return preferredTotalWidth_;
+}
+
+@end
+
+@interface InfoBarView (Testing)
+// Returns the buttons' height.
+- (CGFloat)buttonsHeight;
+// Returns the button margin applied in some views.
+- (CGFloat)buttonMargin;
+// Returns the height of the infobar, and lays out the subviews if |layout| is
+// YES.
+- (CGFloat)computeRequiredHeightAndLayoutSubviews:(BOOL)layout;
+// Returns the height of the laid out buttons when not on the first line.
+// Either the buttons are narrow enough and they are on a single line next to
+// each other, or they are supperposed on top of each other.
+// Also lays out the buttons when |layout| is YES, in which case it uses
+// |heightOfFirstLine| to compute their vertical position.
+- (CGFloat)heightThatFitsButtonsUnderOtherWidgets:(CGFloat)heightOfFirstLine
+ layout:(BOOL)layout;
+// The |button| is positioned with the right edge at the specified y-axis
+// position |rightEdge| and the top row at |y|.
+// Returns the left edge of the newly-positioned button.
+- (CGFloat)layoutWideButtonAlignRight:(UIButton*)button
+ rightEdge:(CGFloat)rightEdge
+ y:(CGFloat)y;
+// Returns the minimum height of infobars.
+- (CGFloat)minimumInfobarHeight;
+// Returns |string| stripped of the markers specifying the links and fills
+// |linkRanges_| with the ranges of the enclosed links.
+- (NSString*)stripMarkersFromString:(NSString*)string;
+// Returns the ranges of the links and the associated tags.
+- (const std::vector<std::pair<NSUInteger, NSRange>>&)linkRanges;
+@end
+
+@interface InfoBarView ()
+
+// Returns the marker delimiting the start of a link.
++ (NSString*)openingMarkerForLink;
+// Returns the marker delimiting the end of a link.
++ (NSString*)closingMarkerForLink;
+
+@end
+
+@implementation InfoBarView {
+ // Delegates UIView events.
+ InfoBarViewDelegate* delegate_; // weak
+ // The current height of this infobar (used for animations where part of the
+ // infobar is hidden).
+ CGFloat visibleHeight_;
+ // The height of this infobar when fully visible.
+ CGFloat targetHeight_;
+ // View containing |imageView_|. Exists to apply drop shadows to the view.
+ base::scoped_nsobject<UIView> imageViewContainer_;
+ // View containing the icon.
+ base::scoped_nsobject<UIImageView> imageView_;
+ // Close button.
+ base::scoped_nsobject<UIButton> closeButton_;
+ // View containing the switch and its label.
+ base::scoped_nsobject<SwitchView> switchView_;
+ // We are using a LabelLinkController with an UILabel to be able to have
+ // parts of the label underlined and clickable. This label_ may be nil if
+ // the delegate returns an empty string for GetMessageText().
+ base::scoped_nsobject<LabelLinkController> labelLinkController_;
+ UILabel* label_; // Weak.
+ // Array of range information. The first element of the pair is the tag of
+ // the action and the second element is the range defining the link.
+ std::vector<std::pair<NSUInteger, NSRange>> linkRanges_;
+ // Text for the label with link markers included.
+ base::scoped_nsobject<NSString> markedLabel_;
+ // Buttons.
+ // button1_ is tagged with ConfirmInfoBarDelegate::BUTTON_OK .
+ // button2_ is tagged with ConfirmInfoBarDelegate::BUTTON_CANCEL .
+ base::scoped_nsobject<UIButton> button1_;
+ base::scoped_nsobject<UIButton> button2_;
+ // Drop shadow.
+ base::scoped_nsobject<UIImageView> shadow_;
+}
+
+@synthesize visibleHeight = visibleHeight_;
+
+- (id)initWithFrame:(CGRect)frame delegate:(InfoBarViewDelegate*)delegate {
+ self = [super initWithFrame:frame];
+ if (self) {
+ delegate_ = delegate;
+ // Make the drop shadow.
+ UIImage* shadowImage = [UIImage imageNamed:@"infobar_shadow"];
+ shadow_.reset([[UIImageView alloc] initWithImage:shadowImage]);
+ [self addSubview:shadow_];
+ [self setAutoresizingMask:UIViewAutoresizingFlexibleWidth |
+ UIViewAutoresizingFlexibleHeight];
+ [self setAccessibilityViewIsModal:YES];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [super dealloc];
+}
+
+- (NSString*)markedLabel {
+ return markedLabel_;
+}
+
+- (void)resetDelegate {
+ delegate_ = NULL;
+}
+
+// Returns the width reserved for the icon.
+- (CGFloat)leftMarginOnFirstLine {
+ CGFloat leftMargin = 0;
+ if (imageViewContainer_) {
+ leftMargin += [self frameOfIcon].size.width;
+ // The margin between the label and the icon is the same as the margin
+ // between the edge of the screen and the icon.
+ leftMargin += 2 * [self frameOfIcon].origin.x;
+ } else {
+ leftMargin += kLeftMarginOnFirstLineWhenIconAbsent;
+ }
+ return leftMargin;
+}
+
+// Returns the width reserved for the close button.
+- (CGFloat)rightMarginOnFirstLine {
+ return
+ [closeButton_ imageView].image.size.width + kCloseButtonInnerPadding * 2;
+}
+
+// Returns the horizontal space available between the icon and the close
+// button.
+- (CGFloat)horizontalSpaceAvailableOnFirstLine {
+ return [self frame].size.width - [self leftMarginOnFirstLine] -
+ [self rightMarginOnFirstLine];
+}
+
+// Returns the height taken by a label constrained by a width of |width|.
+- (CGFloat)heightRequiredForLabelWithWidth:(CGFloat)width {
+ return [label_ sizeThatFits:CGSizeMake(width, CGFLOAT_MAX)].height;
+}
+
+// Returns the width required by a label if it was displayed on a single line.
+- (CGFloat)widthOfLabelOnASingleLine {
+ // |label_| can be nil when delegate returns "" for GetMessageText().
+ if (!label_)
+ return 0.0;
+ CGSize rect = [[label_ text] cr_pixelAlignedSizeWithFont:[label_ font]];
+ return rect.width;
+}
+
+// Returns the minimum size required by |button| to be properly displayed.
+- (CGFloat)narrowestWidthOfButton:(UIButton*)button {
+ if (!button)
+ return 0;
+ // The button itself is queried for the size. The width is rounded up to be a
+ // multiple of 8 to fit Material grid spacing requirements.
+ CGFloat labelWidth =
+ [button sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)].width;
+ return ceil(labelWidth / kButtonWidthUnits) * kButtonWidthUnits;
+}
+
+// Returns the width of the buttons if they are laid out on the first line.
+- (CGFloat)widthOfButtonsOnFirstLine {
+ CGFloat width = [self narrowestWidthOfButton:button1_] +
+ [self narrowestWidthOfButton:button2_];
+ if (button1_ && button2_) {
+ width += kSpaceBetweenWidgets;
+ }
+ return width;
+}
+
+// Returns the width needed for the switch.
+- (CGFloat)preferredWidthOfSwitch {
+ return [switchView_ preferredWidth];
+}
+
+// Returns the space required to separate the left aligned widgets (label) from
+// the right aligned widgets (switch, buttons), assuming they fit on one line.
+- (CGFloat)widthToSeparateRightAndLeftWidgets {
+ BOOL leftWidgetsArePresent = (label_ != nil);
+ BOOL rightWidgetsArePresent = button1_ || button2_ || switchView_;
+ if (!leftWidgetsArePresent || !rightWidgetsArePresent)
+ return 0;
+ return kMinimumSpaceBetweenRightAndLeftAlignedWidgets;
+}
+
+// Returns the space required to separate the switch and the buttons.
+- (CGFloat)widthToSeparateSwitchAndButtons {
+ BOOL buttonsArePresent = button1_ || button2_;
+ BOOL switchIsPresent = (switchView_ != nil);
+ if (!buttonsArePresent || !switchIsPresent)
+ return 0;
+ return kSpaceBetweenWidgets;
+}
+
+// Lays out |button| at the height |y| and in the position |position|.
+// Must only be used for wide buttons, i.e. buttons not on the first line.
+- (void)layoutWideButton:(UIButton*)button
+ y:(CGFloat)y
+ position:(InfoBarButtonPosition)position {
+ CGFloat screenWidth = [self frame].size.width;
+ CGFloat startPercentage = 0.0;
+ CGFloat endPercentage = 0.0;
+ switch (position) {
+ case LEFT:
+ startPercentage = 0.0;
+ endPercentage = 0.5;
+ break;
+ case RIGHT:
+ startPercentage = 0.5;
+ endPercentage = 1.0;
+ break;
+ case CENTER:
+ startPercentage = 0.0;
+ endPercentage = 1.0;
+ break;
+ case ON_FIRST_LINE:
+ NOTREACHED();
+ }
+ DCHECK(startPercentage >= 0.0 && startPercentage <= 1.0);
+ DCHECK(endPercentage >= 0.0 && endPercentage <= 1.0);
+ DCHECK(startPercentage < endPercentage);
+ // In Material the button is not stretched to fit the available space. It is
+ // placed centrally in the allotted space.
+ CGFloat minX = screenWidth * startPercentage;
+ CGFloat maxX = screenWidth * endPercentage;
+ CGFloat midpoint = (minX + maxX) / 2;
+ CGFloat minWidth =
+ std::min([self narrowestWidthOfButton:button], maxX - minX);
+ CGFloat left = midpoint - minWidth / 2;
+ CGRect frame = CGRectMake(left, y, minWidth, kButtonHeight);
+ frame = AlignRectOriginAndSizeToPixels(frame);
+ [button setFrame:frame];
+}
+
+- (CGFloat)layoutWideButtonAlignRight:(UIButton*)button
+ rightEdge:(CGFloat)rightEdge
+ y:(CGFloat)y {
+ CGFloat width = [self narrowestWidthOfButton:button];
+ CGFloat leftEdge = rightEdge - width;
+ CGRect frame = CGRectMake(leftEdge, y, width, kButtonHeight);
+ frame = AlignRectOriginAndSizeToPixels(frame);
+ [button setFrame:frame];
+ return leftEdge;
+}
+
+- (CGFloat)heightThatFitsButtonsUnderOtherWidgets:(CGFloat)heightOfFirstLine
+ layout:(BOOL)layout {
+ if (button1_ && button2_) {
+ CGFloat halfWidthOfScreen = [self frame].size.width / 2.0;
+ if ([self narrowestWidthOfButton:button1_] <= halfWidthOfScreen &&
+ [self narrowestWidthOfButton:button2_] <= halfWidthOfScreen) {
+ // Each button can fit in half the screen's width.
+ if (layout) {
+ // When there are two buttons on one line, they are positioned aligned
+ // right in the available space, spaced apart by kButtonSpacing.
+ CGFloat leftOfRightmostButton =
+ [self layoutWideButtonAlignRight:button1_
+ rightEdge:CGRectGetWidth(self.bounds) -
+ kButtonMargin
+ y:heightOfFirstLine];
+ [self layoutWideButtonAlignRight:button2_
+ rightEdge:leftOfRightmostButton - kButtonSpacing
+ y:heightOfFirstLine];
+ }
+ return kButtonHeight;
+ } else {
+ // At least one of the two buttons is larger than half the screen's width,
+ // so |button2_| is placed underneath |button1_|.
+ if (layout) {
+ [self layoutWideButton:button1_ y:heightOfFirstLine position:CENTER];
+ [self layoutWideButton:button2_
+ y:heightOfFirstLine + kButtonHeight
+ position:CENTER];
+ }
+ return 2 * kButtonHeight;
+ }
+ }
+ // There is at most 1 button to layout.
+ UIButton* button = button1_ ? button1_ : button2_;
+ if (button) {
+ if (layout) {
+ // Where is there is just one button it is positioned aligned right in the
+ // available space.
+ [self
+ layoutWideButtonAlignRight:button
+ rightEdge:CGRectGetWidth(self.bounds) - kButtonMargin
+ y:heightOfFirstLine];
+ }
+ return kButtonHeight;
+ }
+ return 0;
+}
+
+- (CGFloat)computeRequiredHeightAndLayoutSubviews:(BOOL)layout {
+ CGFloat requiredHeight = 0;
+ CGFloat widthOfLabel = [self widthOfLabelOnASingleLine] +
+ [self widthToSeparateRightAndLeftWidgets];
+ CGFloat widthOfButtons = [self widthOfButtonsOnFirstLine];
+ CGFloat preferredWidthOfSwitch = [self preferredWidthOfSwitch];
+ CGFloat widthOfScreen = [self frame].size.width;
+ CGFloat rightMarginOnFirstLine = [self rightMarginOnFirstLine];
+ CGFloat spaceAvailableOnFirstLine =
+ [self horizontalSpaceAvailableOnFirstLine];
+ CGFloat widthOfButtonAndSwitch = widthOfButtons +
+ [self widthToSeparateSwitchAndButtons] +
+ preferredWidthOfSwitch;
+ // Tests if the label, switch, and buttons can fit on a single line.
+ if (widthOfLabel + widthOfButtonAndSwitch < spaceAvailableOnFirstLine) {
+ // The label, switch, and buttons can fit on a single line.
+ requiredHeight = kMinimumInfobarHeight;
+ if (layout) {
+ // Lays out the close button.
+ CGRect buttonFrame = [self frameOfCloseButton:YES];
+ [closeButton_ setFrame:buttonFrame];
+ // Lays out the label.
+ CGFloat labelHeight = [self heightRequiredForLabelWithWidth:widthOfLabel];
+ CGRect frame = CGRectMake([self leftMarginOnFirstLine],
+ (kMinimumInfobarHeight - labelHeight) / 2,
+ [self widthOfLabelOnASingleLine], labelHeight);
+ frame = AlignRectOriginAndSizeToPixels(frame);
+ [label_ setFrame:frame];
+ // Layouts the buttons.
+ CGFloat buttonMargin =
+ rightMarginOnFirstLine + kExtraButtonMarginOnSingleLine;
+ if (button1_) {
+ CGFloat width = [self narrowestWidthOfButton:button1_];
+ CGFloat offset = width;
+ frame = CGRectMake(widthOfScreen - buttonMargin - offset,
+ (kMinimumInfobarHeight - kButtonHeight) / 2, width,
+ kButtonHeight);
+ frame = AlignRectOriginAndSizeToPixels(frame);
+ [button1_ setFrame:frame];
+ }
+ if (button2_) {
+ CGFloat width = [self narrowestWidthOfButton:button2_];
+ CGFloat offset = widthOfButtons;
+ frame = CGRectMake(widthOfScreen - buttonMargin - offset,
+ (kMinimumInfobarHeight - kButtonHeight) / 2, width,
+ frame.size.height = kButtonHeight);
+ frame = AlignRectOriginAndSizeToPixels(frame);
+ [button2_ setFrame:frame];
+ }
+ // Lays out the switch view to the left of the buttons.
+ if (switchView_) {
+ frame = CGRectMake(
+ widthOfScreen - buttonMargin - widthOfButtonAndSwitch,
+ (kMinimumInfobarHeight - [switchView_ frame].size.height) / 2.0,
+ preferredWidthOfSwitch, [switchView_ frame].size.height);
+ frame = AlignRectOriginAndSizeToPixels(frame);
+ [switchView_ setFrame:frame];
+ }
+ }
+ } else {
+ // The widgets (label, switch, buttons) can't fit on a single line. Attempts
+ // to lay out the label and switch on the first line, and the buttons
+ // underneath.
+ CGFloat heightOfLabelAndSwitch = 0;
+
+ if (layout) {
+ // Lays out the close button.
+ CGRect buttonFrame = [self frameOfCloseButton:NO];
+ [closeButton_ setFrame:buttonFrame];
+ }
+ if (widthOfLabel + preferredWidthOfSwitch < spaceAvailableOnFirstLine) {
+ // The label and switch can fit on the first line.
+ heightOfLabelAndSwitch = kMinimumInfobarHeight;
+ if (layout) {
+ CGFloat labelHeight =
+ [self heightRequiredForLabelWithWidth:widthOfLabel];
+ CGRect labelFrame =
+ CGRectMake([self leftMarginOnFirstLine],
+ (heightOfLabelAndSwitch - labelHeight) / 2,
+ [self widthOfLabelOnASingleLine], labelHeight);
+ labelFrame = AlignRectOriginAndSizeToPixels(labelFrame);
+ [label_ setFrame:labelFrame];
+ if (switchView_) {
+ CGRect switchRect = CGRectMake(
+ widthOfScreen - rightMarginOnFirstLine - preferredWidthOfSwitch,
+ (heightOfLabelAndSwitch - [switchView_ frame].size.height) / 2,
+ preferredWidthOfSwitch, [switchView_ frame].size.height);
+ switchRect = AlignRectOriginAndSizeToPixels(switchRect);
+ [switchView_ setFrame:switchRect];
+ }
+ }
+ } else {
+ // The label and switch can't fit on the first line, so lay them out on
+ // different lines.
+ // Computes the height of the label, and optionally lays it out.
+ CGFloat labelMarginBottom = kLabelMarginBottom;
+ if (button1_ || button2_) {
+ // Material features more padding between the label and the button than
+ // the label and the bottom of the dialog when there is no button.
+ labelMarginBottom += kExtraMarginBetweenLabelAndButton;
+ }
+ CGFloat heightOfLabelWithPadding =
+ [self heightRequiredForLabelWithWidth:spaceAvailableOnFirstLine] +
+ kLabelMarginTop + labelMarginBottom;
+ if (layout) {
+ CGRect labelFrame = CGRectMake(
+ [self leftMarginOnFirstLine], kLabelMarginTop,
+ spaceAvailableOnFirstLine,
+ heightOfLabelWithPadding - kLabelMarginTop - labelMarginBottom);
+ labelFrame = AlignRectOriginAndSizeToPixels(labelFrame);
+ [label_ setFrame:labelFrame];
+ }
+ // Computes the height of the switch view (if any), and optionally lays it
+ // out.
+ CGFloat heightOfSwitchWithPadding = 0;
+ if (switchView_ != nil) {
+ // The switch view is aligned with the first line's label, hence the
+ // call to |leftMarginOnFirstLine|.
+ CGFloat widthAvailableForSwitchView = [self frame].size.width -
+ [self leftMarginOnFirstLine] -
+ kRightMargin;
+ CGFloat heightOfSwitch = [switchView_
+ heightRequiredForSwitchWithWidth:widthAvailableForSwitchView
+ layout:layout];
+ // If there are buttons underneath the switch, add padding.
+ if (button1_ || button2_) {
+ heightOfSwitchWithPadding = heightOfSwitch + kSpaceBetweenWidgets +
+ kExtraMarginBetweenLabelAndButton;
+ } else {
+ heightOfSwitchWithPadding = heightOfSwitch;
+ }
+ if (layout) {
+ CGRect switchRect =
+ CGRectMake([self leftMarginOnFirstLine], heightOfLabelWithPadding,
+ widthAvailableForSwitchView, heightOfSwitch);
+ switchRect = AlignRectOriginAndSizeToPixels(switchRect);
+ [switchView_ setFrame:switchRect];
+ }
+ }
+ heightOfLabelAndSwitch =
+ std::max(heightOfLabelWithPadding + heightOfSwitchWithPadding,
+ kMinimumInfobarHeight);
+ }
+ // Lays out the button(s) under the label and switch.
+ CGFloat heightOfButtons =
+ [self heightThatFitsButtonsUnderOtherWidgets:heightOfLabelAndSwitch
+ layout:layout];
+ requiredHeight = heightOfLabelAndSwitch;
+ if (heightOfButtons > 0)
+ requiredHeight += heightOfButtons + kButtonMargin;
+ }
+ return requiredHeight;
+}
+
+- (CGSize)sizeThatFits:(CGSize)size {
+ CGFloat requiredHeight = [self computeRequiredHeightAndLayoutSubviews:NO];
+ return CGSizeMake([self frame].size.width, requiredHeight);
+}
+
+- (void)layoutSubviews {
+ // Lays out the position of the icon.
+ [imageViewContainer_ setFrame:[self frameOfIcon]];
+ targetHeight_ = [self computeRequiredHeightAndLayoutSubviews:YES];
+
+ if (delegate_)
+ delegate_->SetInfoBarTargetHeight(targetHeight_);
+ [self resetBackground];
+
+ // Asks the BidiContainerView to reposition of all the subviews.
+ for (UIView* view in [self subviews])
+ [self setSubviewNeedsAdjustmentForRTL:view];
+ [super layoutSubviews];
+}
+
+- (void)resetBackground {
+ UIColor* color = [UIColor whiteColor];
+ [self setBackgroundColor:color];
+ CGFloat shadowY = 0;
+ shadowY = -[shadow_ image].size.height; // Shadow above the infobar.
+ [shadow_ setFrame:CGRectMake(0, shadowY, self.bounds.size.width,
+ [shadow_ image].size.height)];
+ [shadow_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
+}
+
+- (void)addCloseButtonWithTag:(NSInteger)tag
+ target:(id)target
+ action:(SEL)action {
+ DCHECK(!closeButton_);
+ // TODO(jeanfrancoisg): Add IDR_ constant and use GetNativeImageNamed().
+ // crbug/228611
+ NSString* imagePath =
+ [[NSBundle mainBundle] pathForResource:@"infobar_close" ofType:@"png"];
+ UIImage* image = [UIImage imageWithContentsOfFile:imagePath];
+ closeButton_.reset([[UIButton buttonWithType:UIButtonTypeCustom] retain]);
+ [closeButton_ setExclusiveTouch:YES];
+ [closeButton_ setImage:image forState:UIControlStateNormal];
+ [closeButton_ addTarget:target
+ action:action
+ forControlEvents:UIControlEventTouchUpInside];
+ [closeButton_ setTag:tag];
+ [closeButton_ setAccessibilityLabel:l10n_util::GetNSString(IDS_CLOSE)];
+ [self addSubview:closeButton_];
+}
+
+- (void)addSwitchWithLabel:(NSString*)label
+ isOn:(BOOL)isOn
+ tag:(NSInteger)tag
+ target:(id)target
+ action:(SEL)action {
+ switchView_.reset([[SwitchView alloc] initWithLabel:label isOn:isOn]);
+ [switchView_ setTag:tag target:target action:action];
+ [self addSubview:switchView_];
+}
+
+- (void)addLeftIcon:(UIImage*)image {
+ if (!imageViewContainer_) {
+ imageViewContainer_.reset([[UIView alloc] init]);
+ [self addSubview:imageViewContainer_];
+ }
+ imageView_.reset([[UIImageView alloc] initWithImage:image]);
+ [imageViewContainer_ addSubview:imageView_];
+}
+
+- (void)addPlaceholderTransparentIcon:(CGSize const&)imageSize {
+ UIGraphicsBeginImageContext(imageSize);
+ UIImage* placeholder = UIGraphicsGetImageFromCurrentImageContext();
+ UIGraphicsEndImageContext();
+ [self addLeftIcon:placeholder];
+}
+
+// Since shadows & rounded corners cannot be applied simultaneously to a
+// UIView, this method adds rounded corners to the UIImageView and then adds
+// drop shadow to the UIView containing the UIImageView.
+- (void)addLeftIconWithRoundedCornersAndShadow:(UIImage*)image {
+ CGFloat effectScaleFactor = image.size.width / kBaseSizeForEffects;
+ [self addLeftIcon:image];
+ CALayer* layer = [imageView_ layer];
+ [layer setMasksToBounds:YES];
+ [layer setCornerRadius:kCornerRadius * effectScaleFactor];
+ layer = [imageViewContainer_ layer];
+ [layer setShadowColor:[UIColor blackColor].CGColor];
+ [layer
+ setShadowOffset:CGSizeMake(0, kShadowVerticalOffset * effectScaleFactor)];
+ [layer setShadowOpacity:kShadowOpacity];
+ [layer setShadowRadius:kShadowRadius * effectScaleFactor];
+ [imageViewContainer_ setClipsToBounds:NO];
+}
+
+- (NSString*)stripMarkersFromString:(NSString*)string {
+ linkRanges_.clear();
+ for (;;) {
+ // Find the opening marker, followed by the tag between parentheses.
+ NSRange startingRange =
+ [string rangeOfString:[[InfoBarView openingMarkerForLink]
+ stringByAppendingString:@"("]];
+ if (!startingRange.length)
+ return [[string copy] autorelease];
+ // Read the tag.
+ NSUInteger beginTag = NSMaxRange(startingRange);
+ NSRange closingParenthesis = [string
+ rangeOfString:@")"
+ options:NSLiteralSearch
+ range:NSMakeRange(beginTag, [string length] - beginTag)];
+ if (closingParenthesis.location == NSNotFound)
+ return [[string copy] autorelease];
+ NSInteger tag = [[string
+ substringWithRange:NSMakeRange(beginTag, closingParenthesis.location -
+ beginTag)] integerValue];
+ // If the parsing fails, |tag| is 0. Negative values are not allowed.
+ if (tag <= 0)
+ return [[string copy] autorelease];
+ // Find the closing marker.
+ startingRange.length =
+ closingParenthesis.location - startingRange.location + 1;
+ NSRange endingRange =
+ [string rangeOfString:[InfoBarView closingMarkerForLink]];
+ DCHECK(endingRange.length);
+ // Compute range of link in stripped string and add it to the array.
+ NSRange rangeOfLinkInStrippedString =
+ NSMakeRange(startingRange.location,
+ endingRange.location - NSMaxRange(startingRange));
+ linkRanges_.push_back(std::make_pair(tag, rangeOfLinkInStrippedString));
+ // Creates a new string without the markers.
+ NSString* beforeLink = [string substringToIndex:startingRange.location];
+ NSRange rangeOfLink =
+ NSMakeRange(NSMaxRange(startingRange),
+ endingRange.location - NSMaxRange(startingRange));
+ NSString* link = [string substringWithRange:rangeOfLink];
+ NSString* afterLink = [string substringFromIndex:NSMaxRange(endingRange)];
+ string = [NSString stringWithFormat:@"%@%@%@", beforeLink, link, afterLink];
+ }
+}
+
+- (void)addLabel:(NSString*)label {
+ [self addLabel:label target:nil action:nil];
+}
+
+- (void)addLabel:(NSString*)text target:(id)target action:(SEL)action {
+ markedLabel_.reset([text copy]);
+ if (target)
+ text = [self stripMarkersFromString:text];
+ if ([label_ superview]) {
+ [label_ removeFromSuperview];
+ }
+
+ label_ = [[[UILabel alloc] initWithFrame:CGRectZero] autorelease];
+
+ UIFont* font = [MDCTypography subheadFont];
+
+ [label_ setBackgroundColor:[UIColor clearColor]];
+
+ NSMutableParagraphStyle* paragraphStyle =
+ [[[NSMutableParagraphStyle alloc] init] autorelease];
+ paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
+ paragraphStyle.lineSpacing = kLabelLineSpacing;
+ NSDictionary* attributes = @{
+ NSParagraphStyleAttributeName : paragraphStyle,
+ NSFontAttributeName : font,
+ };
+ [label_ setNumberOfLines:0];
+
+ [label_ setAttributedText:[[[NSAttributedString alloc]
+ initWithString:text
+ attributes:attributes] autorelease]];
+
+ [self addSubview:label_];
+
+ if (linkRanges_.empty())
+ return;
+
+ DCHECK([target respondsToSelector:action]);
+
+ labelLinkController_.reset([[LabelLinkController alloc]
+ initWithLabel:label_
+ action:^(const GURL& gurl) {
+ NSUInteger actionTag = [base::SysUTF8ToNSString(
+ gurl.ExtractFileName()) integerValue];
+ [target performSelector:action withObject:@(actionTag)];
+ }]);
+
+ [labelLinkController_ setLinkUnderlineStyle:NSUnderlineStyleSingle];
+ [labelLinkController_ setLinkColor:[UIColor blackColor]];
+
+ std::vector<std::pair<NSUInteger, NSRange>>::const_iterator it;
+ for (it = linkRanges_.begin(); it != linkRanges_.end(); ++it) {
+ // The last part of the URL contains the tag, so it can be retrieved in the
+ // callback. This tag is generally a command ID.
+ std::string url = std::string(kChromeInfobarURL) +
+ std::string(std::to_string((int)it->first));
+ [labelLinkController_ addLinkWithRange:it->second url:GURL(url)];
+ }
+}
+
+- (void)addButton1:(NSString*)title1
+ tag1:(NSInteger)tag1
+ button2:(NSString*)title2
+ tag2:(NSInteger)tag2
+ target:(id)target
+ action:(SEL)action {
+ button1_.reset([[self infoBarButton:title1
+ palette:[MDCPalette cr_bluePalette]
+ customTitleColor:[UIColor whiteColor]
+ tag:tag1
+ target:target
+ action:action] retain]);
+ [self addSubview:button1_];
+
+ button2_.reset([[self infoBarButton:title2
+ palette:nil
+ customTitleColor:UIColorFromRGB(kButton2TitleColor)
+ tag:tag2
+ target:target
+ action:action] retain]);
+ [self addSubview:button2_];
+}
+
+- (void)addButton:(NSString*)title
+ tag:(NSInteger)tag
+ target:(id)target
+ action:(SEL)action {
+ if (![title length])
+ return;
+ button1_.reset([[self infoBarButton:title
+ palette:[MDCPalette cr_bluePalette]
+ customTitleColor:[UIColor whiteColor]
+ tag:tag
+ target:target
+ action:action] retain]);
+ [self addSubview:button1_];
+}
+
+// Initializes and returns a button for the infobar, with the specified
+// |message| and colors.
+- (UIButton*)infoBarButton:(NSString*)message
+ palette:(MDCPalette*)palette
+ customTitleColor:(UIColor*)customTitleColor
+ tag:(NSInteger)tag
+ target:(id)target
+ action:(SEL)action {
+ base::scoped_nsobject<MDCFlatButton> button([[MDCFlatButton alloc] init]);
+ button.get().inkColor = [[palette tint300] colorWithAlphaComponent:0.5f];
+ [button setBackgroundColor:[palette tint500] forState:UIControlStateNormal];
+ [button setBackgroundColor:[UIColor colorWithWhite:0.8f alpha:1.0f]
+ forState:UIControlStateDisabled];
+ if (palette)
+ button.get().hasOpaqueBackground = YES;
+ if (customTitleColor)
+ button.get().customTitleColor = customTitleColor;
+ button.get().titleLabel.adjustsFontSizeToFitWidth = YES;
+ button.get().titleLabel.minimumScaleFactor = 0.6f;
+ [button setTitle:message forState:UIControlStateNormal];
+ [button setTag:tag];
+ [button addTarget:target
+ action:action
+ forControlEvents:UIControlEventTouchUpInside];
+ // Without the call to layoutIfNeeded, |button| returns an incorrect
+ // titleLabel the first time it is accessed in |narrowestWidthOfButton|.
+ [button layoutIfNeeded];
+ return button.autorelease();
+}
+
+- (CGRect)frameOfCloseButton:(BOOL)singleLineMode {
+ DCHECK(closeButton_);
+ // Add padding to increase the touchable area.
+ CGSize closeButtonSize = [closeButton_ imageView].image.size;
+ closeButtonSize.width += kCloseButtonInnerPadding * 2;
+ closeButtonSize.height += kCloseButtonInnerPadding * 2;
+ CGFloat x = CGRectGetMaxX(self.frame) - closeButtonSize.width;
+ // Aligns the close button at the top (height includes touch padding).
+ CGFloat y = 0;
+ if (singleLineMode) {
+ // On single-line mode the button is centered vertically.
+ y = ui::AlignValueToUpperPixel(
+ (kMinimumInfobarHeight - closeButtonSize.height) * 0.5);
+ }
+ return CGRectMake(x, y, closeButtonSize.width, closeButtonSize.height);
+}
+
+- (CGRect)frameOfIcon {
+ CGSize iconSize = [imageView_ image].size;
+ CGFloat y = kButtonsTopMargin;
+ CGFloat x = kCloseButtonLeftMargin;
+ return CGRectMake(AlignValueToPixel(x), AlignValueToPixel(y), iconSize.width,
+ iconSize.height);
+}
+
++ (NSString*)openingMarkerForLink {
+ return @"$LINK_START";
+}
+
++ (NSString*)closingMarkerForLink {
+ return @"$LINK_END";
+}
+
++ (NSString*)stringAsLink:(NSString*)string tag:(NSUInteger)tag {
+ DCHECK_NE(0u, tag);
+ return [NSString stringWithFormat:@"%@(%" PRIuNS ")%@%@",
+ [InfoBarView openingMarkerForLink], tag,
+ string, [InfoBarView closingMarkerForLink]];
+}
+
+#pragma mark - Testing
+
+- (CGFloat)minimumInfobarHeight {
+ return kMinimumInfobarHeight;
+}
+
+- (CGFloat)buttonsHeight {
+ return kButtonHeight;
+}
+
+- (CGFloat)buttonMargin {
+ return kButtonMargin;
+}
+
+- (const std::vector<std::pair<NSUInteger, NSRange>>&)linkRanges {
+ return linkRanges_;
+}
+
+@end
« no previous file with comments | « ios/chrome/browser/ui/infobars/infobar_view.h ('k') | ios/chrome/browser/ui/infobars/infobar_view_unittest.mm » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698