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_details_container.h"
9 #include "base/mac/foundation_util.h"
10 #include "chrome/browser/ui/autofill/autofill_dialog_view_delegate.h"
11 #import "chrome/browser/ui/cocoa/autofill/autofill_section_container.h"
12 #import "chrome/browser/ui/cocoa/info_bubble_view.h"
13 #include "skia/ext/skia_utils_mac.h"
17 // Imported constant from Views version. TODO(groby): Share.
18 SkColor const kWarningColor = 0xffde4932; // SkColorSetRGB(0xde, 0x49, 0x32);
22 @interface AutofillDetailsContainer ()
23 // Compute infobubble origin based on anchor/view.
24 - (NSPoint)originFromAnchorView:(NSView*)view;
27 @implementation AutofillDetailsContainer
29 - (id)initWithDelegate:(autofill::AutofillDialogViewDelegate*)delegate {
30 if (self = [super init]) {
36 - (void)addSection:(autofill::DialogSection)section {
37 base::scoped_nsobject<AutofillSectionContainer> sectionContainer(
38 [[AutofillSectionContainer alloc] initWithDelegate:delegate_
40 [sectionContainer setValidationDelegate:self];
41 [details_ addObject:sectionContainer];
45 details_.reset([[NSMutableArray alloc] init]);
47 [self addSection:autofill::SECTION_CC];
48 [self addSection:autofill::SECTION_BILLING];
49 [self addSection:autofill::SECTION_CC_BILLING];
50 [self addSection:autofill::SECTION_SHIPPING];
52 scrollView_.reset([[NSScrollView alloc] initWithFrame:NSZeroRect]);
53 [scrollView_ setHasVerticalScroller:YES];
54 [scrollView_ setHasHorizontalScroller:NO];
55 [scrollView_ setBorderType:NSNoBorder];
56 [scrollView_ setAutohidesScrollers:YES];
57 [self setView:scrollView_];
59 [scrollView_ setDocumentView:[[NSView alloc] initWithFrame:NSZeroRect]];
61 for (AutofillSectionContainer* container in details_.get())
62 [[scrollView_ documentView] addSubview:[container view]];
64 errorBubble_.reset([[InfoBubbleView alloc] initWithFrame:NSZeroRect]);
65 [errorBubble_ setBackgroundColor:
66 gfx::SkColorToCalibratedNSColor(kWarningColor)];
67 [errorBubble_ setArrowLocation:info_bubble::kTopCenter];
68 [errorBubble_ setAlignment:info_bubble::kAlignArrowToAnchor];
69 [errorBubble_ setHidden:YES];
71 base::scoped_nsobject<NSTextField> label([[NSTextField alloc] init]);
72 [label setEditable:NO];
73 [label setBordered:NO];
74 [label setDrawsBackground:NO];
75 [label setTextColor:[NSColor whiteColor]];
76 [errorBubble_ addSubview:label];
78 [[scrollView_ documentView] addSubview:errorBubble_];
83 - (NSSize)preferredSize {
84 NSSize size = NSZeroSize;
85 for (AutofillSectionContainer* container in details_.get()) {
86 NSSize containerSize = [container preferredSize];
87 size.height += containerSize.height;
88 size.width = std::max(containerSize.width, size.width);
93 - (void)performLayout {
94 NSRect rect = NSZeroRect;
95 for (AutofillSectionContainer* container in
96 [details_ reverseObjectEnumerator]) {
97 if (![[container view] isHidden]) {
98 [container performLayout];
99 [[container view] setFrameOrigin:NSMakePoint(0, NSMaxY(rect))];
100 rect = NSUnionRect(rect, [[container view] frame]);
104 [[scrollView_ documentView] setFrameSize:[self preferredSize]];
107 - (AutofillSectionContainer*)sectionForId:(autofill::DialogSection)section {
108 for (AutofillSectionContainer* details in details_.get()) {
109 if ([details section] == section)
115 - (void)modelChanged {
116 for (AutofillSectionContainer* details in details_.get())
117 [details modelChanged];
121 bool allValid = true;
122 for (AutofillSectionContainer* details in details_.get()) {
123 if (![[details view] isHidden])
124 allValid = [details validateFor:autofill::VALIDATE_FINAL] && allValid;
129 - (void)updateErrorBubble {
130 if (!delegate_->ShouldShowErrorBubble())
131 [errorBubble_ setHidden:YES];
134 // TODO(groby): Unify with BaseBubbleController's originFromAnchor:view:.
135 - (NSPoint)originFromAnchorView:(NSView*)view {
136 // All math done in window coordinates, since views might be flipped.
137 NSRect viewRect = [view convertRect:[view bounds] toView:nil];
138 NSPoint anchorPoint =
139 NSMakePoint(NSMidX(viewRect), NSMinY(viewRect));
140 NSRect bubbleRect = [errorBubble_ convertRect:[errorBubble_ bounds]
142 NSPoint bubbleOrigin = NSMakePoint(anchorPoint.x - NSWidth(bubbleRect) / 2.0,
143 anchorPoint.y - NSHeight(bubbleRect));
144 return [[errorBubble_ superview] convertPoint:bubbleOrigin fromView:nil];
147 - (void)updateMessageForField:(NSControl<AutofillInputField>*)field {
148 // Ignore fields that are not first responder. Testing this is a bit
149 // convoluted, since for NSTextFields with firstResponder status, the
150 // firstResponder is a subview of the NSTextField, not the field itself.
151 NSView* firstResponderView =
152 base::mac::ObjCCast<NSView>([[field window] firstResponder]);
153 if (![firstResponderView isDescendantOf:field])
156 if (!delegate_->ShouldShowErrorBubble()) {
157 DCHECK([errorBubble_ isHidden]);
161 if ([field invalid]) {
162 const CGFloat labelInset = 3.0;
164 NSTextField* label = [[errorBubble_ subviews] objectAtIndex:0];
165 [label setStringValue:[field validityMessage]];
167 NSSize bubbleSize = [label frame].size;
168 bubbleSize.width += 2 * labelInset;
169 bubbleSize.height += 2 * labelInset + info_bubble::kBubbleArrowHeight;
170 [errorBubble_ setFrameSize:bubbleSize];
171 [label setFrameOrigin:NSMakePoint(labelInset, labelInset)];
172 [errorBubble_ setFrameOrigin:[self originFromAnchorView:field]];
173 [errorBubble_ setHidden:NO];
175 [errorBubble_ setHidden:YES];