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