blob: 2dce2d0c99cc8e2bc61a3cde87a0c56ee27e6585 [file] [log] [blame]
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "chrome/browser/ui/cocoa/autofill/autofill_main_container.h"
#include <algorithm>
#include <cmath>
#include "base/mac/foundation_util.h"
#include "base/strings/sys_string_conversions.h"
#include "chrome/browser/ui/autofill/autofill_dialog_view_delegate.h"
#include "chrome/browser/ui/cocoa/autofill/autofill_dialog_constants.h"
#import "chrome/browser/ui/cocoa/constrained_window/constrained_window_button.h"
#import "chrome/browser/ui/cocoa/autofill/autofill_details_container.h"
#import "chrome/browser/ui/cocoa/autofill/autofill_notification_container.h"
#import "chrome/browser/ui/cocoa/hyperlink_text_view.h"
#import "chrome/browser/ui/cocoa/key_equivalent_constants.h"
#include "grit/generated_resources.h"
#import "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h"
#include "ui/base/cocoa/window_size_constants.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/range/range.h"
@interface AutofillMainContainer (Private)
- (void)buildWindowButtonsForFrame:(NSRect)frame;
- (void)layoutButtons;
- (NSSize)preferredLegalDocumentSizeForWidth:(CGFloat)width;
@end
@implementation AutofillMainContainer
@synthesize target = target_;
- (id)initWithDelegate:(autofill::AutofillDialogViewDelegate*)delegate {
if (self = [super init]) {
delegate_ = delegate;
}
return self;
}
- (void)loadView {
[self buildWindowButtonsForFrame:NSZeroRect];
base::scoped_nsobject<NSView> view([[NSView alloc] initWithFrame:NSZeroRect]);
[view setAutoresizesSubviews:YES];
[view setSubviews:@[buttonContainer_]];
[self setView:view];
[self layoutButtons];
// Set up "Save in Chrome" checkbox.
saveInChromeCheckbox_.reset([[NSButton alloc] initWithFrame:NSZeroRect]);
[saveInChromeCheckbox_ setButtonType:NSSwitchButton];
[saveInChromeCheckbox_ setTitle:
base::SysUTF16ToNSString(delegate_->SaveLocallyText())];
[saveInChromeCheckbox_ setState:NSOnState];
[saveInChromeCheckbox_ sizeToFit];
[[self view] addSubview:saveInChromeCheckbox_];
detailsContainer_.reset(
[[AutofillDetailsContainer alloc] initWithDelegate:delegate_]);
NSSize frameSize = [[detailsContainer_ view] frame].size;
[[detailsContainer_ view] setFrameOrigin:
NSMakePoint(0, NSHeight([buttonContainer_ frame]))];
frameSize.height += NSHeight([buttonContainer_ frame]);
[[self view] setFrameSize:frameSize];
[[self view] addSubview:[detailsContainer_ view]];
legalDocumentsView_.reset(
[[HyperlinkTextView alloc] initWithFrame:NSZeroRect]);
[legalDocumentsView_ setEditable:NO];
[legalDocumentsView_ setBackgroundColor:
[NSColor colorWithCalibratedRed:0.96
green:0.96
blue:0.96
alpha:1.0]];
[legalDocumentsView_ setDrawsBackground:YES];
[legalDocumentsView_ setHidden:YES];
[legalDocumentsView_ setDelegate:self];
legalDocumentsSizeDirty_ = YES;
[[self view] addSubview:legalDocumentsView_];
notificationContainer_.reset(
[[AutofillNotificationContainer alloc] initWithDelegate:delegate_]);
[[self view] addSubview:[notificationContainer_ view]];
}
// Called when embedded links are clicked.
- (BOOL)textView:(NSTextView*)textView
clickedOnLink:(id)link
atIndex:(NSUInteger)charIndex {
int index = [base::mac::ObjCCastStrict<NSNumber>(link) intValue];
delegate_->LegalDocumentLinkClicked(
delegate_->LegalDocumentLinks()[index]);
return YES;
}
- (NSSize)preferredSize {
// Overall width is determined by |detailsContainer_|.
NSSize buttonSize = [buttonContainer_ frame].size;
NSSize detailsSize = [detailsContainer_ preferredSize];
NSSize size = NSMakeSize(std::max(buttonSize.width, detailsSize.width),
buttonSize.height + detailsSize.height);
size.height += autofill::kDetailBottomPadding;
if (![legalDocumentsView_ isHidden]) {
NSSize legalDocumentSize =
[self preferredLegalDocumentSizeForWidth:detailsSize.width];
size.height += legalDocumentSize.height + autofill::kVerticalSpacing;
}
NSSize notificationSize =
[notificationContainer_ preferredSizeForWidth:detailsSize.width];
size.height += notificationSize.height;
return size;
}
- (void)performLayout {
NSRect bounds = [[self view] bounds];
CGFloat currentY = 0.0;
if (![legalDocumentsView_ isHidden]) {
[legalDocumentsView_ setFrameSize:
[self preferredLegalDocumentSizeForWidth:NSWidth(bounds)]];
currentY = NSMaxY([legalDocumentsView_ frame]) +
autofill::kVerticalSpacing;
}
NSRect buttonFrame = [buttonContainer_ frame];
buttonFrame.origin.y = currentY;
[buttonContainer_ setFrameOrigin:buttonFrame.origin];
NSRect checkboxFrame = [saveInChromeCheckbox_ frame];
checkboxFrame.origin.y = NSMidY(buttonFrame) - NSHeight(checkboxFrame) / 2.0;
[saveInChromeCheckbox_ setFrameOrigin:checkboxFrame.origin];
currentY = NSMaxY(buttonFrame) + autofill::kDetailBottomPadding;
NSRect notificationFrame = NSZeroRect;
notificationFrame.size = [notificationContainer_ preferredSizeForWidth:
NSWidth(bounds)];
// Buttons/checkbox/legal take up lower part of view, notifications the
// upper part. Adjust the detailsContainer to take up the remainder.
CGFloat remainingHeight =
NSHeight(bounds) - currentY - NSHeight(notificationFrame);
NSRect containerFrame =
NSMakeRect(0, currentY, NSWidth(bounds), remainingHeight);
[[detailsContainer_ view] setFrame:containerFrame];
[detailsContainer_ performLayout];
notificationFrame.origin =
NSMakePoint(0, NSMaxY(containerFrame) + autofill::kDetailTopPadding);
[[notificationContainer_ view] setFrame:notificationFrame];
[notificationContainer_ performLayout];
}
- (void)buildWindowButtonsForFrame:(NSRect)frame {
if (buttonContainer_.get())
return;
buttonContainer_.reset([[GTMWidthBasedTweaker alloc] initWithFrame:
ui::kWindowSizeDeterminedLater]);
[buttonContainer_
setAutoresizingMask:(NSViewMinXMargin | NSViewMaxYMargin)];
base::scoped_nsobject<NSButton> button(
[[ConstrainedWindowButton alloc] initWithFrame:NSZeroRect]);
[button setTitle:l10n_util::GetNSStringWithFixup(IDS_CANCEL)];
[button setKeyEquivalent:kKeyEquivalentEscape];
[button setTarget:target_];
[button setAction:@selector(cancel:)];
[button sizeToFit];
[buttonContainer_ addSubview:button];
CGFloat nextX = NSMaxX([button frame]) + autofill::kButtonGap;
button.reset([[ConstrainedWindowButton alloc] initWithFrame:NSZeroRect]);
[button setFrameOrigin:NSMakePoint(nextX, 0)];
[button setTitle:l10n_util::GetNSStringWithFixup(
IDS_AUTOFILL_DIALOG_SUBMIT_BUTTON)];
[button setKeyEquivalent:kKeyEquivalentReturn];
[button setTarget:target_];
[button setAction:@selector(accept:)];
[button sizeToFit];
[buttonContainer_ addSubview:button];
frame = NSMakeRect(
NSWidth(frame) - NSMaxX([button frame]), 0,
NSMaxX([button frame]), NSHeight([button frame]));
[buttonContainer_ setFrame:frame];
}
- (void)layoutButtons {
base::scoped_nsobject<GTMUILocalizerAndLayoutTweaker> layoutTweaker(
[[GTMUILocalizerAndLayoutTweaker alloc] init]);
[layoutTweaker tweakUI:buttonContainer_];
}
// Compute the preferred size for the legal documents text, given a width.
- (NSSize)preferredLegalDocumentSizeForWidth:(CGFloat)width {
// Only recompute if necessary (On text or frame width change).
if (!legalDocumentsSizeDirty_ && abs(legalDocumentsSize_.width-width) < 1.0)
return legalDocumentsSize_;
// There's no direct API to compute desired sizes - use layouting instead.
// Layout in a rect with fixed width and "infinite" height.
NSRect currentFrame = [legalDocumentsView_ frame];
[legalDocumentsView_ setFrame:NSMakeRect(0, 0, width, CGFLOAT_MAX)];
// Now use the layout manager to compute layout.
NSLayoutManager* layoutManager = [legalDocumentsView_ layoutManager];
NSTextContainer* textContainer = [legalDocumentsView_ textContainer];
[layoutManager ensureLayoutForTextContainer:textContainer];
NSRect newFrame = [layoutManager usedRectForTextContainer:textContainer];
// And finally, restore old frame.
[legalDocumentsView_ setFrame:currentFrame];
newFrame.size.width = width;
legalDocumentsSizeDirty_ = NO;
legalDocumentsSize_ = newFrame.size;
return legalDocumentsSize_;
}
- (AutofillSectionContainer*)sectionForId:(autofill::DialogSection)section {
return [detailsContainer_ sectionForId:section];
}
- (void)modelChanged {
[saveInChromeCheckbox_ setHidden:!delegate_->ShouldOfferToSaveInChrome()];
[detailsContainer_ modelChanged];
}
- (BOOL)saveDetailsLocally {
return [saveInChromeCheckbox_ state] == NSOnState;
}
- (void)updateLegalDocuments {
NSString* text = base::SysUTF16ToNSString(delegate_->LegalDocumentsText());
if ([text length]) {
NSFont* font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
[legalDocumentsView_ setMessage:text
withFont:font
messageColor:[NSColor blackColor]];
const std::vector<gfx::Range>& link_ranges =
delegate_->LegalDocumentLinks();
for (size_t i = 0; i < link_ranges.size(); ++i) {
NSRange range = link_ranges[i].ToNSRange();
[legalDocumentsView_ addLinkRange:range
withName:@(i)
linkColor:[NSColor blueColor]];
}
legalDocumentsSizeDirty_ = YES;
}
[legalDocumentsView_ setHidden:[text length] == 0];
// Always request re-layout on state change.
id delegate = [[[self view] window] windowController];
if ([delegate respondsToSelector:@selector(requestRelayout)])
[delegate performSelector:@selector(requestRelayout)];
}
- (void)updateNotificationArea {
[notificationContainer_ setNotifications:delegate_->CurrentNotifications()];
id delegate = [[[self view] window] windowController];
if ([delegate respondsToSelector:@selector(requestRelayout)])
[delegate performSelector:@selector(requestRelayout)];
}
- (void)setAnchorView:(NSView*)anchorView {
[notificationContainer_ setAnchorView:anchorView];
}
- (BOOL)validate {
return [detailsContainer_ validate];
}
@end
@implementation AutofillMainContainer (Testing)
- (NSButton*)saveInChromeCheckboxForTesting {
return saveInChromeCheckbox_.get();
}
@end