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