1// Copyright 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#include "chrome/browser/ui/cocoa/autofill/new_credit_card_bubble_cocoa.h" 6 7#include "base/i18n/rtl.h" 8#include "base/mac/foundation_util.h" 9#include "base/strings/sys_string_conversions.h" 10#include "chrome/browser/ui/autofill/new_credit_card_bubble_controller.h" 11#include "chrome/browser/ui/browser_finder.h" 12#include "chrome/browser/ui/browser_window.h" 13#import "chrome/browser/ui/cocoa/browser_window_controller.h" 14#import "chrome/browser/ui/cocoa/base_bubble_controller.h" 15#import "chrome/browser/ui/cocoa/info_bubble_view.h" 16#import "chrome/browser/ui/cocoa/info_bubble_window.h" 17#import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h" 18#include "content/public/browser/web_contents.h" 19#include "skia/ext/skia_utils_mac.h" 20#import "ui/base/cocoa/controls/hyperlink_button_cell.h" 21#include "ui/base/cocoa/window_size_constants.h" 22#include "ui/native_theme/native_theme.h" 23 24namespace { 25 26const CGFloat kWrenchBubblePointOffsetY = 6; 27const CGFloat kVerticalSpacing = 8; 28const CGFloat kHorizontalSpacing = 4; 29const CGFloat kInset = 20.0; 30const CGFloat kTitleSpacing = 15; 31const CGFloat kAnchorlessEndPadding = 20; 32const CGFloat kAnchorlessTopPadding = 10; 33 34} // namespace 35 36@interface NewCreditCardBubbleControllerCocoa : BaseBubbleController { 37 @private 38 // Controller that drives this bubble. Never NULL; outlives this class. 39 autofill::NewCreditCardBubbleController* controller_; 40 41 // Objective-C/C++ bridge. Owned by this class. 42 scoped_ptr<autofill::NewCreditCardBubbleCocoa> bridge_; 43} 44 45// Designated initializer. 46- (id)initWithParentWindow:(NSWindow*)parentWindow 47 controller: 48 (autofill::NewCreditCardBubbleController*)controller 49 bridge:(autofill::NewCreditCardBubbleCocoa*)bridge; 50 51// Displays the bubble anchored at the specified |anchorPoint|. 52- (void)showAtAnchor:(NSPoint)anchorPoint; 53 54// Helper function to create an uneditable text field. 55- (base::scoped_nsobject<NSTextField>)makeTextField; 56 57// Helper function to set the appropriately styled details string. 58- (void)setDetailsStringForField:(NSTextField*)details; 59 60// Create and lay out all control elements inside the bubble. 61- (void)performLayout; 62 63// Delegate interface for clicks on "Learn more". 64- (void)onLinkClicked:(id)sender; 65 66@end 67 68 69@implementation NewCreditCardBubbleControllerCocoa 70 71- (id)initWithParentWindow:(NSWindow*)parentWindow 72 controller: 73 (autofill::NewCreditCardBubbleController*)controller 74 bridge:(autofill::NewCreditCardBubbleCocoa*)bridge { 75 base::scoped_nsobject<InfoBubbleWindow> window( 76 [[InfoBubbleWindow alloc] 77 initWithContentRect:ui::kWindowSizeDeterminedLater 78 styleMask:NSBorderlessWindowMask 79 backing:NSBackingStoreBuffered 80 defer:NO]); 81 bridge_.reset(bridge); 82 if ((self = [super initWithWindow:window 83 parentWindow:parentWindow 84 anchoredAt:NSZeroPoint])) { 85 controller_ = controller; 86 [window setCanBecomeKeyWindow:NO]; 87 88 ui::NativeTheme* nativeTheme = ui::NativeTheme::instance(); 89 [[self bubble] setBackgroundColor: 90 gfx::SkColorToCalibratedNSColor(nativeTheme->GetSystemColor( 91 ui::NativeTheme::kColorId_DialogBackground))]; 92 [self performLayout]; 93 } 94 return self; 95} 96 97- (void)showAtAnchor:(NSPoint)anchorPoint { 98 [self setAnchorPoint:anchorPoint]; 99 [[self bubble] setNeedsDisplay:YES]; 100 [self showWindow:nil]; 101} 102 103- (base::scoped_nsobject<NSTextField>)makeTextField { 104 base::scoped_nsobject<NSTextField> textField( 105 [[NSTextField alloc] initWithFrame:NSZeroRect]); 106 [textField setEditable:NO]; 107 [textField setSelectable:NO]; 108 [textField setDrawsBackground:NO]; 109 [textField setBezeled:NO]; 110 111 return textField; 112} 113 114- (void)setDetailsStringForField:(NSTextField*)details { 115 // Set linespacing for |details| to match line spacing used in the dialog. 116 base::scoped_nsobject<NSMutableParagraphStyle> paragraphStyle( 117 [[NSMutableParagraphStyle alloc] init]); 118 [paragraphStyle setLineSpacing:0.5 * [[details font] pointSize]]; 119 120 NSString* description = 121 base::SysUTF16ToNSString(controller_->CardDescription().description); 122 base::scoped_nsobject<NSAttributedString> detailsString( 123 [[NSAttributedString alloc] 124 initWithString:description 125 attributes:@{ NSParagraphStyleAttributeName : paragraphStyle }]); 126 127 [details setAttributedStringValue:detailsString]; 128} 129 130- (void)performLayout { 131 base::scoped_nsobject<NSTextField> title([self makeTextField]); 132 base::scoped_nsobject<NSImageView> icon([[NSImageView alloc] 133 initWithFrame:NSZeroRect]); 134 base::scoped_nsobject<NSTextField> name([self makeTextField]); 135 base::scoped_nsobject<NSTextField> details([self makeTextField]); 136 base::scoped_nsobject<NSButton> link([[HyperlinkButtonCell buttonWithString: 137 base::SysUTF16ToNSString(controller_->LinkText())] retain]); 138 139 [link setTarget:self]; 140 [link setAction:@selector(onLinkClicked:)]; 141 142 // Use uniform font across all controls (except title). 143 [details setFont:[link font]]; 144 [name setFont:[link font]]; 145 [title setFont:[NSFont systemFontOfSize:15.0]]; 146 147 // Populate fields. 148 const autofill::CreditCardDescription& card_desc = 149 controller_->CardDescription(); 150 [title setStringValue:base::SysUTF16ToNSString(controller_->TitleText())]; 151 [icon setImage:card_desc.icon.AsNSImage()]; 152 [name setStringValue:base::SysUTF16ToNSString(card_desc.name)]; 153 [self setDetailsStringForField:details]; 154 155 // Size fields. 156 CGFloat bubbleWidth = autofill::NewCreditCardBubbleView::kContentsWidth; 157 [title sizeToFit]; 158 bubbleWidth = std::max(bubbleWidth, NSWidth([title frame]) + 2 * kInset); 159 [icon setFrameSize:[[icon image] size]]; 160 [name sizeToFit]; 161 [details setFrameSize:[[details cell] cellSizeForBounds: 162 NSMakeRect(0, 0, bubbleWidth - 2 * kInset, CGFLOAT_MAX)]]; 163 [link sizeToFit]; 164 165 // Layout fields. 166 [link setFrameOrigin:NSMakePoint(kInset, kInset)]; 167 [details setFrameOrigin:NSMakePoint( 168 kInset, NSMaxY([link frame]) + kVerticalSpacing)]; 169 [icon setFrameOrigin:NSMakePoint( 170 kInset, NSMaxY([details frame]) + kVerticalSpacing)]; 171 [name setFrameOrigin:NSMakePoint( 172 NSMaxX([icon frame]) + kHorizontalSpacing, 173 NSMidY([icon frame]) - (NSHeight([name frame]) / 2.0))]; 174 [title setFrameOrigin: 175 NSMakePoint(kInset, NSMaxY([icon frame]) + kTitleSpacing)]; 176 177 [[[self window] contentView] setSubviews: 178 @[ title, icon, name, details, link ]]; 179 180 // Update window frame. 181 NSRect windowFrame = [[self window] frame]; 182 windowFrame.size.height = NSMaxY([title frame]) + kInset; 183 windowFrame.size.width = bubbleWidth; 184 [[self window] setFrame:windowFrame display:NO]; 185} 186 187- (void)onLinkClicked:(id)sender { 188 controller_->OnLinkClicked(); 189} 190 191@end 192 193 194namespace autofill { 195 196NewCreditCardBubbleCocoa::~NewCreditCardBubbleCocoa() { 197 controller_->OnBubbleDestroyed(); 198} 199 200void NewCreditCardBubbleCocoa::Show() { 201 NSView* browser_view = controller_->web_contents()->GetNativeView(); 202 NSWindow* parent_window = [browser_view window]; 203 BrowserWindowController* bwc = [BrowserWindowController 204 browserWindowControllerForWindow:parent_window]; 205 206 if (!bubbleController_) 207 CreateCocoaController(parent_window); 208 209 NSPoint anchor_point; 210 NSView* anchor_view; 211 if ([bwc isTabbedWindow]) { 212 anchor_view = [[bwc toolbarController] wrenchButton]; 213 anchor_point = NSMakePoint( 214 NSMidX([anchor_view bounds]), 215 NSMinY([anchor_view bounds]) + kWrenchBubblePointOffsetY); 216 [[bubbleController_ bubble] setArrowLocation:info_bubble::kTopRight]; 217 [[bubbleController_ bubble] setAlignment:info_bubble::kAlignArrowToAnchor]; 218 } else { 219 anchor_view = browser_view; 220 anchor_point = NSMakePoint( 221 NSMaxX([browser_view bounds]) - kAnchorlessEndPadding, 222 NSMaxY([browser_view bounds]) - kAnchorlessTopPadding); 223 [[bubbleController_ bubble] setArrowLocation:info_bubble::kNoArrow]; 224 if (base::i18n::IsRTL()) { 225 anchor_point.x = NSMaxX([anchor_view bounds]) - anchor_point.x; 226 [[bubbleController_ bubble] setAlignment: 227 info_bubble::kAlignLeftEdgeToAnchorEdge]; 228 } else { 229 [[bubbleController_ bubble] setAlignment: 230 info_bubble::kAlignRightEdgeToAnchorEdge]; 231 } 232 } 233 if ([anchor_view isFlipped]) 234 anchor_point.y = NSMaxY([anchor_view bounds]) - anchor_point.y; 235 anchor_point = [anchor_view convertPoint:anchor_point toView:nil]; 236 237 NSRect frame = NSZeroRect; 238 frame.origin = anchor_point; 239 anchor_point = [parent_window convertBaseToScreen:anchor_point]; 240 [bubbleController_ showAtAnchor:anchor_point]; 241} 242 243void NewCreditCardBubbleCocoa::Hide() { 244 [bubbleController_ close]; 245} 246 247// static 248base::WeakPtr<NewCreditCardBubbleView> NewCreditCardBubbleView::Create( 249 NewCreditCardBubbleController* controller) { 250 NewCreditCardBubbleCocoa* bubble = new NewCreditCardBubbleCocoa(controller); 251 return bubble->weak_ptr_factory_.GetWeakPtr(); 252} 253 254NewCreditCardBubbleCocoa::NewCreditCardBubbleCocoa( 255 NewCreditCardBubbleController* controller) 256 : bubbleController_(NULL), 257 controller_(controller), 258 weak_ptr_factory_(this) { 259} 260 261void NewCreditCardBubbleCocoa::CreateCocoaController(NSWindow* parent) { 262 DCHECK(!bubbleController_); 263 bubbleController_ = [[NewCreditCardBubbleControllerCocoa alloc] 264 initWithParentWindow:parent 265 controller:controller_ 266 bridge:this]; 267} 268 269InfoBubbleWindow* NewCreditCardBubbleCocoa::GetInfoBubbleWindow() { 270 return base::mac::ObjCCastStrict<InfoBubbleWindow>( 271 [bubbleController_ window]); 272} 273 274} // namespace autofill 275