autofill_details_container.mm revision 7dbb3d5cf0c15f500944d211057644d6a2f37371
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.
4
5#import "chrome/browser/ui/cocoa/autofill/autofill_details_container.h"
6
7#include <algorithm>
8
9#include "base/mac/foundation_util.h"
10#include "chrome/browser/ui/autofill/autofill_dialog_controller.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"
14
15namespace {
16
17// Imported constant from Views version. TODO(groby): Share.
18SkColor const kWarningColor = 0xffde4932;  // SkColorSetRGB(0xde, 0x49, 0x32);
19
20}  // namespace
21
22@interface AutofillDetailsContainer ()
23// Compute infobubble origin based on anchor/view.
24- (NSPoint)originFromAnchorView:(NSView*)view;
25@end
26
27@implementation AutofillDetailsContainer
28
29- (id)initWithController:(autofill::AutofillDialogController*)controller {
30  if (self = [super init]) {
31    controller_ = controller;
32  }
33  return self;
34}
35
36- (void)addSection:(autofill::DialogSection)section {
37  base::scoped_nsobject<AutofillSectionContainer> sectionContainer(
38      [[AutofillSectionContainer alloc] initWithController:controller_
39                                                forSection:section]);
40  [sectionContainer setValidationDelegate:self];
41  [details_ addObject:sectionContainer];
42}
43
44- (void)loadView {
45  details_.reset([[NSMutableArray alloc] init]);
46
47  [self addSection:autofill::SECTION_EMAIL];
48  [self addSection:autofill::SECTION_CC];
49  [self addSection:autofill::SECTION_BILLING];
50  [self addSection:autofill::SECTION_CC_BILLING];
51  [self addSection:autofill::SECTION_SHIPPING];
52
53  [self setView:[[NSView alloc] init]];
54  for (AutofillSectionContainer* container in details_.get())
55    [[self view] addSubview:[container view]];
56
57  infoBubble_.reset([[InfoBubbleView alloc] initWithFrame:NSZeroRect]);
58  [infoBubble_ setBackgroundColor:
59      gfx::SkColorToCalibratedNSColor(kWarningColor)];
60  [infoBubble_ setArrowLocation:info_bubble::kTopRight];
61  [infoBubble_ setAlignment:info_bubble::kAlignArrowToAnchor];
62  [infoBubble_ setHidden:YES];
63
64  base::scoped_nsobject<NSTextField> label([[NSTextField alloc] init]);
65  [label setEditable:NO];
66  [label setBordered:NO];
67  [label setDrawsBackground:NO];
68  [infoBubble_ addSubview:label];
69
70  [[self view] addSubview:infoBubble_];
71
72  [self performLayout];
73}
74
75- (NSSize)preferredSize {
76  NSSize size = NSZeroSize;
77  for (AutofillSectionContainer* container in details_.get()) {
78    NSSize containerSize = [container preferredSize];
79    size.height += containerSize.height;
80    size.width = std::max(containerSize.width, size.width);
81  }
82  return size;
83}
84
85- (void)performLayout {
86  NSRect rect = NSZeroRect;
87  for (AutofillSectionContainer* container in
88      [details_ reverseObjectEnumerator]) {
89    if (![[container view] isHidden]) {
90      [container performLayout];
91      [[container view] setFrameOrigin:NSMakePoint(0, NSMaxY(rect))];
92      rect = NSUnionRect(rect, [[container view] frame]);
93    }
94  }
95
96  [[self view] setFrameSize:[self preferredSize]];
97}
98
99- (AutofillSectionContainer*)sectionForId:(autofill::DialogSection)section {
100  for (AutofillSectionContainer* details in details_.get()) {
101    if ([details section] == section)
102      return details;
103  }
104  return nil;
105}
106
107- (void)modelChanged {
108  for (AutofillSectionContainer* details in details_.get())
109    [details modelChanged];
110}
111
112- (BOOL)validate {
113  bool allValid = true;
114  for (AutofillSectionContainer* details in details_.get()) {
115    if (![[details view] isHidden])
116      allValid = [details validateFor:autofill::VALIDATE_FINAL] && allValid;
117  }
118  return allValid;
119}
120
121// TODO(groby): Unify with BaseBubbleController's originFromAnchor:view:.
122- (NSPoint)originFromAnchorView:(NSView*)view {
123  NSView* bubbleParent = [infoBubble_ superview];
124  NSPoint origin = [[view superview] convertPoint:[view frame].origin
125                                           toView:nil];
126  NSRect bubbleFrame =
127      [bubbleParent convertRect:[infoBubble_ frame] toView:nil];
128
129  NSSize offsets = NSMakeSize(info_bubble::kBubbleArrowXOffset +
130                              info_bubble::kBubbleArrowWidth / 2.0, 0);
131  offsets = [view convertSize:offsets toView:nil];
132  origin.x -= NSWidth(bubbleFrame) - offsets.width;
133
134  origin.y -= NSHeight(bubbleFrame);
135  return [bubbleParent convertPoint:origin fromView:nil];
136}
137
138- (void)updateMessageForField:(NSControl<AutofillInputField>*)field {
139  // Ignore fields that are not first responder. Testing this is a bit
140  // convoluted, since for NSTextFields with firstResponder status, the
141  // firstResponder is a subview of the NSTextField, not the field itself.
142  NSView* firstResponderView =
143      base::mac::ObjCCast<NSView>([[field window] firstResponder]);
144  if (![firstResponderView isDescendantOf:field]) {
145    return;
146  }
147
148  if ([field invalid]) {
149    const CGFloat labelInset = 3.0;
150
151    NSTextField* label = [[infoBubble_ subviews] objectAtIndex:0];
152    [label setStringValue:[field validityMessage]];
153    [label sizeToFit];
154    NSSize bubbleSize = [label frame].size;
155    bubbleSize.width += 2 * labelInset;
156    bubbleSize.height += 2 * labelInset + info_bubble::kBubbleArrowHeight;
157    [infoBubble_ setFrameSize:bubbleSize];
158    [label setFrameOrigin:NSMakePoint(labelInset, labelInset)];
159    [infoBubble_ setFrameOrigin:[self originFromAnchorView:field]];
160    [infoBubble_ setHidden:NO];
161  } else {
162    [infoBubble_ setHidden:YES];
163  }
164}
165
166@end
167