1// Copyright (c) 2012 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#import "chrome/browser/ui/cocoa/one_click_signin_view_controller.h"
6
7#include "base/callback_helpers.h"
8#include "base/logging.h"
9#include "base/mac/bundle_locations.h"
10#import "chrome/browser/ui/chrome_style.h"
11#include "chrome/browser/ui/sync/one_click_signin_helper.h"
12#include "chrome/browser/ui/sync/one_click_signin_histogram.h"
13#include "chrome/common/url_constants.h"
14#include "chrome/grit/chromium_strings.h"
15#include "chrome/grit/generated_resources.h"
16#include "content/public/browser/web_contents.h"
17#include "grit/components_strings.h"
18#include "skia/ext/skia_utils_mac.h"
19#import "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h"
20#import "ui/base/cocoa/controls/hyperlink_text_view.h"
21#include "ui/base/l10n/l10n_util_mac.h"
22
23namespace {
24
25// The margin between the top edge of the border and the error message text in
26// the sign-in bubble, in the case of an error.
27const CGFloat kTopErrorMessageMargin = 12;
28
29// Shift the origin of |view|'s frame by the given amount in the
30// positive y direction (up).
31void ShiftOriginY(NSView* view, CGFloat amount) {
32  NSPoint origin = [view frame].origin;
33  origin.y += amount;
34  [view setFrameOrigin:origin];
35}
36
37}  // namespace
38
39@interface OneClickSigninViewController ()
40- (CGFloat)initializeInformativeTextView;
41- (void)close;
42@end
43
44@implementation OneClickSigninViewController
45
46
47- (id)initWithNibName:(NSString*)nibName
48          webContents:(content::WebContents*)webContents
49         syncCallback:(const BrowserWindow::StartSyncCallback&)syncCallback
50        closeCallback:(const base::Closure&)closeCallback
51         isSyncDialog:(BOOL)isSyncDialog
52                email:(const base::string16&)email
53         errorMessage:(NSString*)errorMessage {
54  if ((self = [super initWithNibName:nibName
55                              bundle:base::mac::FrameworkBundle()])) {
56    webContents_ = webContents;
57    startSyncCallback_ = syncCallback;
58    closeCallback_ = closeCallback;
59    isSyncDialog_ = isSyncDialog;
60    clickedLearnMore_ = NO;
61    email_ = email;
62    errorMessage_.reset([errorMessage retain]);
63    if (isSyncDialog_)
64      DCHECK(!startSyncCallback_.is_null());
65  }
66  return self;
67}
68
69- (void)viewWillClose {
70  // This is usually called after a click handler has initiated sync
71  // and has reset the callback. However, in the case that we are closing
72  // the window and nothing else has initiated the sync, we must do so here
73  if (isSyncDialog_ && !startSyncCallback_.is_null()) {
74    base::ResetAndReturn(&startSyncCallback_).Run(
75        OneClickSigninSyncStarter::UNDO_SYNC);
76  }
77}
78
79- (IBAction)ok:(id)sender {
80  if (isSyncDialog_) {
81    OneClickSigninHelper::LogConfirmHistogramValue(
82        clickedLearnMore_ ?
83            one_click_signin::HISTOGRAM_CONFIRM_LEARN_MORE_OK :
84            one_click_signin::HISTOGRAM_CONFIRM_OK);
85
86    base::ResetAndReturn(&startSyncCallback_).Run(
87      OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS);
88  }
89  [self close];
90}
91
92- (IBAction)onClickUndo:(id)sender {
93  if (isSyncDialog_) {
94    OneClickSigninHelper::LogConfirmHistogramValue(
95        clickedLearnMore_ ?
96            one_click_signin::HISTOGRAM_CONFIRM_LEARN_MORE_UNDO :
97            one_click_signin::HISTOGRAM_CONFIRM_UNDO);
98
99    base::ResetAndReturn(&startSyncCallback_).Run(
100      OneClickSigninSyncStarter::UNDO_SYNC);
101  }
102  [self close];
103}
104
105- (IBAction)onClickAdvancedLink:(id)sender {
106  if (isSyncDialog_) {
107    OneClickSigninHelper::LogConfirmHistogramValue(
108        clickedLearnMore_ ?
109            one_click_signin::HISTOGRAM_CONFIRM_LEARN_MORE_ADVANCED :
110            one_click_signin::HISTOGRAM_CONFIRM_ADVANCED);
111
112    base::ResetAndReturn(&startSyncCallback_).Run(
113        OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST);
114  }
115  else {
116    content::OpenURLParams params(GURL(chrome::kChromeUISettingsURL),
117                                  content::Referrer(), CURRENT_TAB,
118                                  ui::PAGE_TRANSITION_LINK, false);
119    webContents_->OpenURL(params);
120  }
121  [self close];
122}
123
124- (IBAction)onClickClose:(id)sender {
125  if (isSyncDialog_) {
126    OneClickSigninHelper::LogConfirmHistogramValue(
127        clickedLearnMore_ ?
128            one_click_signin::HISTOGRAM_CONFIRM_LEARN_MORE_CLOSE :
129            one_click_signin::HISTOGRAM_CONFIRM_CLOSE);
130
131    base::ResetAndReturn(&startSyncCallback_).Run(
132        OneClickSigninSyncStarter::UNDO_SYNC);
133  }
134  [self close];
135}
136
137- (void)awakeFromNib {
138  // Lay out the text controls from the bottom up.
139  CGFloat totalYOffset = 0.0;
140
141  if ([errorMessage_ length] == 0) {
142    totalYOffset +=
143        [GTMUILocalizerAndLayoutTweaker sizeToFitView:advancedLink_].height;
144    [[advancedLink_ cell] setTextColor:
145        gfx::SkColorToCalibratedNSColor(chrome_style::GetLinkColor())];
146  } else {
147    // Don't display the advanced link for the error bubble.
148    // To align the Learn More link with the OK button, we need to offset by
149    // the height of the Advanced link, plus the padding between it and the
150    // Learn More link above.
151    float advancedLinkHeightPlusPadding =
152        [informativePlaceholderTextField_ frame].origin.y -
153        [advancedLink_ frame].origin.y;
154
155    totalYOffset -= advancedLinkHeightPlusPadding;
156    [advancedLink_ removeFromSuperview];
157  }
158
159  if (informativePlaceholderTextField_) {
160    if (!isSyncDialog_ && ([errorMessage_ length] != 0)) {
161      // Move up the "Learn more" origin in error case to account for the
162      // smaller bubble.
163      NSRect frame = [informativePlaceholderTextField_ frame];
164      frame = NSOffsetRect(frame, 0, NSHeight([titleTextField_ frame]));
165      [informativePlaceholderTextField_ setFrame:frame];
166    }
167
168    ShiftOriginY(informativePlaceholderTextField_, totalYOffset);
169    totalYOffset += [self initializeInformativeTextView];
170  }
171
172  ShiftOriginY(messageTextField_, totalYOffset);
173  totalYOffset +=
174      [GTMUILocalizerAndLayoutTweaker
175          sizeToFitFixedWidthTextField:messageTextField_];
176
177  ShiftOriginY(titleTextField_, totalYOffset);
178  totalYOffset +=
179      [GTMUILocalizerAndLayoutTweaker
180          sizeToFitFixedWidthTextField:titleTextField_];
181
182  NSSize delta = NSMakeSize(0.0, totalYOffset);
183
184  if (isSyncDialog_) {
185    [messageTextField_ setStringValue:l10n_util::GetNSStringWithFixup(
186        IDS_ONE_CLICK_SIGNIN_DIALOG_TITLE_NEW)];
187  } else if ([errorMessage_ length] != 0) {
188    [titleTextField_ setHidden:YES];
189    [messageTextField_ setStringValue:errorMessage_];
190
191    // Make the bubble less tall, as the title text will be hidden.
192    NSSize size = [[self view] frame].size;
193    size.height = size.height - NSHeight([titleTextField_ frame]);
194    [[self view] setFrameSize:size];
195
196    // Shift the message text up to where the title text used to be.
197    NSPoint origin = [titleTextField_ frame].origin;
198    [messageTextField_ setFrameOrigin:origin];
199    ShiftOriginY(messageTextField_, -kTopErrorMessageMargin);
200
201    // Use "OK" instead of "OK, got it" in the error case, and size the button
202    // accordingly.
203    [closeButton_ setTitle:l10n_util::GetNSStringWithFixup(
204        IDS_OK)];
205    [GTMUILocalizerAndLayoutTweaker sizeToFitView:[closeButton_ superview]];
206  }
207
208  // Resize bubble and window to hold the controls.
209  [GTMUILocalizerAndLayoutTweaker
210      resizeViewWithoutAutoResizingSubViews:[self view]
211                                      delta:delta];
212
213  if (isSyncDialog_) {
214    OneClickSigninHelper::LogConfirmHistogramValue(
215        one_click_signin::HISTOGRAM_CONFIRM_SHOWN);
216  }
217}
218
219- (CGFloat)initializeInformativeTextView {
220  NSRect oldFrame = [informativePlaceholderTextField_ frame];
221
222  // Replace the placeholder NSTextField with the real label NSTextView. The
223  // former doesn't show links in a nice way, but the latter can't be added in
224  // a xib without a containing scroll view, so create the NSTextView
225  // programmatically.
226  informativeTextView_.reset(
227      [[HyperlinkTextView alloc] initWithFrame:oldFrame]);
228  [informativeTextView_.get() setAutoresizingMask:
229      [informativePlaceholderTextField_ autoresizingMask]];
230  [informativeTextView_.get() setDelegate:self];
231
232  // Set the text.
233  NSString* learnMoreText = l10n_util::GetNSStringWithFixup(IDS_LEARN_MORE);
234  NSString* messageText;
235
236  ui::ResourceBundle::FontStyle fontStyle = isSyncDialog_ ?
237      chrome_style::kTextFontStyle : ui::ResourceBundle::SmallFont;
238  NSFont* font = ui::ResourceBundle::GetSharedInstance().GetFont(
239      fontStyle).GetNativeFont();
240
241  // The non-modal bubble already has a text content and only needs the
242  // Learn More link (in a smaller font).
243  if (isSyncDialog_) {
244    messageText = l10n_util::GetNSStringFWithFixup(
245        IDS_ONE_CLICK_SIGNIN_DIALOG_MESSAGE_NEW, email_);
246    messageText = [messageText stringByAppendingString:@" "];
247  } else {
248    messageText = @"";
249  }
250
251  NSColor* linkColor =
252      gfx::SkColorToCalibratedNSColor(chrome_style::GetLinkColor());
253  [informativeTextView_ setMessageAndLink:messageText
254                                 withLink:learnMoreText
255                                 atOffset:[messageText length]
256                                     font:font
257                             messageColor:[NSColor blackColor]
258                                linkColor:linkColor];
259
260
261  // Make the "Advanced" link font as large as the "Learn More" link.
262  [[advancedLink_ cell] setFont:font];
263  [advancedLink_ sizeToFit];
264
265  // Size to fit.
266  [[informativePlaceholderTextField_ cell] setAttributedStringValue:
267      [informativeTextView_ attributedString]];
268  [GTMUILocalizerAndLayoutTweaker
269        sizeToFitFixedWidthTextField:informativePlaceholderTextField_];
270  NSRect newFrame = [informativePlaceholderTextField_ frame];
271  [informativeTextView_ setFrame:newFrame];
272
273  // Swap placeholder.
274  [[informativePlaceholderTextField_ superview]
275     replaceSubview:informativePlaceholderTextField_
276               with:informativeTextView_.get()];
277  informativePlaceholderTextField_ = nil;  // Now released.
278
279  return NSHeight(newFrame) - NSHeight(oldFrame);
280}
281
282- (BOOL)textView:(NSTextView*)textView
283   clickedOnLink:(id)link
284         atIndex:(NSUInteger)charIndex {
285  if (isSyncDialog_ && !clickedLearnMore_) {
286    clickedLearnMore_ = YES;
287
288    OneClickSigninHelper::LogConfirmHistogramValue(
289        one_click_signin::HISTOGRAM_CONFIRM_LEARN_MORE);
290  }
291  WindowOpenDisposition location = isSyncDialog_ ?
292                                   NEW_WINDOW : NEW_FOREGROUND_TAB;
293  content::OpenURLParams params(GURL(chrome::kChromeSyncLearnMoreURL),
294                                content::Referrer(), location,
295                                ui::PAGE_TRANSITION_LINK, false);
296  webContents_->OpenURL(params);
297  return YES;
298}
299
300- (void)close {
301  base::ResetAndReturn(&closeCallback_).Run();
302}
303
304@end
305