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"
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 #import "chrome/browser/ui/cocoa/autofill/autofill_details_container.h"
15 #include "chrome/browser/ui/cocoa/autofill/autofill_dialog_constants.h"
16 #import "chrome/browser/ui/cocoa/autofill/autofill_notification_container.h"
17 #import "chrome/browser/ui/cocoa/autofill/autofill_tooltip_controller.h"
18 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_button.h"
19 #import "chrome/browser/ui/cocoa/key_equivalent_constants.h"
20 #include "grit/theme_resources.h"
21 #include "skia/ext/skia_utils_mac.h"
22 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h"
23 #import "ui/base/cocoa/controls/blue_label_button.h"
24 #import "ui/base/cocoa/controls/hyperlink_text_view.h"
25 #include "ui/base/cocoa/window_size_constants.h"
26 #include "ui/gfx/range/range.h"
30 // Padding between buttons and the last suggestion or details view. The mock
31 // has a total of 30px - but 10px are already provided by details/suggestions.
32 const CGFloat kButtonVerticalPadding = 20.0;
36 @interface AutofillMainContainer (Private)
37 - (void)buildWindowButtons;
38 - (void)layoutButtons;
39 - (void)updateButtons;
43 @implementation AutofillMainContainer
45 @synthesize target = target_;
47 - (id)initWithDelegate:(autofill::AutofillDialogViewDelegate*)delegate {
48 if (self = [super init]) {
55 [self buildWindowButtons];
57 base::scoped_nsobject<NSView> view([[NSView alloc] initWithFrame:NSZeroRect]);
58 [view setAutoresizesSubviews:YES];
59 [view setSubviews:@[buttonContainer_]];
62 // Set up "Save in Chrome" checkbox.
63 saveInChromeCheckbox_.reset([[NSButton alloc] initWithFrame:NSZeroRect]);
64 [saveInChromeCheckbox_ setButtonType:NSSwitchButton];
65 [saveInChromeCheckbox_ setTitle:
66 base::SysUTF16ToNSString(delegate_->SaveLocallyText())];
67 [saveInChromeCheckbox_ sizeToFit];
68 [[self view] addSubview:saveInChromeCheckbox_];
70 saveInChromeTooltip_.reset(
71 [[AutofillTooltipController alloc]
72 initWithArrowLocation:info_bubble::kTopCenter]);
73 [saveInChromeTooltip_ setImage:
74 ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed(
75 IDR_AUTOFILL_TOOLTIP_ICON).ToNSImage()];
76 [saveInChromeTooltip_ setMessage:
77 base::SysUTF16ToNSString(delegate_->SaveLocallyTooltip())];
78 [[self view] addSubview:[saveInChromeTooltip_ view]];
79 [self updateSaveInChrome];
81 detailsContainer_.reset(
82 [[AutofillDetailsContainer alloc] initWithDelegate:delegate_]);
83 NSSize frameSize = [[detailsContainer_ view] frame].size;
84 [[detailsContainer_ view] setFrameOrigin:
85 NSMakePoint(0, NSHeight([buttonContainer_ frame]))];
86 frameSize.height += NSHeight([buttonContainer_ frame]);
87 [[self view] setFrameSize:frameSize];
88 [[self view] addSubview:[detailsContainer_ view]];
90 notificationContainer_.reset(
91 [[AutofillNotificationContainer alloc] initWithDelegate:delegate_]);
92 [[self view] addSubview:[notificationContainer_ view]];
95 - (NSSize)decorationSizeForWidth:(CGFloat)width {
96 NSSize buttonSize = [buttonContainer_ frame].size;
97 NSSize buttonStripSize =
98 NSMakeSize(buttonSize.width + chrome_style::kHorizontalPadding,
99 buttonSize.height + kButtonVerticalPadding +
100 chrome_style::kClientBottomPadding);
102 NSSize size = NSMakeSize(std::max(buttonStripSize.width, width),
103 buttonStripSize.height);
104 NSSize notificationSize =
105 [notificationContainer_ preferredSizeForWidth:width];
106 size.height += notificationSize.height;
111 - (NSSize)preferredSize {
112 NSSize detailsSize = [detailsContainer_ preferredSize];
113 NSSize decorationSize = [self decorationSizeForWidth:detailsSize.width];
115 NSSize size = NSMakeSize(std::max(decorationSize.width, detailsSize.width),
116 decorationSize.height + detailsSize.height);
117 size.height += autofill::kDetailVerticalPadding;
122 - (void)performLayout {
123 NSRect bounds = [[self view] bounds];
125 CGFloat currentY = 0.0;
126 NSRect buttonFrame = [buttonContainer_ frame];
127 buttonFrame.origin.y = currentY + chrome_style::kClientBottomPadding;
128 [buttonContainer_ setFrameOrigin:buttonFrame.origin];
129 currentY = NSMaxY(buttonFrame) + kButtonVerticalPadding;
131 NSRect checkboxFrame = [saveInChromeCheckbox_ frame];
132 [saveInChromeCheckbox_ setFrameOrigin:
133 NSMakePoint(chrome_style::kHorizontalPadding,
134 NSMidY(buttonFrame) - NSHeight(checkboxFrame) / 2.0)];
136 NSRect tooltipFrame = [[saveInChromeTooltip_ view] frame];
137 [[saveInChromeTooltip_ view] setFrameOrigin:
138 NSMakePoint(NSMaxX([saveInChromeCheckbox_ frame]) + autofill::kButtonGap,
139 NSMidY(buttonFrame) - (NSHeight(tooltipFrame) / 2.0))];
141 NSRect notificationFrame = NSZeroRect;
142 notificationFrame.size = [notificationContainer_ preferredSizeForWidth:
145 // Buttons/checkbox/legal take up lower part of view, notifications the
146 // upper part. Adjust the detailsContainer to take up the remainder.
147 CGFloat remainingHeight =
148 NSHeight(bounds) - currentY - NSHeight(notificationFrame);
149 NSRect containerFrame =
150 NSMakeRect(0, currentY, NSWidth(bounds), remainingHeight);
151 [[detailsContainer_ view] setFrame:containerFrame];
152 [detailsContainer_ performLayout];
154 notificationFrame.origin = NSMakePoint(0, NSMaxY(containerFrame));
155 [[notificationContainer_ view] setFrame:notificationFrame];
156 [notificationContainer_ performLayout];
159 - (void)buildWindowButtons {
160 if (buttonContainer_.get())
163 buttonContainer_.reset([[GTMWidthBasedTweaker alloc] initWithFrame:
164 ui::kWindowSizeDeterminedLater]);
166 setAutoresizingMask:(NSViewMinXMargin | NSViewMaxYMargin)];
168 base::scoped_nsobject<NSButton> button(
169 [[ConstrainedWindowButton alloc] initWithFrame:NSZeroRect]);
170 [button setKeyEquivalent:kKeyEquivalentEscape];
171 [button setTarget:target_];
172 [button setAction:@selector(cancel:)];
174 [buttonContainer_ addSubview:button];
176 CGFloat nextX = NSMaxX([button frame]) + autofill::kButtonGap;
177 button.reset([[BlueLabelButton alloc] initWithFrame:NSZeroRect]);
178 [button setFrameOrigin:NSMakePoint(nextX, 0)];
179 [button setKeyEquivalent:kKeyEquivalentReturn];
180 [button setTarget:target_];
181 [button setAction:@selector(accept:)];
182 [buttonContainer_ addSubview:button];
183 [self updateButtons];
185 NSRect frame = NSMakeRect(
186 -NSMaxX([button frame]) - chrome_style::kHorizontalPadding, 0,
187 NSMaxX([button frame]), NSHeight([button frame]));
188 [buttonContainer_ setFrame:frame];
191 - (void)layoutButtons {
192 base::scoped_nsobject<GTMUILocalizerAndLayoutTweaker> layoutTweaker(
193 [[GTMUILocalizerAndLayoutTweaker alloc] init]);
194 [layoutTweaker tweakUI:buttonContainer_];
196 // Now ensure both buttons have the same height. The second button is
197 // known to be the larger one.
198 CGFloat buttonHeight =
199 NSHeight([[[buttonContainer_ subviews] objectAtIndex:1] frame]);
201 // Force first button to be the same height.
202 NSView* button = [[buttonContainer_ subviews] objectAtIndex:0];
203 NSSize buttonSize = [button frame].size;
204 buttonSize.height = buttonHeight;
205 [button setFrameSize:buttonSize];
208 - (void)updateButtons {
209 NSButton* button = base::mac::ObjCCastStrict<NSButton>(
210 [[buttonContainer_ subviews] objectAtIndex:0]);
211 [button setTitle:base::SysUTF16ToNSString(delegate_->CancelButtonText())];
212 button = base::mac::ObjCCastStrict<NSButton>(
213 [[buttonContainer_ subviews] objectAtIndex:1]);
214 [button setTitle:base::SysUTF16ToNSString(delegate_->ConfirmButtonText())];
215 [self layoutButtons];
218 - (AutofillSectionContainer*)sectionForId:(autofill::DialogSection)section {
219 return [detailsContainer_ sectionForId:section];
222 - (void)modelChanged {
223 [self updateSaveInChrome];
224 [self updateButtons];
225 [detailsContainer_ modelChanged];
228 - (BOOL)saveDetailsLocally {
229 return [saveInChromeCheckbox_ state] == NSOnState;
232 - (void)updateNotificationArea {
233 [notificationContainer_ setNotifications:delegate_->CurrentNotifications()];
234 id delegate = [[[self view] window] windowController];
235 if ([delegate respondsToSelector:@selector(requestRelayout)])
236 [delegate performSelector:@selector(requestRelayout)];
240 return [detailsContainer_ validate];
243 - (void)updateSaveInChrome {
244 [saveInChromeCheckbox_ setHidden:!delegate_->ShouldOfferToSaveInChrome()];
245 [[saveInChromeTooltip_ view] setHidden:[saveInChromeCheckbox_ isHidden]];
246 [saveInChromeCheckbox_ setState:
247 (delegate_->ShouldSaveInChrome() ? NSOnState : NSOffState)];
250 - (void)makeFirstInvalidInputFirstResponder {
251 NSView* field = [detailsContainer_ firstInvalidField];
255 [detailsContainer_ scrollToView:field];
256 [[[self view] window] makeFirstResponder:field];
259 - (void)scrollInitialEditorIntoViewAndMakeFirstResponder {
260 // Try to focus on the first invalid field. If there isn't one, focus on the
261 // first editable field instead.
262 NSView* field = [detailsContainer_ firstInvalidField];
264 field = [detailsContainer_ firstVisibleField];
268 [detailsContainer_ scrollToView:field];
269 [[[self view] window] makeFirstResponder:field];
272 - (void)updateErrorBubble {
273 [detailsContainer_ updateErrorBubble];
279 @implementation AutofillMainContainer (Testing)
281 - (NSButton*)saveInChromeCheckboxForTesting {
282 return saveInChromeCheckbox_.get();
285 - (NSButton*)saveInChromeTooltipForTesting {
286 return base::mac::ObjCCast<NSButton>([saveInChromeTooltip_ view]);