Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / autofill / autofill_main_container.mm
blobc0f44855b61cf36d1ea651c89912ca14456a4246
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "chrome/browser/ui/cocoa/autofill/autofill_main_container.h"
7 #include <algorithm>
8 #include <cmath>
10 #include "base/mac/foundation_util.h"
11 #include "base/strings/sys_string_conversions.h"
12 #include "chrome/browser/ui/autofill/autofill_dialog_view_delegate.h"
13 #include "chrome/browser/ui/chrome_style.h"
14 #include "chrome/browser/ui/cocoa/autofill/autofill_dialog_constants.h"
15 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_button.h"
16 #import "chrome/browser/ui/cocoa/autofill/autofill_details_container.h"
17 #import "chrome/browser/ui/cocoa/autofill/autofill_notification_container.h"
18 #import "chrome/browser/ui/cocoa/autofill/autofill_tooltip_controller.h"
19 #import "chrome/browser/ui/cocoa/hyperlink_text_view.h"
20 #import "chrome/browser/ui/cocoa/key_equivalent_constants.h"
21 #include "grit/generated_resources.h"
22 #include "grit/theme_resources.h"
23 #include "skia/ext/skia_utils_mac.h"
24 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h"
25 #import "ui/base/cocoa/controls/blue_label_button.h"
26 #include "ui/base/cocoa/window_size_constants.h"
27 #include "ui/base/l10n/l10n_util.h"
28 #include "ui/gfx/range/range.h"
30 namespace {
32 // Padding between buttons and the last suggestion or details view. The mock
33 // has a total of 30px - but 10px are already provided by details/suggestions.
34 const CGFloat kButtonVerticalPadding = 20.0;
36 // Padding around the text for the legal documents.
37 const CGFloat kLegalDocumentsPadding = 20.0;
39 // The font color for the legal documents text. Set to match the Views
40 // implementation.
41 const SkColor kLegalDocumentsTextColor = SkColorSetRGB(102, 102, 102);
43 }  // namespace
45 @interface AutofillMainContainer (Private)
46 - (void)buildWindowButtons;
47 - (void)layoutButtons;
48 - (void)updateButtons;
49 - (NSSize)preferredLegalDocumentSizeForWidth:(CGFloat)width;
50 @end
53 @implementation AutofillMainContainer
55 @synthesize target = target_;
57 - (id)initWithDelegate:(autofill::AutofillDialogViewDelegate*)delegate {
58   if (self = [super init]) {
59     delegate_ = delegate;
60   }
61   return self;
64 - (void)loadView {
65   [self buildWindowButtons];
67   base::scoped_nsobject<NSView> view([[NSView alloc] initWithFrame:NSZeroRect]);
68   [view setAutoresizesSubviews:YES];
69   [view setSubviews:@[buttonContainer_]];
70   [self setView:view];
72   // Set up Wallet icon.
73   buttonStripImage_.reset([[NSImageView alloc] initWithFrame:NSZeroRect]);
74   [self updateWalletIcon];
75   [[self view] addSubview:buttonStripImage_];
77   // Set up "Save in Chrome" checkbox.
78   saveInChromeCheckbox_.reset([[NSButton alloc] initWithFrame:NSZeroRect]);
79   [saveInChromeCheckbox_ setButtonType:NSSwitchButton];
80   [saveInChromeCheckbox_ setTitle:
81       base::SysUTF16ToNSString(delegate_->SaveLocallyText())];
82   [saveInChromeCheckbox_ sizeToFit];
83   [[self view] addSubview:saveInChromeCheckbox_];
85   saveInChromeTooltip_.reset(
86       [[AutofillTooltipController alloc]
87             initWithArrowLocation:info_bubble::kTopCenter]);
88   [saveInChromeTooltip_ setImage:
89       ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed(
90           IDR_AUTOFILL_TOOLTIP_ICON).ToNSImage()];
91   [saveInChromeTooltip_ setMessage:
92       base::SysUTF16ToNSString(delegate_->SaveLocallyTooltip())];
93   [[self view] addSubview:[saveInChromeTooltip_ view]];
94   [self updateSaveInChrome];
96   detailsContainer_.reset(
97       [[AutofillDetailsContainer alloc] initWithDelegate:delegate_]);
98   NSSize frameSize = [[detailsContainer_ view] frame].size;
99   [[detailsContainer_ view] setFrameOrigin:
100       NSMakePoint(0, NSHeight([buttonContainer_ frame]))];
101   frameSize.height += NSHeight([buttonContainer_ frame]);
102   [[self view] setFrameSize:frameSize];
103   [[self view] addSubview:[detailsContainer_ view]];
105   legalDocumentsView_.reset(
106       [[HyperlinkTextView alloc] initWithFrame:NSZeroRect]);
107   [legalDocumentsView_ setEditable:NO];
108   [legalDocumentsView_ setBackgroundColor:
109       [NSColor colorWithCalibratedRed:0.96
110                                 green:0.96
111                                  blue:0.96
112                                 alpha:1.0]];
113   [legalDocumentsView_ setDrawsBackground:YES];
114   [legalDocumentsView_ setTextContainerInset:
115       NSMakeSize(kLegalDocumentsPadding, kLegalDocumentsPadding)];
116   [legalDocumentsView_ setHidden:YES];
117   [legalDocumentsView_ setDelegate:self];
118   legalDocumentsSizeDirty_ = YES;
119   [[self view] addSubview:legalDocumentsView_];
121   notificationContainer_.reset(
122       [[AutofillNotificationContainer alloc] initWithDelegate:delegate_]);
123   [[self view] addSubview:[notificationContainer_ view]];
126 // Called when embedded links are clicked.
127 - (BOOL)textView:(NSTextView*)textView
128     clickedOnLink:(id)link
129           atIndex:(NSUInteger)charIndex {
130   int index = [base::mac::ObjCCastStrict<NSNumber>(link) intValue];
131   delegate_->LegalDocumentLinkClicked(
132       delegate_->LegalDocumentLinks()[index]);
133   return YES;
136 - (NSSize)decorationSizeForWidth:(CGFloat)width {
137   NSSize buttonSize = [buttonContainer_ frame].size;
138   NSSize buttonStripImageSize = [buttonStripImage_ frame].size;
139   NSSize buttonStripSize =
140       NSMakeSize(buttonSize.width + chrome_style::kHorizontalPadding +
141                      buttonStripImageSize.width,
142                  std::max(buttonSize.height + kButtonVerticalPadding,
143                           buttonStripImageSize.height) +
144                      chrome_style::kClientBottomPadding);
146   NSSize size = NSMakeSize(std::max(buttonStripSize.width, width),
147                            buttonStripSize.height);
148   if (![legalDocumentsView_ isHidden]) {
149     NSSize legalDocumentSize =
150         [self preferredLegalDocumentSizeForWidth:width];
151     size.height += legalDocumentSize.height + autofill::kVerticalSpacing;
152   }
154   NSSize notificationSize =
155       [notificationContainer_ preferredSizeForWidth:width];
156   size.height += notificationSize.height;
158   return size;
161 - (NSSize)preferredSize {
162   NSSize detailsSize = [detailsContainer_ preferredSize];
163   NSSize decorationSize = [self decorationSizeForWidth:detailsSize.width];
165   NSSize size = NSMakeSize(std::max(decorationSize.width, detailsSize.width),
166                            decorationSize.height + detailsSize.height);
167   size.height += autofill::kDetailVerticalPadding;
169   return size;
172 - (void)performLayout {
173   NSRect bounds = [[self view] bounds];
175   CGFloat currentY = 0.0;
176   if (![legalDocumentsView_ isHidden]) {
177     [legalDocumentsView_ setFrameSize:
178         [self preferredLegalDocumentSizeForWidth:NSWidth(bounds)]];
179     currentY = NSMaxY([legalDocumentsView_ frame]) + autofill::kVerticalSpacing;
180   }
182   NSRect buttonFrame = [buttonContainer_ frame];
183   buttonFrame.origin.y = currentY + chrome_style::kClientBottomPadding;
184   [buttonContainer_ setFrameOrigin:buttonFrame.origin];
185   currentY = NSMaxY(buttonFrame) + kButtonVerticalPadding;
187   NSPoint walletIconOrigin =
188       NSMakePoint(chrome_style::kHorizontalPadding, buttonFrame.origin.y);
189   [buttonStripImage_ setFrameOrigin:walletIconOrigin];
190   currentY = std::max(currentY, NSMaxY([buttonStripImage_ frame]));
192   NSRect checkboxFrame = [saveInChromeCheckbox_ frame];
193   [saveInChromeCheckbox_ setFrameOrigin:
194       NSMakePoint(chrome_style::kHorizontalPadding,
195                   NSMidY(buttonFrame) - NSHeight(checkboxFrame) / 2.0)];
197   NSRect tooltipFrame = [[saveInChromeTooltip_ view] frame];
198   [[saveInChromeTooltip_ view] setFrameOrigin:
199       NSMakePoint(NSMaxX([saveInChromeCheckbox_ frame]) + autofill::kButtonGap,
200                   NSMidY(buttonFrame) - (NSHeight(tooltipFrame) / 2.0))];
202   NSRect notificationFrame = NSZeroRect;
203   notificationFrame.size = [notificationContainer_ preferredSizeForWidth:
204       NSWidth(bounds)];
206   // Buttons/checkbox/legal take up lower part of view, notifications the
207   // upper part. Adjust the detailsContainer to take up the remainder.
208   CGFloat remainingHeight =
209       NSHeight(bounds) - currentY - NSHeight(notificationFrame);
210   NSRect containerFrame =
211       NSMakeRect(0, currentY, NSWidth(bounds), remainingHeight);
212   [[detailsContainer_ view] setFrame:containerFrame];
213   [detailsContainer_ performLayout];
215   notificationFrame.origin = NSMakePoint(0, NSMaxY(containerFrame));
216   [[notificationContainer_ view] setFrame:notificationFrame];
217   [notificationContainer_ performLayout];
220 - (void)buildWindowButtons {
221   if (buttonContainer_.get())
222     return;
224   buttonContainer_.reset([[GTMWidthBasedTweaker alloc] initWithFrame:
225       ui::kWindowSizeDeterminedLater]);
226   [buttonContainer_
227       setAutoresizingMask:(NSViewMinXMargin | NSViewMaxYMargin)];
229   base::scoped_nsobject<NSButton> button(
230       [[ConstrainedWindowButton alloc] initWithFrame:NSZeroRect]);
231   [button setKeyEquivalent:kKeyEquivalentEscape];
232   [button setTarget:target_];
233   [button setAction:@selector(cancel:)];
234   [button sizeToFit];
235   [buttonContainer_ addSubview:button];
237   CGFloat nextX = NSMaxX([button frame]) + autofill::kButtonGap;
238   button.reset([[BlueLabelButton alloc] initWithFrame:NSZeroRect]);
239   [button setFrameOrigin:NSMakePoint(nextX, 0)];
240   [button setKeyEquivalent:kKeyEquivalentReturn];
241   [button setTarget:target_];
242   [button setAction:@selector(accept:)];
243   [buttonContainer_ addSubview:button];
244   [self updateButtons];
246   NSRect frame = NSMakeRect(
247       -NSMaxX([button frame]) - chrome_style::kHorizontalPadding, 0,
248       NSMaxX([button frame]), NSHeight([button frame]));
249   [buttonContainer_ setFrame:frame];
252 - (void)layoutButtons {
253   base::scoped_nsobject<GTMUILocalizerAndLayoutTweaker> layoutTweaker(
254       [[GTMUILocalizerAndLayoutTweaker alloc] init]);
255   [layoutTweaker tweakUI:buttonContainer_];
257   // Now ensure both buttons have the same height. The second button is
258   // known to be the larger one.
259   CGFloat buttonHeight =
260       NSHeight([[[buttonContainer_ subviews] objectAtIndex:1] frame]);
262   // Force first button to be the same height.
263   NSView* button = [[buttonContainer_ subviews] objectAtIndex:0];
264   NSSize buttonSize = [button frame].size;
265   buttonSize.height = buttonHeight;
266   [button setFrameSize:buttonSize];
269 - (void)updateButtons {
270   NSButton* button = base::mac::ObjCCastStrict<NSButton>(
271       [[buttonContainer_ subviews] objectAtIndex:0]);
272   [button setTitle:base::SysUTF16ToNSString(delegate_->CancelButtonText())];
273   button = base::mac::ObjCCastStrict<NSButton>(
274     [[buttonContainer_ subviews] objectAtIndex:1]);
275   [button setTitle:base::SysUTF16ToNSString(delegate_->ConfirmButtonText())];
276   [self layoutButtons];
279 // Compute the preferred size for the legal documents text, given a width.
280 - (NSSize)preferredLegalDocumentSizeForWidth:(CGFloat)width {
281   // Only recompute if necessary (On text or frame width change).
282   if (!legalDocumentsSizeDirty_ && abs(legalDocumentsSize_.width-width) < 1.0)
283     return legalDocumentsSize_;
285   // There's no direct API to compute desired sizes - use layouting instead.
286   // Layout in a rect with fixed width and "infinite" height.
287   NSRect currentFrame = [legalDocumentsView_ frame];
288   [legalDocumentsView_ setFrame:NSMakeRect(0, 0, width, CGFLOAT_MAX)];
290   // Now use the layout manager to compute layout.
291   NSLayoutManager* layoutManager = [legalDocumentsView_ layoutManager];
292   NSTextContainer* textContainer = [legalDocumentsView_ textContainer];
293   [layoutManager ensureLayoutForTextContainer:textContainer];
294   NSRect newFrame = [layoutManager usedRectForTextContainer:textContainer];
296   // And finally, restore old frame.
297   [legalDocumentsView_ setFrame:currentFrame];
298   newFrame.size.width = width;
300   // Account for the padding around the text.
301   newFrame.size.height += 2 * kLegalDocumentsPadding;
303   legalDocumentsSizeDirty_ = NO;
304   legalDocumentsSize_ = newFrame.size;
305   return legalDocumentsSize_;
308 - (AutofillSectionContainer*)sectionForId:(autofill::DialogSection)section {
309   return [detailsContainer_ sectionForId:section];
312 - (void)modelChanged {
313   [self updateSaveInChrome];
314   [self updateWalletIcon];
315   [self updateButtons];
316   [detailsContainer_ modelChanged];
319 - (BOOL)saveDetailsLocally {
320   return [saveInChromeCheckbox_ state] == NSOnState;
323 - (void)updateLegalDocuments {
324   NSString* text = base::SysUTF16ToNSString(delegate_->LegalDocumentsText());
326   if ([text length]) {
327     NSFont* font =
328         [NSFont labelFontOfSize:[[legalDocumentsView_ font] pointSize]];
329     NSColor* color = gfx::SkColorToCalibratedNSColor(kLegalDocumentsTextColor);
330     [legalDocumentsView_ setMessage:text withFont:font messageColor:color];
332     const std::vector<gfx::Range>& link_ranges =
333         delegate_->LegalDocumentLinks();
334     for (size_t i = 0; i < link_ranges.size(); ++i) {
335       NSRange range = link_ranges[i].ToNSRange();
336       [legalDocumentsView_ addLinkRange:range
337                                withName:@(i)
338                               linkColor:[NSColor blueColor]];
339     }
340     legalDocumentsSizeDirty_ = YES;
341   }
342   [legalDocumentsView_ setHidden:[text length] == 0];
344   // Always request re-layout on state change.
345   id delegate = [[[self view] window] windowController];
346   if ([delegate respondsToSelector:@selector(requestRelayout)])
347     [delegate performSelector:@selector(requestRelayout)];
350 - (void)updateNotificationArea {
351   [notificationContainer_ setNotifications:delegate_->CurrentNotifications()];
352   id delegate = [[[self view] window] windowController];
353   if ([delegate respondsToSelector:@selector(requestRelayout)])
354     [delegate performSelector:@selector(requestRelayout)];
357 - (void)setAnchorView:(NSView*)anchorView {
358   [notificationContainer_ setAnchorView:anchorView];
361 - (BOOL)validate {
362   return [detailsContainer_ validate];
365 - (void)updateSaveInChrome {
366   [saveInChromeCheckbox_ setHidden:!delegate_->ShouldOfferToSaveInChrome()];
367   [[saveInChromeTooltip_ view] setHidden:[saveInChromeCheckbox_ isHidden]];
368   [saveInChromeCheckbox_ setState:
369       (delegate_->ShouldSaveInChrome() ? NSOnState : NSOffState)];
372 - (void)makeFirstInvalidInputFirstResponder {
373   NSView* field = [detailsContainer_ firstInvalidField];
374   if (!field)
375     return;
377   [detailsContainer_ scrollToView:field];
378   [[[self view] window] makeFirstResponder:field];
381 - (void)updateWalletIcon {
382   gfx::Image image = delegate_->ButtonStripImage();
383   [buttonStripImage_ setHidden:image.IsEmpty()];
384   if (![buttonStripImage_ isHidden]) {
385     [buttonStripImage_ setImage:image.ToNSImage()];
386     [buttonStripImage_ setFrameSize:[[buttonStripImage_ image] size]];
387   }
390 - (void)scrollInitialEditorIntoViewAndMakeFirstResponder {
391   // Try to focus on the first invalid field. If there isn't one, focus on the
392   // first editable field instead.
393   NSView* field = [detailsContainer_ firstInvalidField];
394   if (!field)
395     field = [detailsContainer_ firstVisibleField];
396   if (!field)
397     return;
399   [detailsContainer_ scrollToView:field];
400   [[[self view] window] makeFirstResponder:field];
403 - (void)updateErrorBubble {
404   [detailsContainer_ updateErrorBubble];
407 @end
410 @implementation AutofillMainContainer (Testing)
412 - (NSButton*)saveInChromeCheckboxForTesting {
413   return saveInChromeCheckbox_.get();
416 - (NSImageView*)buttonStripImageForTesting {
417   return buttonStripImage_.get();
420 - (NSButton*)saveInChromeTooltipForTesting {
421   return base::mac::ObjCCast<NSButton>([saveInChromeTooltip_ view]);
424 @end