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