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.
5 #include "chrome/browser/ui/cocoa/autofill/new_credit_card_bubble_cocoa.h"
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/base_bubble_controller.h"
14 #import "chrome/browser/ui/cocoa/browser_window_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"
26 const CGFloat kWrenchBubblePointOffsetY = 6;
27 const CGFloat kVerticalSpacing = 8;
28 const CGFloat kHorizontalSpacing = 4;
29 const CGFloat kInset = 20.0;
30 const CGFloat kTitleSpacing = 15;
31 const CGFloat kAnchorlessEndPadding = 20;
32 const CGFloat kAnchorlessTopPadding = 10;
36 @interface NewCreditCardBubbleControllerCocoa : BaseBubbleController {
38 // Controller that drives this bubble. Never NULL; outlives this class.
39 autofill::NewCreditCardBubbleController* controller_;
41 // Objective-C/C++ bridge. Owned by this class.
42 scoped_ptr<autofill::NewCreditCardBubbleCocoa> bridge_;
45 // Designated initializer.
46 - (id)initWithParentWindow:(NSWindow*)parentWindow
48 (autofill::NewCreditCardBubbleController*)controller
49 bridge:(autofill::NewCreditCardBubbleCocoa*)bridge;
51 // Displays the bubble anchored at the specified |anchorPoint|.
52 - (void)showAtAnchor:(NSPoint)anchorPoint;
54 // Helper function to create an uneditable text field.
55 - (base::scoped_nsobject<NSTextField>)makeTextField;
57 // Helper function to set the appropriately styled details string.
58 - (void)setDetailsStringForField:(NSTextField*)details;
60 // Create and lay out all control elements inside the bubble.
61 - (void)performLayout;
63 // Delegate interface for clicks on "Learn more".
64 - (void)onLinkClicked:(id)sender;
69 @implementation NewCreditCardBubbleControllerCocoa
71 - (id)initWithParentWindow:(NSWindow*)parentWindow
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
81 bridge_.reset(bridge);
82 if ((self = [super initWithWindow:window
83 parentWindow:parentWindow
84 anchoredAt:NSZeroPoint])) {
85 controller_ = controller;
86 [window setInfoBubbleCanBecomeKeyWindow:NO];
88 ui::NativeTheme* nativeTheme = ui::NativeTheme::instance();
89 [[self bubble] setBackgroundColor:
90 gfx::SkColorToCalibratedNSColor(nativeTheme->GetSystemColor(
91 ui::NativeTheme::kColorId_DialogBackground))];
97 - (void)showAtAnchor:(NSPoint)anchorPoint {
98 [self setAnchorPoint:anchorPoint];
99 [[self bubble] setNeedsDisplay:YES];
100 [self showWindow:nil];
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];
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]];
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 }]);
127 [details setAttributedStringValue:detailsString];
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]);
139 [link setTarget:self];
140 [link setAction:@selector(onLinkClicked:)];
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]];
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];
156 CGFloat bubbleWidth = autofill::NewCreditCardBubbleView::kContentsWidth;
158 bubbleWidth = std::max(bubbleWidth, NSWidth([title frame]) + 2 * kInset);
159 [icon setFrameSize:[[icon image] size]];
161 [details setFrameSize:[[details cell] cellSizeForBounds:
162 NSMakeRect(0, 0, bubbleWidth - 2 * kInset, CGFLOAT_MAX)]];
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)];
177 [[[self window] contentView] setSubviews:
178 @[ title, icon, name, details, link ]];
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];
187 - (void)onLinkClicked:(id)sender {
188 controller_->OnLinkClicked();
196 NewCreditCardBubbleCocoa::~NewCreditCardBubbleCocoa() {
197 controller_->OnBubbleDestroyed();
200 void 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];
206 if (!bubbleController_)
207 CreateCocoaController(parent_window);
209 NSPoint anchor_point;
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];
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];
229 [[bubbleController_ bubble] setAlignment:
230 info_bubble::kAlignRightEdgeToAnchorEdge];
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];
237 NSRect frame = NSZeroRect;
238 frame.origin = anchor_point;
239 anchor_point = [parent_window convertBaseToScreen:anchor_point];
240 [bubbleController_ showAtAnchor:anchor_point];
243 void NewCreditCardBubbleCocoa::Hide() {
244 [bubbleController_ close];
248 base::WeakPtr<NewCreditCardBubbleView> NewCreditCardBubbleView::Create(
249 NewCreditCardBubbleController* controller) {
250 NewCreditCardBubbleCocoa* bubble = new NewCreditCardBubbleCocoa(controller);
251 return bubble->weak_ptr_factory_.GetWeakPtr();
254 NewCreditCardBubbleCocoa::NewCreditCardBubbleCocoa(
255 NewCreditCardBubbleController* controller)
256 : bubbleController_(NULL),
257 controller_(controller),
258 weak_ptr_factory_(this) {
261 void NewCreditCardBubbleCocoa::CreateCocoaController(NSWindow* parent) {
262 DCHECK(!bubbleController_);
263 bubbleController_ = [[NewCreditCardBubbleControllerCocoa alloc]
264 initWithParentWindow:parent
265 controller:controller_
269 InfoBubbleWindow* NewCreditCardBubbleCocoa::GetInfoBubbleWindow() {
270 return base::mac::ObjCCastStrict<InfoBubbleWindow>(
271 [bubbleController_ window]);
274 } // namespace autofill