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