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