// Copyright 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. #include "chrome/browser/ui/cocoa/autofill/new_credit_card_bubble_cocoa.h" #include "base/i18n/rtl.h" #include "base/mac/foundation_util.h" #include "base/strings/sys_string_conversions.h" #include "chrome/browser/ui/autofill/new_credit_card_bubble_controller.h" #include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/browser_window.h" #import "chrome/browser/ui/cocoa/base_bubble_controller.h" #import "chrome/browser/ui/cocoa/browser_window_controller.h" #import "chrome/browser/ui/cocoa/info_bubble_view.h" #import "chrome/browser/ui/cocoa/info_bubble_window.h" #import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h" #include "content/public/browser/web_contents.h" #include "skia/ext/skia_utils_mac.h" #include "ui/base/cocoa/cocoa_base_utils.h" #import "ui/base/cocoa/controls/hyperlink_button_cell.h" #include "ui/base/cocoa/window_size_constants.h" #include "ui/native_theme/native_theme.h" #include "ui/native_theme/native_theme_mac.h" namespace { const CGFloat kAppMenuBubblePointOffsetY = 6; const CGFloat kVerticalSpacing = 8; const CGFloat kHorizontalSpacing = 4; const CGFloat kInset = 20.0; const CGFloat kTitleSpacing = 15; const CGFloat kAnchorlessEndPadding = 20; const CGFloat kAnchorlessTopPadding = 10; } // namespace @interface NewCreditCardBubbleControllerCocoa : BaseBubbleController { @private // Controller that drives this bubble. Never NULL; outlives this class. autofill::NewCreditCardBubbleController* controller_; // Objective-C/C++ bridge. Owned by this class. scoped_ptr bridge_; } // Designated initializer. - (id)initWithParentWindow:(NSWindow*)parentWindow controller: (autofill::NewCreditCardBubbleController*)controller bridge:(autofill::NewCreditCardBubbleCocoa*)bridge; // Displays the bubble anchored at the specified |anchorPoint|. - (void)showAtAnchor:(NSPoint)anchorPoint; // Helper function to create an uneditable text field. - (base::scoped_nsobject)makeTextField; // Helper function to set the appropriately styled details string. - (void)setDetailsStringForField:(NSTextField*)details; // Create and lay out all control elements inside the bubble. - (void)performLayout; // Delegate interface for clicks on "Learn more". - (void)onLinkClicked:(id)sender; @end @implementation NewCreditCardBubbleControllerCocoa - (id)initWithParentWindow:(NSWindow*)parentWindow controller: (autofill::NewCreditCardBubbleController*)controller bridge:(autofill::NewCreditCardBubbleCocoa*)bridge { base::scoped_nsobject window( [[InfoBubbleWindow alloc] initWithContentRect:ui::kWindowSizeDeterminedLater styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]); bridge_.reset(bridge); if ((self = [super initWithWindow:window parentWindow:parentWindow anchoredAt:NSZeroPoint])) { controller_ = controller; [window setInfoBubbleCanBecomeKeyWindow:NO]; ui::NativeTheme* nativeTheme = ui::NativeThemeMac::instance(); [[self bubble] setBackgroundColor: skia::SkColorToCalibratedNSColor(nativeTheme->GetSystemColor( ui::NativeTheme::kColorId_DialogBackground))]; [self performLayout]; } return self; } - (void)showAtAnchor:(NSPoint)anchorPoint { [self setAnchorPoint:anchorPoint]; [[self bubble] setNeedsDisplay:YES]; [self showWindow:nil]; } - (base::scoped_nsobject)makeTextField { base::scoped_nsobject textField( [[NSTextField alloc] initWithFrame:NSZeroRect]); [textField setEditable:NO]; [textField setSelectable:NO]; [textField setDrawsBackground:NO]; [textField setBezeled:NO]; return textField; } - (void)setDetailsStringForField:(NSTextField*)details { // Set linespacing for |details| to match line spacing used in the dialog. base::scoped_nsobject paragraphStyle( [[NSMutableParagraphStyle alloc] init]); [paragraphStyle setLineSpacing:0.5 * [[details font] pointSize]]; NSString* description = base::SysUTF16ToNSString(controller_->CardDescription().description); base::scoped_nsobject detailsString( [[NSAttributedString alloc] initWithString:description attributes:@{ NSParagraphStyleAttributeName : paragraphStyle }]); [details setAttributedStringValue:detailsString]; } - (void)performLayout { base::scoped_nsobject title([self makeTextField]); base::scoped_nsobject icon([[NSImageView alloc] initWithFrame:NSZeroRect]); base::scoped_nsobject name([self makeTextField]); base::scoped_nsobject details([self makeTextField]); base::scoped_nsobject link([[HyperlinkButtonCell buttonWithString: base::SysUTF16ToNSString(controller_->LinkText())] retain]); [link setTarget:self]; [link setAction:@selector(onLinkClicked:)]; // Use uniform font across all controls (except title). [details setFont:[link font]]; [name setFont:[link font]]; [title setFont:[NSFont systemFontOfSize:15.0]]; // Populate fields. const autofill::CreditCardDescription& card_desc = controller_->CardDescription(); [title setStringValue:base::SysUTF16ToNSString(controller_->TitleText())]; [icon setImage:card_desc.icon.AsNSImage()]; [name setStringValue:base::SysUTF16ToNSString(card_desc.name)]; [self setDetailsStringForField:details]; // Size fields. CGFloat bubbleWidth = autofill::NewCreditCardBubbleView::kContentsWidth; [title sizeToFit]; bubbleWidth = std::max(bubbleWidth, NSWidth([title frame]) + 2 * kInset); [icon setFrameSize:[[icon image] size]]; [name sizeToFit]; [details setFrameSize:[[details cell] cellSizeForBounds: NSMakeRect(0, 0, bubbleWidth - 2 * kInset, CGFLOAT_MAX)]]; [link sizeToFit]; // Layout fields. [link setFrameOrigin:NSMakePoint(kInset, kInset)]; [details setFrameOrigin:NSMakePoint( kInset, NSMaxY([link frame]) + kVerticalSpacing)]; [icon setFrameOrigin:NSMakePoint( kInset, NSMaxY([details frame]) + kVerticalSpacing)]; [name setFrameOrigin:NSMakePoint( NSMaxX([icon frame]) + kHorizontalSpacing, NSMidY([icon frame]) - (NSHeight([name frame]) / 2.0))]; [title setFrameOrigin: NSMakePoint(kInset, NSMaxY([icon frame]) + kTitleSpacing)]; [[[self window] contentView] setSubviews: @[ title, icon, name, details, link ]]; // Update window frame. NSRect windowFrame = [[self window] frame]; windowFrame.size.height = NSMaxY([title frame]) + kInset; windowFrame.size.width = bubbleWidth; [[self window] setFrame:windowFrame display:NO]; } - (void)onLinkClicked:(id)sender { controller_->OnLinkClicked(); } @end namespace autofill { NewCreditCardBubbleCocoa::~NewCreditCardBubbleCocoa() { controller_->OnBubbleDestroyed(); } void NewCreditCardBubbleCocoa::Show() { NSView* browser_view = controller_->web_contents()->GetNativeView(); NSWindow* parent_window = [browser_view window]; BrowserWindowController* bwc = [BrowserWindowController browserWindowControllerForWindow:parent_window]; if (!bubbleController_) CreateCocoaController(parent_window); NSPoint anchor_point; NSView* anchor_view; if ([bwc isTabbedWindow]) { anchor_view = [[bwc toolbarController] appMenuButton]; anchor_point = NSMakePoint( NSMidX([anchor_view bounds]), NSMinY([anchor_view bounds]) + kAppMenuBubblePointOffsetY); [[bubbleController_ bubble] setArrowLocation:info_bubble::kTopRight]; [[bubbleController_ bubble] setAlignment:info_bubble::kAlignArrowToAnchor]; } else { anchor_view = browser_view; anchor_point = NSMakePoint( NSMaxX([browser_view bounds]) - kAnchorlessEndPadding, NSMaxY([browser_view bounds]) - kAnchorlessTopPadding); [[bubbleController_ bubble] setArrowLocation:info_bubble::kNoArrow]; if (base::i18n::IsRTL()) { anchor_point.x = NSMaxX([anchor_view bounds]) - anchor_point.x; [[bubbleController_ bubble] setAlignment: info_bubble::kAlignLeftEdgeToAnchorEdge]; } else { [[bubbleController_ bubble] setAlignment: info_bubble::kAlignRightEdgeToAnchorEdge]; } } if ([anchor_view isFlipped]) anchor_point.y = NSMaxY([anchor_view bounds]) - anchor_point.y; anchor_point = [anchor_view convertPoint:anchor_point toView:nil]; anchor_point = ui::ConvertPointFromWindowToScreen(parent_window, anchor_point); [bubbleController_ showAtAnchor:anchor_point]; } void NewCreditCardBubbleCocoa::Hide() { [bubbleController_ close]; } // static base::WeakPtr NewCreditCardBubbleView::Create( NewCreditCardBubbleController* controller) { NewCreditCardBubbleCocoa* bubble = new NewCreditCardBubbleCocoa(controller); return bubble->weak_ptr_factory_.GetWeakPtr(); } NewCreditCardBubbleCocoa::NewCreditCardBubbleCocoa( NewCreditCardBubbleController* controller) : bubbleController_(NULL), controller_(controller), weak_ptr_factory_(this) { } void NewCreditCardBubbleCocoa::CreateCocoaController(NSWindow* parent) { DCHECK(!bubbleController_); bubbleController_ = [[NewCreditCardBubbleControllerCocoa alloc] initWithParentWindow:parent controller:controller_ bridge:this]; } InfoBubbleWindow* NewCreditCardBubbleCocoa::GetInfoBubbleWindow() { return base::mac::ObjCCastStrict( [bubbleController_ window]); } } // namespace autofill