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 #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/hyperlink_text_view.h"
19 #import "chrome/browser/ui/cocoa/key_equivalent_constants.h"
20 #include "grit/generated_resources.h"
21 #include "grit/theme_resources.h"
22 #import "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h"
23 #include "ui/base/cocoa/window_size_constants.h"
24 #include "ui/base/l10n/l10n_util.h"
25 #include "ui/gfx/range/range.h"
27 @interface AutofillMainContainer (Private)
28 - (void)buildWindowButtons;
29 - (void)layoutButtons;
30 - (NSSize)preferredLegalDocumentSizeForWidth:(CGFloat)width;
34 @implementation AutofillMainContainer
36 @synthesize target = target_;
38 - (id)initWithDelegate:(autofill::AutofillDialogViewDelegate*)delegate {
39 if (self = [super init]) {
46 [self buildWindowButtons];
48 base::scoped_nsobject<NSView> view([[NSView alloc] initWithFrame:NSZeroRect]);
49 [view setAutoresizesSubviews:YES];
50 [view setSubviews:@[buttonContainer_]];
55 // Set up Wallet icon.
56 buttonStripImage_.reset([[NSImageView alloc] initWithFrame:NSZeroRect]);
57 [self updateWalletIcon];
58 [[self view] addSubview:buttonStripImage_];
60 // Set up "Save in Chrome" checkbox.
61 saveInChromeCheckbox_.reset([[NSButton alloc] initWithFrame:NSZeroRect]);
62 [saveInChromeCheckbox_ setButtonType:NSSwitchButton];
63 [saveInChromeCheckbox_ setTitle:
64 base::SysUTF16ToNSString(delegate_->SaveLocallyText())];
65 [self updateSaveInChrome];
66 [saveInChromeCheckbox_ sizeToFit];
67 [[self view] addSubview:saveInChromeCheckbox_];
69 saveInChromeTooltip_.reset([[NSImageView alloc] initWithFrame:NSZeroRect]);
70 [saveInChromeTooltip_ setImage:
71 ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed(
72 IDR_AUTOFILL_TOOLTIP_ICON).ToNSImage()];
73 [saveInChromeTooltip_ setToolTip:
74 base::SysUTF16ToNSString(delegate_->SaveLocallyTooltip())];
75 [saveInChromeTooltip_ setFrameSize:[[saveInChromeTooltip_ image] size]];
76 [[self view] addSubview:saveInChromeTooltip_];
78 detailsContainer_.reset(
79 [[AutofillDetailsContainer alloc] initWithDelegate:delegate_]);
80 NSSize frameSize = [[detailsContainer_ view] frame].size;
81 [[detailsContainer_ view] setFrameOrigin:
82 NSMakePoint(0, NSHeight([buttonContainer_ frame]))];
83 frameSize.height += NSHeight([buttonContainer_ frame]);
84 [[self view] setFrameSize:frameSize];
85 [[self view] addSubview:[detailsContainer_ view]];
87 legalDocumentsView_.reset(
88 [[HyperlinkTextView alloc] initWithFrame:NSZeroRect]);
89 [legalDocumentsView_ setEditable:NO];
90 [legalDocumentsView_ setBackgroundColor:
91 [NSColor colorWithCalibratedRed:0.96
95 [legalDocumentsView_ setDrawsBackground:YES];
96 [legalDocumentsView_ setHidden:YES];
97 [legalDocumentsView_ setDelegate:self];
98 legalDocumentsSizeDirty_ = YES;
99 [[self view] addSubview:legalDocumentsView_];
101 notificationContainer_.reset(
102 [[AutofillNotificationContainer alloc] initWithDelegate:delegate_]);
103 [[self view] addSubview:[notificationContainer_ view]];
106 // Called when embedded links are clicked.
107 - (BOOL)textView:(NSTextView*)textView
108 clickedOnLink:(id)link
109 atIndex:(NSUInteger)charIndex {
110 int index = [base::mac::ObjCCastStrict<NSNumber>(link) intValue];
111 delegate_->LegalDocumentLinkClicked(
112 delegate_->LegalDocumentLinks()[index]);
116 - (NSSize)preferredSize {
117 // Overall width is determined by |detailsContainer_|.
118 NSSize buttonSize = [buttonContainer_ frame].size;
119 NSSize buttonStripImageSize = [buttonStripImage_ frame].size;
120 NSSize buttonStripSize =
121 NSMakeSize(buttonSize.width + chrome_style::kHorizontalPadding +
122 buttonStripImageSize.width,
123 std::max(buttonSize.height, buttonStripImageSize.height));
125 NSSize detailsSize = [detailsContainer_ preferredSize];
127 NSSize size = NSMakeSize(std::max(buttonStripSize.width, detailsSize.width),
128 buttonStripSize.height + detailsSize.height);
129 size.height += 2 * autofill::kDetailVerticalPadding;
131 if (![legalDocumentsView_ isHidden]) {
132 NSSize legalDocumentSize =
133 [self preferredLegalDocumentSizeForWidth:detailsSize.width];
134 size.height += legalDocumentSize.height + autofill::kVerticalSpacing;
137 NSSize notificationSize =
138 [notificationContainer_ preferredSizeForWidth:detailsSize.width];
139 size.height += notificationSize.height;
143 - (void)performLayout {
144 NSRect bounds = [[self view] bounds];
146 CGFloat currentY = 0.0;
147 if (![legalDocumentsView_ isHidden]) {
148 [legalDocumentsView_ setFrameSize:
149 [self preferredLegalDocumentSizeForWidth:NSWidth(bounds)]];
150 currentY = NSMaxY([legalDocumentsView_ frame]) + autofill::kVerticalSpacing;
153 NSRect buttonFrame = [buttonContainer_ frame];
154 buttonFrame.origin.y = currentY;
155 [buttonContainer_ setFrameOrigin:buttonFrame.origin];
156 currentY = NSMaxY(buttonFrame) + autofill::kDetailVerticalPadding;
158 NSPoint walletIconOrigin =
159 NSMakePoint(chrome_style::kHorizontalPadding, buttonFrame.origin.y);
160 [buttonStripImage_ setFrameOrigin:walletIconOrigin];
161 currentY = std::max(currentY, NSMaxY([buttonStripImage_ frame]));
163 NSRect checkboxFrame = [saveInChromeCheckbox_ frame];
164 [saveInChromeCheckbox_ setFrameOrigin:
165 NSMakePoint(chrome_style::kHorizontalPadding,
166 NSMidY(buttonFrame) - NSHeight(checkboxFrame) / 2.0)];
168 NSRect tooltipFrame = [saveInChromeTooltip_ frame];
169 [saveInChromeTooltip_ setFrameOrigin:
170 NSMakePoint(NSMaxX([saveInChromeCheckbox_ frame]) + autofill::kButtonGap,
171 NSMidY(buttonFrame) - (NSHeight(tooltipFrame) / 2.0))];
173 NSRect notificationFrame = NSZeroRect;
174 notificationFrame.size = [notificationContainer_ preferredSizeForWidth:
177 // Buttons/checkbox/legal take up lower part of view, notifications the
178 // upper part. Adjust the detailsContainer to take up the remainder.
179 CGFloat remainingHeight =
180 NSHeight(bounds) - currentY - NSHeight(notificationFrame) -
181 autofill::kDetailVerticalPadding;
182 NSRect containerFrame =
183 NSMakeRect(0, currentY, NSWidth(bounds), remainingHeight);
184 [[detailsContainer_ view] setFrame:containerFrame];
185 [detailsContainer_ performLayout];
187 notificationFrame.origin =
188 NSMakePoint(0, NSMaxY(containerFrame) + autofill::kDetailVerticalPadding);
189 [[notificationContainer_ view] setFrame:notificationFrame];
190 [notificationContainer_ performLayout];
193 - (void)buildWindowButtons {
194 if (buttonContainer_.get())
197 buttonContainer_.reset([[GTMWidthBasedTweaker alloc] initWithFrame:
198 ui::kWindowSizeDeterminedLater]);
200 setAutoresizingMask:(NSViewMinXMargin | NSViewMaxYMargin)];
202 base::scoped_nsobject<NSButton> button(
203 [[ConstrainedWindowButton alloc] initWithFrame:NSZeroRect]);
204 [button setTitle:l10n_util::GetNSStringWithFixup(IDS_CANCEL)];
205 [button setKeyEquivalent:kKeyEquivalentEscape];
206 [button setTarget:target_];
207 [button setAction:@selector(cancel:)];
209 [buttonContainer_ addSubview:button];
211 CGFloat nextX = NSMaxX([button frame]) + autofill::kButtonGap;
212 button.reset([[ConstrainedWindowButton alloc] initWithFrame:NSZeroRect]);
213 [button setFrameOrigin:NSMakePoint(nextX, 0)];
214 [button setTitle:l10n_util::GetNSStringWithFixup(
215 IDS_AUTOFILL_DIALOG_SUBMIT_BUTTON)];
216 [button setKeyEquivalent:kKeyEquivalentReturn];
217 [button setTarget:target_];
218 [button setAction:@selector(accept:)];
220 [buttonContainer_ addSubview:button];
222 NSRect frame = NSMakeRect(
223 -NSMaxX([button frame]) - chrome_style::kHorizontalPadding, 0,
224 NSMaxX([button frame]), NSHeight([button frame]));
225 [buttonContainer_ setFrame:frame];
228 - (void)layoutButtons {
229 base::scoped_nsobject<GTMUILocalizerAndLayoutTweaker> layoutTweaker(
230 [[GTMUILocalizerAndLayoutTweaker alloc] init]);
231 [layoutTweaker tweakUI:buttonContainer_];
234 // Compute the preferred size for the legal documents text, given a width.
235 - (NSSize)preferredLegalDocumentSizeForWidth:(CGFloat)width {
236 // Only recompute if necessary (On text or frame width change).
237 if (!legalDocumentsSizeDirty_ && abs(legalDocumentsSize_.width-width) < 1.0)
238 return legalDocumentsSize_;
240 // There's no direct API to compute desired sizes - use layouting instead.
241 // Layout in a rect with fixed width and "infinite" height.
242 NSRect currentFrame = [legalDocumentsView_ frame];
243 [legalDocumentsView_ setFrame:NSMakeRect(0, 0, width, CGFLOAT_MAX)];
245 // Now use the layout manager to compute layout.
246 NSLayoutManager* layoutManager = [legalDocumentsView_ layoutManager];
247 NSTextContainer* textContainer = [legalDocumentsView_ textContainer];
248 [layoutManager ensureLayoutForTextContainer:textContainer];
249 NSRect newFrame = [layoutManager usedRectForTextContainer:textContainer];
251 // And finally, restore old frame.
252 [legalDocumentsView_ setFrame:currentFrame];
253 newFrame.size.width = width;
255 legalDocumentsSizeDirty_ = NO;
256 legalDocumentsSize_ = newFrame.size;
257 return legalDocumentsSize_;
260 - (AutofillSectionContainer*)sectionForId:(autofill::DialogSection)section {
261 return [detailsContainer_ sectionForId:section];
264 - (void)modelChanged {
265 [self updateSaveInChrome];
266 [self updateWalletIcon];
267 [detailsContainer_ modelChanged];
270 - (BOOL)saveDetailsLocally {
271 return [saveInChromeCheckbox_ state] == NSOnState;
274 - (void)updateLegalDocuments {
275 NSString* text = base::SysUTF16ToNSString(delegate_->LegalDocumentsText());
278 NSFont* font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
279 [legalDocumentsView_ setMessage:text
281 messageColor:[NSColor blackColor]];
283 const std::vector<gfx::Range>& link_ranges =
284 delegate_->LegalDocumentLinks();
285 for (size_t i = 0; i < link_ranges.size(); ++i) {
286 NSRange range = link_ranges[i].ToNSRange();
287 [legalDocumentsView_ addLinkRange:range
289 linkColor:[NSColor blueColor]];
291 legalDocumentsSizeDirty_ = YES;
293 [legalDocumentsView_ setHidden:[text length] == 0];
295 // Always request re-layout on state change.
296 id delegate = [[[self view] window] windowController];
297 if ([delegate respondsToSelector:@selector(requestRelayout)])
298 [delegate performSelector:@selector(requestRelayout)];
301 - (void)updateNotificationArea {
302 [notificationContainer_ setNotifications:delegate_->CurrentNotifications()];
303 id delegate = [[[self view] window] windowController];
304 if ([delegate respondsToSelector:@selector(requestRelayout)])
305 [delegate performSelector:@selector(requestRelayout)];
308 - (void)setAnchorView:(NSView*)anchorView {
309 [notificationContainer_ setAnchorView:anchorView];
313 return [detailsContainer_ validate];
316 - (void)updateSaveInChrome {
317 [saveInChromeCheckbox_ setHidden:!delegate_->ShouldOfferToSaveInChrome()];
318 [saveInChromeTooltip_ setHidden:[saveInChromeCheckbox_ isHidden]];
319 [saveInChromeCheckbox_ setState:
320 (delegate_->ShouldSaveInChrome() ? NSOnState : NSOffState)];
323 - (void)updateWalletIcon {
324 gfx::Image image = delegate_->ButtonStripImage();
325 [buttonStripImage_ setHidden:image.IsEmpty()];
326 if (![buttonStripImage_ isHidden]) {
327 [buttonStripImage_ setImage:image.ToNSImage()];
328 [buttonStripImage_ setFrameSize:[[buttonStripImage_ image] size]];
332 - (void)updateErrorBubble {
333 [detailsContainer_ updateErrorBubble];
339 @implementation AutofillMainContainer (Testing)
341 - (NSButton*)saveInChromeCheckboxForTesting {
342 return saveInChromeCheckbox_.get();
345 - (NSImageView*)buttonStripImageForTesting {
346 return buttonStripImage_.get();
349 - (NSImageView*)saveInChromeTooltipForTesting {
350 return saveInChromeTooltip_.get();