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