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

Side by Side 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import "ios/chrome/browser/ui/infobars/infobar_view.h"
6
7 #import <CoreGraphics/CoreGraphics.h>
8 #import <QuartzCore/QuartzCore.h>
9
10 #include "base/format_macros.h"
11 #include "base/i18n/rtl.h"
12 #include "base/ios/weak_nsobject.h"
13 #include "base/logging.h"
14 #include "base/mac/foundation_util.h"
15 #include "base/strings/sys_string_conversions.h"
16 #include "components/strings/grit/components_strings.h"
17 #import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h"
18 #include "ios/chrome/browser/ui/ui_util.h"
19 #import "ios/chrome/browser/ui/uikit_ui_util.h"
20 #import "ios/chrome/browser/ui/util/label_link_controller.h"
21 #import "ios/public/provider/chrome/browser/ui/infobar_view_delegate.h"
22 #import "ios/third_party/material_components_ios/src/components/Buttons/src/Mate rialButtons.h"
23 #import "ios/third_party/material_components_ios/src/components/Typography/src/M aterialTypography.h"
24 #include "ui/base/l10n/l10n_util.h"
25 #import "ui/gfx/ios/NSString+CrStringDrawing.h"
26 #import "ui/gfx/ios/uikit_util.h"
27 #include "url/gurl.h"
28
29 namespace {
30
31 const char kChromeInfobarURL[] = "chromeinternal://infobar/";
32
33 // UX configuration constants for the shadow/rounded corners on the icon.
34 const CGFloat kBaseSizeForEffects = 57.0;
35 const CGFloat kCornerRadius = 10.0;
36 const CGFloat kShadowVerticalOffset = 1.0;
37 const CGFloat kShadowOpacity = 0.5;
38 const CGFloat kShadowRadius = 0.8;
39
40 // UX configuration for the layout of items.
41 const CGFloat kLeftMarginOnFirstLineWhenIconAbsent = 20.0;
42 const CGFloat kMinimumSpaceBetweenRightAndLeftAlignedWidgets = 30.0;
43 const CGFloat kRightMargin = 10.0;
44 const CGFloat kSpaceBetweenWidgets = 10.0;
45 const CGFloat kCloseButtonInnerPadding = 16.0;
46 const CGFloat kButtonHeight = 36.0;
47 const CGFloat kButtonMargin = 16.0;
48 const CGFloat kExtraButtonMarginOnSingleLine = 8.0;
49 const CGFloat kButtonSpacing = 8.0;
50 const CGFloat kButtonWidthUnits = 8.0;
51 const CGFloat kButtonsTopMargin = kCloseButtonInnerPadding;
52 const CGFloat kCloseButtonLeftMargin = 16.0;
53 const CGFloat kLabelLineSpacing = 5.0;
54 const CGFloat kLabelMarginBottom = 22.0;
55 const CGFloat kExtraMarginBetweenLabelAndButton = 8.0;
56 const CGFloat kLabelMarginTop = kButtonsTopMargin + 5.0; // Baseline lowered.
57 const CGFloat kMinimumInfobarHeight = 68.0;
58
59 const int kButton2TitleColor = 0x4285f4;
60
61 enum InfoBarButtonPosition { ON_FIRST_LINE, CENTER, LEFT, RIGHT };
62
63 } // namespace
64
65 // UIView containing a switch and a label.
66 @interface SwitchView : BidiContainerView
67
68 // Initialize the view's label with |labelText|.
69 - (id)initWithLabel:(NSString*)labelText isOn:(BOOL)isOn;
70
71 // Specifies the object, action, and tag used when the switch is toggled.
72 - (void)setTag:(NSInteger)tag target:(id)target action:(SEL)action;
73
74 // Returns the height taken by the view constrained by a width of |width|.
75 // If |layout| is yes, it sets the frame of the label and the switch to fit
76 // |width|.
77 - (CGFloat)heightRequiredForSwitchWithWidth:(CGFloat)width layout:(BOOL)layout;
78
79 // Returns the preferred width. A smaller width requires eliding the text.
80 - (CGFloat)preferredWidth;
81
82 @end
83
84 @implementation SwitchView {
85 base::scoped_nsobject<UILabel> label_;
86 base::scoped_nsobject<UISwitch> switch_;
87 CGFloat preferredTotalWidth_;
88 CGFloat preferredLabelWidth_;
89 }
90
91 - (id)initWithLabel:(NSString*)labelText isOn:(BOOL)isOn {
92 // Creates switch and label.
93 base::scoped_nsobject<UILabel> tempLabel(
94 [[UILabel alloc] initWithFrame:CGRectZero]);
95 [tempLabel setTextAlignment:NSTextAlignmentNatural];
96 [tempLabel setFont:[MDCTypography body1Font]];
97 [tempLabel setText:labelText];
98 [tempLabel setBackgroundColor:[UIColor clearColor]];
99 [tempLabel setLineBreakMode:NSLineBreakByWordWrapping];
100 [tempLabel setNumberOfLines:0];
101 [tempLabel setAdjustsFontSizeToFitWidth:NO];
102 base::scoped_nsobject<UISwitch> tempSwitch(
103 [[UISwitch alloc] initWithFrame:CGRectZero]);
104 [tempSwitch setExclusiveTouch:YES];
105 [tempSwitch setAccessibilityLabel:labelText];
106 [tempSwitch setOnTintColor:[[MDCPalette cr_bluePalette] tint500]];
107 [tempSwitch setOn:isOn];
108
109 // Computes the size and initializes the view.
110 CGSize maxSize = CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX);
111 CGSize labelSize =
112 [[tempLabel text] cr_boundingSizeWithSize:maxSize font:[tempLabel font]];
113 CGSize switchSize = [tempSwitch frame].size;
114 CGRect frameRect = CGRectMake(
115 0, 0, labelSize.width + kSpaceBetweenWidgets + switchSize.width,
116 std::max(labelSize.height, switchSize.height));
117 self = [super initWithFrame:frameRect];
118 if (!self)
119 return nil;
120 label_.reset([tempLabel retain]);
121 switch_.reset([tempSwitch retain]);
122
123 // Sets the position of the label and the switch. The label is left aligned
124 // and the switch is right aligned. Both are vertically centered.
125 CGRect labelFrame =
126 CGRectMake(0, (self.frame.size.height - labelSize.height) / 2,
127 labelSize.width, labelSize.height);
128 CGRect switchFrame =
129 CGRectMake(self.frame.size.width - switchSize.width,
130 (self.frame.size.height - switchSize.height) / 2,
131 switchSize.width, switchSize.height);
132
133 labelFrame = AlignRectOriginAndSizeToPixels(labelFrame);
134 switchFrame = AlignRectOriginAndSizeToPixels(switchFrame);
135
136 [label_ setFrame:labelFrame];
137 [switch_ setFrame:switchFrame];
138 preferredTotalWidth_ = CGRectGetMaxX(switchFrame);
139 preferredLabelWidth_ = CGRectGetMaxX(labelFrame);
140
141 [self addSubview:label_];
142 [self addSubview:switch_];
143 return self;
144 }
145
146 - (void)setTag:(NSInteger)tag target:(id)target action:(SEL)action {
147 [switch_ setTag:tag];
148 [switch_ addTarget:target
149 action:action
150 forControlEvents:UIControlEventValueChanged];
151 }
152
153 - (CGFloat)heightRequiredForSwitchWithWidth:(CGFloat)width layout:(BOOL)layout {
154 CGFloat widthLeftForLabel =
155 width - [switch_ frame].size.width - kSpaceBetweenWidgets;
156 CGSize maxSize = CGSizeMake(widthLeftForLabel, CGFLOAT_MAX);
157 CGSize labelSize =
158 [[label_ text] cr_boundingSizeWithSize:maxSize font:[label_ font]];
159 CGFloat viewHeight = std::max(labelSize.height, [switch_ frame].size.height);
160 if (layout) {
161 // Lays out the label and the switch to fit in {width, viewHeight}.
162 CGRect newLabelFrame;
163 newLabelFrame.origin.x = 0;
164 newLabelFrame.origin.y = (viewHeight - labelSize.height) / 2;
165 newLabelFrame.size = labelSize;
166 newLabelFrame = AlignRectOriginAndSizeToPixels(newLabelFrame);
167 [label_ setFrame:newLabelFrame];
168 CGRect newSwitchFrame;
169 newSwitchFrame.origin.x =
170 CGRectGetMaxX(newLabelFrame) + kSpaceBetweenWidgets;
171 newSwitchFrame.origin.y = (viewHeight - [switch_ frame].size.height) / 2;
172 newSwitchFrame.size = [switch_ frame].size;
173 newSwitchFrame = AlignRectOriginAndSizeToPixels(newSwitchFrame);
174 [switch_ setFrame:newSwitchFrame];
175 }
176 return viewHeight;
177 }
178
179 - (CGFloat)preferredWidth {
180 return preferredTotalWidth_;
181 }
182
183 @end
184
185 @interface InfoBarView (Testing)
186 // Returns the buttons' height.
187 - (CGFloat)buttonsHeight;
188 // Returns the button margin applied in some views.
189 - (CGFloat)buttonMargin;
190 // Returns the height of the infobar, and lays out the subviews if |layout| is
191 // YES.
192 - (CGFloat)computeRequiredHeightAndLayoutSubviews:(BOOL)layout;
193 // Returns the height of the laid out buttons when not on the first line.
194 // Either the buttons are narrow enough and they are on a single line next to
195 // each other, or they are supperposed on top of each other.
196 // Also lays out the buttons when |layout| is YES, in which case it uses
197 // |heightOfFirstLine| to compute their vertical position.
198 - (CGFloat)heightThatFitsButtonsUnderOtherWidgets:(CGFloat)heightOfFirstLine
199 layout:(BOOL)layout;
200 // The |button| is positioned with the right edge at the specified y-axis
201 // position |rightEdge| and the top row at |y|.
202 // Returns the left edge of the newly-positioned button.
203 - (CGFloat)layoutWideButtonAlignRight:(UIButton*)button
204 rightEdge:(CGFloat)rightEdge
205 y:(CGFloat)y;
206 // Returns the minimum height of infobars.
207 - (CGFloat)minimumInfobarHeight;
208 // Returns |string| stripped of the markers specifying the links and fills
209 // |linkRanges_| with the ranges of the enclosed links.
210 - (NSString*)stripMarkersFromString:(NSString*)string;
211 // Returns the ranges of the links and the associated tags.
212 - (const std::vector<std::pair<NSUInteger, NSRange>>&)linkRanges;
213 @end
214
215 @interface InfoBarView ()
216
217 // Returns the marker delimiting the start of a link.
218 + (NSString*)openingMarkerForLink;
219 // Returns the marker delimiting the end of a link.
220 + (NSString*)closingMarkerForLink;
221
222 @end
223
224 @implementation InfoBarView {
225 // Delegates UIView events.
226 InfoBarViewDelegate* delegate_; // weak
227 // The current height of this infobar (used for animations where part of the
228 // infobar is hidden).
229 CGFloat visibleHeight_;
230 // The height of this infobar when fully visible.
231 CGFloat targetHeight_;
232 // View containing |imageView_|. Exists to apply drop shadows to the view.
233 base::scoped_nsobject<UIView> imageViewContainer_;
234 // View containing the icon.
235 base::scoped_nsobject<UIImageView> imageView_;
236 // Close button.
237 base::scoped_nsobject<UIButton> closeButton_;
238 // View containing the switch and its label.
239 base::scoped_nsobject<SwitchView> switchView_;
240 // We are using a LabelLinkController with an UILabel to be able to have
241 // parts of the label underlined and clickable. This label_ may be nil if
242 // the delegate returns an empty string for GetMessageText().
243 base::scoped_nsobject<LabelLinkController> labelLinkController_;
244 UILabel* label_; // Weak.
245 // Array of range information. The first element of the pair is the tag of
246 // the action and the second element is the range defining the link.
247 std::vector<std::pair<NSUInteger, NSRange>> linkRanges_;
248 // Text for the label with link markers included.
249 base::scoped_nsobject<NSString> markedLabel_;
250 // Buttons.
251 // button1_ is tagged with ConfirmInfoBarDelegate::BUTTON_OK .
252 // button2_ is tagged with ConfirmInfoBarDelegate::BUTTON_CANCEL .
253 base::scoped_nsobject<UIButton> button1_;
254 base::scoped_nsobject<UIButton> button2_;
255 // Drop shadow.
256 base::scoped_nsobject<UIImageView> shadow_;
257 }
258
259 @synthesize visibleHeight = visibleHeight_;
260
261 - (id)initWithFrame:(CGRect)frame delegate:(InfoBarViewDelegate*)delegate {
262 self = [super initWithFrame:frame];
263 if (self) {
264 delegate_ = delegate;
265 // Make the drop shadow.
266 UIImage* shadowImage = [UIImage imageNamed:@"infobar_shadow"];
267 shadow_.reset([[UIImageView alloc] initWithImage:shadowImage]);
268 [self addSubview:shadow_];
269 [self setAutoresizingMask:UIViewAutoresizingFlexibleWidth |
270 UIViewAutoresizingFlexibleHeight];
271 [self setAccessibilityViewIsModal:YES];
272 }
273 return self;
274 }
275
276 - (void)dealloc {
277 [super dealloc];
278 }
279
280 - (NSString*)markedLabel {
281 return markedLabel_;
282 }
283
284 - (void)resetDelegate {
285 delegate_ = NULL;
286 }
287
288 // Returns the width reserved for the icon.
289 - (CGFloat)leftMarginOnFirstLine {
290 CGFloat leftMargin = 0;
291 if (imageViewContainer_) {
292 leftMargin += [self frameOfIcon].size.width;
293 // The margin between the label and the icon is the same as the margin
294 // between the edge of the screen and the icon.
295 leftMargin += 2 * [self frameOfIcon].origin.x;
296 } else {
297 leftMargin += kLeftMarginOnFirstLineWhenIconAbsent;
298 }
299 return leftMargin;
300 }
301
302 // Returns the width reserved for the close button.
303 - (CGFloat)rightMarginOnFirstLine {
304 return
305 [closeButton_ imageView].image.size.width + kCloseButtonInnerPadding * 2;
306 }
307
308 // Returns the horizontal space available between the icon and the close
309 // button.
310 - (CGFloat)horizontalSpaceAvailableOnFirstLine {
311 return [self frame].size.width - [self leftMarginOnFirstLine] -
312 [self rightMarginOnFirstLine];
313 }
314
315 // Returns the height taken by a label constrained by a width of |width|.
316 - (CGFloat)heightRequiredForLabelWithWidth:(CGFloat)width {
317 return [label_ sizeThatFits:CGSizeMake(width, CGFLOAT_MAX)].height;
318 }
319
320 // Returns the width required by a label if it was displayed on a single line.
321 - (CGFloat)widthOfLabelOnASingleLine {
322 // |label_| can be nil when delegate returns "" for GetMessageText().
323 if (!label_)
324 return 0.0;
325 CGSize rect = [[label_ text] cr_pixelAlignedSizeWithFont:[label_ font]];
326 return rect.width;
327 }
328
329 // Returns the minimum size required by |button| to be properly displayed.
330 - (CGFloat)narrowestWidthOfButton:(UIButton*)button {
331 if (!button)
332 return 0;
333 // The button itself is queried for the size. The width is rounded up to be a
334 // multiple of 8 to fit Material grid spacing requirements.
335 CGFloat labelWidth =
336 [button sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)].width;
337 return ceil(labelWidth / kButtonWidthUnits) * kButtonWidthUnits;
338 }
339
340 // Returns the width of the buttons if they are laid out on the first line.
341 - (CGFloat)widthOfButtonsOnFirstLine {
342 CGFloat width = [self narrowestWidthOfButton:button1_] +
343 [self narrowestWidthOfButton:button2_];
344 if (button1_ && button2_) {
345 width += kSpaceBetweenWidgets;
346 }
347 return width;
348 }
349
350 // Returns the width needed for the switch.
351 - (CGFloat)preferredWidthOfSwitch {
352 return [switchView_ preferredWidth];
353 }
354
355 // Returns the space required to separate the left aligned widgets (label) from
356 // the right aligned widgets (switch, buttons), assuming they fit on one line.
357 - (CGFloat)widthToSeparateRightAndLeftWidgets {
358 BOOL leftWidgetsArePresent = (label_ != nil);
359 BOOL rightWidgetsArePresent = button1_ || button2_ || switchView_;
360 if (!leftWidgetsArePresent || !rightWidgetsArePresent)
361 return 0;
362 return kMinimumSpaceBetweenRightAndLeftAlignedWidgets;
363 }
364
365 // Returns the space required to separate the switch and the buttons.
366 - (CGFloat)widthToSeparateSwitchAndButtons {
367 BOOL buttonsArePresent = button1_ || button2_;
368 BOOL switchIsPresent = (switchView_ != nil);
369 if (!buttonsArePresent || !switchIsPresent)
370 return 0;
371 return kSpaceBetweenWidgets;
372 }
373
374 // Lays out |button| at the height |y| and in the position |position|.
375 // Must only be used for wide buttons, i.e. buttons not on the first line.
376 - (void)layoutWideButton:(UIButton*)button
377 y:(CGFloat)y
378 position:(InfoBarButtonPosition)position {
379 CGFloat screenWidth = [self frame].size.width;
380 CGFloat startPercentage = 0.0;
381 CGFloat endPercentage = 0.0;
382 switch (position) {
383 case LEFT:
384 startPercentage = 0.0;
385 endPercentage = 0.5;
386 break;
387 case RIGHT:
388 startPercentage = 0.5;
389 endPercentage = 1.0;
390 break;
391 case CENTER:
392 startPercentage = 0.0;
393 endPercentage = 1.0;
394 break;
395 case ON_FIRST_LINE:
396 NOTREACHED();
397 }
398 DCHECK(startPercentage >= 0.0 && startPercentage <= 1.0);
399 DCHECK(endPercentage >= 0.0 && endPercentage <= 1.0);
400 DCHECK(startPercentage < endPercentage);
401 // In Material the button is not stretched to fit the available space. It is
402 // placed centrally in the allotted space.
403 CGFloat minX = screenWidth * startPercentage;
404 CGFloat maxX = screenWidth * endPercentage;
405 CGFloat midpoint = (minX + maxX) / 2;
406 CGFloat minWidth =
407 std::min([self narrowestWidthOfButton:button], maxX - minX);
408 CGFloat left = midpoint - minWidth / 2;
409 CGRect frame = CGRectMake(left, y, minWidth, kButtonHeight);
410 frame = AlignRectOriginAndSizeToPixels(frame);
411 [button setFrame:frame];
412 }
413
414 - (CGFloat)layoutWideButtonAlignRight:(UIButton*)button
415 rightEdge:(CGFloat)rightEdge
416 y:(CGFloat)y {
417 CGFloat width = [self narrowestWidthOfButton:button];
418 CGFloat leftEdge = rightEdge - width;
419 CGRect frame = CGRectMake(leftEdge, y, width, kButtonHeight);
420 frame = AlignRectOriginAndSizeToPixels(frame);
421 [button setFrame:frame];
422 return leftEdge;
423 }
424
425 - (CGFloat)heightThatFitsButtonsUnderOtherWidgets:(CGFloat)heightOfFirstLine
426 layout:(BOOL)layout {
427 if (button1_ && button2_) {
428 CGFloat halfWidthOfScreen = [self frame].size.width / 2.0;
429 if ([self narrowestWidthOfButton:button1_] <= halfWidthOfScreen &&
430 [self narrowestWidthOfButton:button2_] <= halfWidthOfScreen) {
431 // Each button can fit in half the screen's width.
432 if (layout) {
433 // When there are two buttons on one line, they are positioned aligned
434 // right in the available space, spaced apart by kButtonSpacing.
435 CGFloat leftOfRightmostButton =
436 [self layoutWideButtonAlignRight:button1_
437 rightEdge:CGRectGetWidth(self.bounds) -
438 kButtonMargin
439 y:heightOfFirstLine];
440 [self layoutWideButtonAlignRight:button2_
441 rightEdge:leftOfRightmostButton - kButtonSpacing
442 y:heightOfFirstLine];
443 }
444 return kButtonHeight;
445 } else {
446 // At least one of the two buttons is larger than half the screen's width,
447 // so |button2_| is placed underneath |button1_|.
448 if (layout) {
449 [self layoutWideButton:button1_ y:heightOfFirstLine position:CENTER];
450 [self layoutWideButton:button2_
451 y:heightOfFirstLine + kButtonHeight
452 position:CENTER];
453 }
454 return 2 * kButtonHeight;
455 }
456 }
457 // There is at most 1 button to layout.
458 UIButton* button = button1_ ? button1_ : button2_;
459 if (button) {
460 if (layout) {
461 // Where is there is just one button it is positioned aligned right in the
462 // available space.
463 [self
464 layoutWideButtonAlignRight:button
465 rightEdge:CGRectGetWidth(self.bounds) - kButtonMargin
466 y:heightOfFirstLine];
467 }
468 return kButtonHeight;
469 }
470 return 0;
471 }
472
473 - (CGFloat)computeRequiredHeightAndLayoutSubviews:(BOOL)layout {
474 CGFloat requiredHeight = 0;
475 CGFloat widthOfLabel = [self widthOfLabelOnASingleLine] +
476 [self widthToSeparateRightAndLeftWidgets];
477 CGFloat widthOfButtons = [self widthOfButtonsOnFirstLine];
478 CGFloat preferredWidthOfSwitch = [self preferredWidthOfSwitch];
479 CGFloat widthOfScreen = [self frame].size.width;
480 CGFloat rightMarginOnFirstLine = [self rightMarginOnFirstLine];
481 CGFloat spaceAvailableOnFirstLine =
482 [self horizontalSpaceAvailableOnFirstLine];
483 CGFloat widthOfButtonAndSwitch = widthOfButtons +
484 [self widthToSeparateSwitchAndButtons] +
485 preferredWidthOfSwitch;
486 // Tests if the label, switch, and buttons can fit on a single line.
487 if (widthOfLabel + widthOfButtonAndSwitch < spaceAvailableOnFirstLine) {
488 // The label, switch, and buttons can fit on a single line.
489 requiredHeight = kMinimumInfobarHeight;
490 if (layout) {
491 // Lays out the close button.
492 CGRect buttonFrame = [self frameOfCloseButton:YES];
493 [closeButton_ setFrame:buttonFrame];
494 // Lays out the label.
495 CGFloat labelHeight = [self heightRequiredForLabelWithWidth:widthOfLabel];
496 CGRect frame = CGRectMake([self leftMarginOnFirstLine],
497 (kMinimumInfobarHeight - labelHeight) / 2,
498 [self widthOfLabelOnASingleLine], labelHeight);
499 frame = AlignRectOriginAndSizeToPixels(frame);
500 [label_ setFrame:frame];
501 // Layouts the buttons.
502 CGFloat buttonMargin =
503 rightMarginOnFirstLine + kExtraButtonMarginOnSingleLine;
504 if (button1_) {
505 CGFloat width = [self narrowestWidthOfButton:button1_];
506 CGFloat offset = width;
507 frame = CGRectMake(widthOfScreen - buttonMargin - offset,
508 (kMinimumInfobarHeight - kButtonHeight) / 2, width,
509 kButtonHeight);
510 frame = AlignRectOriginAndSizeToPixels(frame);
511 [button1_ setFrame:frame];
512 }
513 if (button2_) {
514 CGFloat width = [self narrowestWidthOfButton:button2_];
515 CGFloat offset = widthOfButtons;
516 frame = CGRectMake(widthOfScreen - buttonMargin - offset,
517 (kMinimumInfobarHeight - kButtonHeight) / 2, width,
518 frame.size.height = kButtonHeight);
519 frame = AlignRectOriginAndSizeToPixels(frame);
520 [button2_ setFrame:frame];
521 }
522 // Lays out the switch view to the left of the buttons.
523 if (switchView_) {
524 frame = CGRectMake(
525 widthOfScreen - buttonMargin - widthOfButtonAndSwitch,
526 (kMinimumInfobarHeight - [switchView_ frame].size.height) / 2.0,
527 preferredWidthOfSwitch, [switchView_ frame].size.height);
528 frame = AlignRectOriginAndSizeToPixels(frame);
529 [switchView_ setFrame:frame];
530 }
531 }
532 } else {
533 // The widgets (label, switch, buttons) can't fit on a single line. Attempts
534 // to lay out the label and switch on the first line, and the buttons
535 // underneath.
536 CGFloat heightOfLabelAndSwitch = 0;
537
538 if (layout) {
539 // Lays out the close button.
540 CGRect buttonFrame = [self frameOfCloseButton:NO];
541 [closeButton_ setFrame:buttonFrame];
542 }
543 if (widthOfLabel + preferredWidthOfSwitch < spaceAvailableOnFirstLine) {
544 // The label and switch can fit on the first line.
545 heightOfLabelAndSwitch = kMinimumInfobarHeight;
546 if (layout) {
547 CGFloat labelHeight =
548 [self heightRequiredForLabelWithWidth:widthOfLabel];
549 CGRect labelFrame =
550 CGRectMake([self leftMarginOnFirstLine],
551 (heightOfLabelAndSwitch - labelHeight) / 2,
552 [self widthOfLabelOnASingleLine], labelHeight);
553 labelFrame = AlignRectOriginAndSizeToPixels(labelFrame);
554 [label_ setFrame:labelFrame];
555 if (switchView_) {
556 CGRect switchRect = CGRectMake(
557 widthOfScreen - rightMarginOnFirstLine - preferredWidthOfSwitch,
558 (heightOfLabelAndSwitch - [switchView_ frame].size.height) / 2,
559 preferredWidthOfSwitch, [switchView_ frame].size.height);
560 switchRect = AlignRectOriginAndSizeToPixels(switchRect);
561 [switchView_ setFrame:switchRect];
562 }
563 }
564 } else {
565 // The label and switch can't fit on the first line, so lay them out on
566 // different lines.
567 // Computes the height of the label, and optionally lays it out.
568 CGFloat labelMarginBottom = kLabelMarginBottom;
569 if (button1_ || button2_) {
570 // Material features more padding between the label and the button than
571 // the label and the bottom of the dialog when there is no button.
572 labelMarginBottom += kExtraMarginBetweenLabelAndButton;
573 }
574 CGFloat heightOfLabelWithPadding =
575 [self heightRequiredForLabelWithWidth:spaceAvailableOnFirstLine] +
576 kLabelMarginTop + labelMarginBottom;
577 if (layout) {
578 CGRect labelFrame = CGRectMake(
579 [self leftMarginOnFirstLine], kLabelMarginTop,
580 spaceAvailableOnFirstLine,
581 heightOfLabelWithPadding - kLabelMarginTop - labelMarginBottom);
582 labelFrame = AlignRectOriginAndSizeToPixels(labelFrame);
583 [label_ setFrame:labelFrame];
584 }
585 // Computes the height of the switch view (if any), and optionally lays it
586 // out.
587 CGFloat heightOfSwitchWithPadding = 0;
588 if (switchView_ != nil) {
589 // The switch view is aligned with the first line's label, hence the
590 // call to |leftMarginOnFirstLine|.
591 CGFloat widthAvailableForSwitchView = [self frame].size.width -
592 [self leftMarginOnFirstLine] -
593 kRightMargin;
594 CGFloat heightOfSwitch = [switchView_
595 heightRequiredForSwitchWithWidth:widthAvailableForSwitchView
596 layout:layout];
597 // If there are buttons underneath the switch, add padding.
598 if (button1_ || button2_) {
599 heightOfSwitchWithPadding = heightOfSwitch + kSpaceBetweenWidgets +
600 kExtraMarginBetweenLabelAndButton;
601 } else {
602 heightOfSwitchWithPadding = heightOfSwitch;
603 }
604 if (layout) {
605 CGRect switchRect =
606 CGRectMake([self leftMarginOnFirstLine], heightOfLabelWithPadding,
607 widthAvailableForSwitchView, heightOfSwitch);
608 switchRect = AlignRectOriginAndSizeToPixels(switchRect);
609 [switchView_ setFrame:switchRect];
610 }
611 }
612 heightOfLabelAndSwitch =
613 std::max(heightOfLabelWithPadding + heightOfSwitchWithPadding,
614 kMinimumInfobarHeight);
615 }
616 // Lays out the button(s) under the label and switch.
617 CGFloat heightOfButtons =
618 [self heightThatFitsButtonsUnderOtherWidgets:heightOfLabelAndSwitch
619 layout:layout];
620 requiredHeight = heightOfLabelAndSwitch;
621 if (heightOfButtons > 0)
622 requiredHeight += heightOfButtons + kButtonMargin;
623 }
624 return requiredHeight;
625 }
626
627 - (CGSize)sizeThatFits:(CGSize)size {
628 CGFloat requiredHeight = [self computeRequiredHeightAndLayoutSubviews:NO];
629 return CGSizeMake([self frame].size.width, requiredHeight);
630 }
631
632 - (void)layoutSubviews {
633 // Lays out the position of the icon.
634 [imageViewContainer_ setFrame:[self frameOfIcon]];
635 targetHeight_ = [self computeRequiredHeightAndLayoutSubviews:YES];
636
637 if (delegate_)
638 delegate_->SetInfoBarTargetHeight(targetHeight_);
639 [self resetBackground];
640
641 // Asks the BidiContainerView to reposition of all the subviews.
642 for (UIView* view in [self subviews])
643 [self setSubviewNeedsAdjustmentForRTL:view];
644 [super layoutSubviews];
645 }
646
647 - (void)resetBackground {
648 UIColor* color = [UIColor whiteColor];
649 [self setBackgroundColor:color];
650 CGFloat shadowY = 0;
651 shadowY = -[shadow_ image].size.height; // Shadow above the infobar.
652 [shadow_ setFrame:CGRectMake(0, shadowY, self.bounds.size.width,
653 [shadow_ image].size.height)];
654 [shadow_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
655 }
656
657 - (void)addCloseButtonWithTag:(NSInteger)tag
658 target:(id)target
659 action:(SEL)action {
660 DCHECK(!closeButton_);
661 // TODO(jeanfrancoisg): Add IDR_ constant and use GetNativeImageNamed().
662 // crbug/228611
663 NSString* imagePath =
664 [[NSBundle mainBundle] pathForResource:@"infobar_close" ofType:@"png"];
665 UIImage* image = [UIImage imageWithContentsOfFile:imagePath];
666 closeButton_.reset([[UIButton buttonWithType:UIButtonTypeCustom] retain]);
667 [closeButton_ setExclusiveTouch:YES];
668 [closeButton_ setImage:image forState:UIControlStateNormal];
669 [closeButton_ addTarget:target
670 action:action
671 forControlEvents:UIControlEventTouchUpInside];
672 [closeButton_ setTag:tag];
673 [closeButton_ setAccessibilityLabel:l10n_util::GetNSString(IDS_CLOSE)];
674 [self addSubview:closeButton_];
675 }
676
677 - (void)addSwitchWithLabel:(NSString*)label
678 isOn:(BOOL)isOn
679 tag:(NSInteger)tag
680 target:(id)target
681 action:(SEL)action {
682 switchView_.reset([[SwitchView alloc] initWithLabel:label isOn:isOn]);
683 [switchView_ setTag:tag target:target action:action];
684 [self addSubview:switchView_];
685 }
686
687 - (void)addLeftIcon:(UIImage*)image {
688 if (!imageViewContainer_) {
689 imageViewContainer_.reset([[UIView alloc] init]);
690 [self addSubview:imageViewContainer_];
691 }
692 imageView_.reset([[UIImageView alloc] initWithImage:image]);
693 [imageViewContainer_ addSubview:imageView_];
694 }
695
696 - (void)addPlaceholderTransparentIcon:(CGSize const&)imageSize {
697 UIGraphicsBeginImageContext(imageSize);
698 UIImage* placeholder = UIGraphicsGetImageFromCurrentImageContext();
699 UIGraphicsEndImageContext();
700 [self addLeftIcon:placeholder];
701 }
702
703 // Since shadows & rounded corners cannot be applied simultaneously to a
704 // UIView, this method adds rounded corners to the UIImageView and then adds
705 // drop shadow to the UIView containing the UIImageView.
706 - (void)addLeftIconWithRoundedCornersAndShadow:(UIImage*)image {
707 CGFloat effectScaleFactor = image.size.width / kBaseSizeForEffects;
708 [self addLeftIcon:image];
709 CALayer* layer = [imageView_ layer];
710 [layer setMasksToBounds:YES];
711 [layer setCornerRadius:kCornerRadius * effectScaleFactor];
712 layer = [imageViewContainer_ layer];
713 [layer setShadowColor:[UIColor blackColor].CGColor];
714 [layer
715 setShadowOffset:CGSizeMake(0, kShadowVerticalOffset * effectScaleFactor)];
716 [layer setShadowOpacity:kShadowOpacity];
717 [layer setShadowRadius:kShadowRadius * effectScaleFactor];
718 [imageViewContainer_ setClipsToBounds:NO];
719 }
720
721 - (NSString*)stripMarkersFromString:(NSString*)string {
722 linkRanges_.clear();
723 for (;;) {
724 // Find the opening marker, followed by the tag between parentheses.
725 NSRange startingRange =
726 [string rangeOfString:[[InfoBarView openingMarkerForLink]
727 stringByAppendingString:@"("]];
728 if (!startingRange.length)
729 return [[string copy] autorelease];
730 // Read the tag.
731 NSUInteger beginTag = NSMaxRange(startingRange);
732 NSRange closingParenthesis = [string
733 rangeOfString:@")"
734 options:NSLiteralSearch
735 range:NSMakeRange(beginTag, [string length] - beginTag)];
736 if (closingParenthesis.location == NSNotFound)
737 return [[string copy] autorelease];
738 NSInteger tag = [[string
739 substringWithRange:NSMakeRange(beginTag, closingParenthesis.location -
740 beginTag)] integerValue];
741 // If the parsing fails, |tag| is 0. Negative values are not allowed.
742 if (tag <= 0)
743 return [[string copy] autorelease];
744 // Find the closing marker.
745 startingRange.length =
746 closingParenthesis.location - startingRange.location + 1;
747 NSRange endingRange =
748 [string rangeOfString:[InfoBarView closingMarkerForLink]];
749 DCHECK(endingRange.length);
750 // Compute range of link in stripped string and add it to the array.
751 NSRange rangeOfLinkInStrippedString =
752 NSMakeRange(startingRange.location,
753 endingRange.location - NSMaxRange(startingRange));
754 linkRanges_.push_back(std::make_pair(tag, rangeOfLinkInStrippedString));
755 // Creates a new string without the markers.
756 NSString* beforeLink = [string substringToIndex:startingRange.location];
757 NSRange rangeOfLink =
758 NSMakeRange(NSMaxRange(startingRange),
759 endingRange.location - NSMaxRange(startingRange));
760 NSString* link = [string substringWithRange:rangeOfLink];
761 NSString* afterLink = [string substringFromIndex:NSMaxRange(endingRange)];
762 string = [NSString stringWithFormat:@"%@%@%@", beforeLink, link, afterLink];
763 }
764 }
765
766 - (void)addLabel:(NSString*)label {
767 [self addLabel:label target:nil action:nil];
768 }
769
770 - (void)addLabel:(NSString*)text target:(id)target action:(SEL)action {
771 markedLabel_.reset([text copy]);
772 if (target)
773 text = [self stripMarkersFromString:text];
774 if ([label_ superview]) {
775 [label_ removeFromSuperview];
776 }
777
778 label_ = [[[UILabel alloc] initWithFrame:CGRectZero] autorelease];
779
780 UIFont* font = [MDCTypography subheadFont];
781
782 [label_ setBackgroundColor:[UIColor clearColor]];
783
784 NSMutableParagraphStyle* paragraphStyle =
785 [[[NSMutableParagraphStyle alloc] init] autorelease];
786 paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
787 paragraphStyle.lineSpacing = kLabelLineSpacing;
788 NSDictionary* attributes = @{
789 NSParagraphStyleAttributeName : paragraphStyle,
790 NSFontAttributeName : font,
791 };
792 [label_ setNumberOfLines:0];
793
794 [label_ setAttributedText:[[[NSAttributedString alloc]
795 initWithString:text
796 attributes:attributes] autorelease]];
797
798 [self addSubview:label_];
799
800 if (linkRanges_.empty())
801 return;
802
803 DCHECK([target respondsToSelector:action]);
804
805 labelLinkController_.reset([[LabelLinkController alloc]
806 initWithLabel:label_
807 action:^(const GURL& gurl) {
808 NSUInteger actionTag = [base::SysUTF8ToNSString(
809 gurl.ExtractFileName()) integerValue];
810 [target performSelector:action withObject:@(actionTag)];
811 }]);
812
813 [labelLinkController_ setLinkUnderlineStyle:NSUnderlineStyleSingle];
814 [labelLinkController_ setLinkColor:[UIColor blackColor]];
815
816 std::vector<std::pair<NSUInteger, NSRange>>::const_iterator it;
817 for (it = linkRanges_.begin(); it != linkRanges_.end(); ++it) {
818 // The last part of the URL contains the tag, so it can be retrieved in the
819 // callback. This tag is generally a command ID.
820 std::string url = std::string(kChromeInfobarURL) +
821 std::string(std::to_string((int)it->first));
822 [labelLinkController_ addLinkWithRange:it->second url:GURL(url)];
823 }
824 }
825
826 - (void)addButton1:(NSString*)title1
827 tag1:(NSInteger)tag1
828 button2:(NSString*)title2
829 tag2:(NSInteger)tag2
830 target:(id)target
831 action:(SEL)action {
832 button1_.reset([[self infoBarButton:title1
833 palette:[MDCPalette cr_bluePalette]
834 customTitleColor:[UIColor whiteColor]
835 tag:tag1
836 target:target
837 action:action] retain]);
838 [self addSubview:button1_];
839
840 button2_.reset([[self infoBarButton:title2
841 palette:nil
842 customTitleColor:UIColorFromRGB(kButton2TitleColor)
843 tag:tag2
844 target:target
845 action:action] retain]);
846 [self addSubview:button2_];
847 }
848
849 - (void)addButton:(NSString*)title
850 tag:(NSInteger)tag
851 target:(id)target
852 action:(SEL)action {
853 if (![title length])
854 return;
855 button1_.reset([[self infoBarButton:title
856 palette:[MDCPalette cr_bluePalette]
857 customTitleColor:[UIColor whiteColor]
858 tag:tag
859 target:target
860 action:action] retain]);
861 [self addSubview:button1_];
862 }
863
864 // Initializes and returns a button for the infobar, with the specified
865 // |message| and colors.
866 - (UIButton*)infoBarButton:(NSString*)message
867 palette:(MDCPalette*)palette
868 customTitleColor:(UIColor*)customTitleColor
869 tag:(NSInteger)tag
870 target:(id)target
871 action:(SEL)action {
872 base::scoped_nsobject<MDCFlatButton> button([[MDCFlatButton alloc] init]);
873 button.get().inkColor = [[palette tint300] colorWithAlphaComponent:0.5f];
874 [button setBackgroundColor:[palette tint500] forState:UIControlStateNormal];
875 [button setBackgroundColor:[UIColor colorWithWhite:0.8f alpha:1.0f]
876 forState:UIControlStateDisabled];
877 if (palette)
878 button.get().hasOpaqueBackground = YES;
879 if (customTitleColor)
880 button.get().customTitleColor = customTitleColor;
881 button.get().titleLabel.adjustsFontSizeToFitWidth = YES;
882 button.get().titleLabel.minimumScaleFactor = 0.6f;
883 [button setTitle:message forState:UIControlStateNormal];
884 [button setTag:tag];
885 [button addTarget:target
886 action:action
887 forControlEvents:UIControlEventTouchUpInside];
888 // Without the call to layoutIfNeeded, |button| returns an incorrect
889 // titleLabel the first time it is accessed in |narrowestWidthOfButton|.
890 [button layoutIfNeeded];
891 return button.autorelease();
892 }
893
894 - (CGRect)frameOfCloseButton:(BOOL)singleLineMode {
895 DCHECK(closeButton_);
896 // Add padding to increase the touchable area.
897 CGSize closeButtonSize = [closeButton_ imageView].image.size;
898 closeButtonSize.width += kCloseButtonInnerPadding * 2;
899 closeButtonSize.height += kCloseButtonInnerPadding * 2;
900 CGFloat x = CGRectGetMaxX(self.frame) - closeButtonSize.width;
901 // Aligns the close button at the top (height includes touch padding).
902 CGFloat y = 0;
903 if (singleLineMode) {
904 // On single-line mode the button is centered vertically.
905 y = ui::AlignValueToUpperPixel(
906 (kMinimumInfobarHeight - closeButtonSize.height) * 0.5);
907 }
908 return CGRectMake(x, y, closeButtonSize.width, closeButtonSize.height);
909 }
910
911 - (CGRect)frameOfIcon {
912 CGSize iconSize = [imageView_ image].size;
913 CGFloat y = kButtonsTopMargin;
914 CGFloat x = kCloseButtonLeftMargin;
915 return CGRectMake(AlignValueToPixel(x), AlignValueToPixel(y), iconSize.width,
916 iconSize.height);
917 }
918
919 + (NSString*)openingMarkerForLink {
920 return @"$LINK_START";
921 }
922
923 + (NSString*)closingMarkerForLink {
924 return @"$LINK_END";
925 }
926
927 + (NSString*)stringAsLink:(NSString*)string tag:(NSUInteger)tag {
928 DCHECK_NE(0u, tag);
929 return [NSString stringWithFormat:@"%@(%" PRIuNS ")%@%@",
930 [InfoBarView openingMarkerForLink], tag,
931 string, [InfoBarView closingMarkerForLink]];
932 }
933
934 #pragma mark - Testing
935
936 - (CGFloat)minimumInfobarHeight {
937 return kMinimumInfobarHeight;
938 }
939
940 - (CGFloat)buttonsHeight {
941 return kButtonHeight;
942 }
943
944 - (CGFloat)buttonMargin {
945 return kButtonMargin;
946 }
947
948 - (const std::vector<std::pair<NSUInteger, NSRange>>&)linkRanges {
949 return linkRanges_;
950 }
951
952 @end
OLDNEW
« 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