Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / profile_signin_confirmation_view_controller.mm
blob4decf84f208fdafd43c7603ac997ea52c1f52e4c
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.
5 #import "chrome/browser/ui/cocoa/profile_signin_confirmation_view_controller.h"
7 #include <algorithm>
8 #include <cmath>
10 #include "base/callback_helpers.h"
11 #include "base/mac/bundle_locations.h"
12 #include "base/strings/sys_string_conversions.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "chrome/browser/ui/browser_finder.h"
15 #include "chrome/browser/ui/browser_navigator.h"
16 #import "chrome/browser/ui/chrome_style.h"
17 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_control_utils.h"
18 #import "chrome/browser/ui/cocoa/hover_close_button.h"
19 #import "chrome/browser/ui/cocoa/hyperlink_text_view.h"
20 #include "chrome/browser/ui/host_desktop.h"
21 #include "chrome/browser/ui/sync/profile_signin_confirmation_helper.h"
22 #include "chrome/common/url_constants.h"
23 #include "google_apis/gaia/gaia_auth_util.h"
24 #include "grit/chromium_strings.h"
25 #include "grit/generated_resources.h"
26 #include "skia/ext/skia_utils_mac.h"
27 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h"
28 #import "ui/base/cocoa/controls/hyperlink_button_cell.h"
29 #include "ui/base/l10n/l10n_util.h"
31 namespace {
33 const CGFloat kWindowMinWidth = 500;
34 const CGFloat kButtonGap = 6;
35 const CGFloat kDialogAlertBarBorderWidth = 1;
37 // Determine the frame required to fit the content of a string.  Uses the
38 // provided height and width as preferred dimensions, where a value of
39 // 0.0 indicates no preference.
40 NSRect ComputeFrame(NSAttributedString* text, CGFloat width, CGFloat height) {
41   NSRect frame =
42       [text boundingRectWithSize:NSMakeSize(width, height)
43                          options:NSStringDrawingUsesLineFragmentOrigin];
44   // boundingRectWithSize is known to underestimate the width.
45   static const CGFloat kTextViewPadding = 10;
46   frame.size.width += kTextViewPadding;
47   return frame;
50 // Make the indicated range of characters in a text view bold.
51 void MakeTextBold(NSTextField* textField, int offset, int length) {
52   base::scoped_nsobject<NSMutableAttributedString> text(
53       [[textField attributedStringValue] mutableCopy]);
54   NSFont* currentFont =
55       [text attribute:NSFontAttributeName
56               atIndex:offset
57                effectiveRange:NULL];
58   NSFontManager* fontManager = [NSFontManager sharedFontManager];
59   NSFont* boldFont = [fontManager convertFont:currentFont
60                                   toHaveTrait:NSBoldFontMask];
61   [text beginEditing];
62   [text addAttribute:NSFontAttributeName
63                value:boldFont
64                range:NSMakeRange(offset, length)];
65   [text endEditing];
66   [textField setAttributedStringValue:text];
69 // Remove underlining from the specified range of characters in a text view.
70 void RemoveUnderlining(NSTextView* textView, int offset, int length) {
71   // Clear the default link attributes that were set by the
72   // HyperlinkTextView, otherwise removing the underline doesn't matter.
73   [textView setLinkTextAttributes:nil];
74   NSTextStorage* text = [textView textStorage];
75   NSRange range = NSMakeRange(offset, length);
76   [text addAttribute:NSUnderlineStyleAttributeName
77                value:[NSNumber numberWithInt:NSUnderlineStyleNone]
78                range:range];
81 // Create a new NSTextView and add it to the specified parent.
82 NSTextView* AddTextView(
83     NSView* parent,
84     id<NSTextViewDelegate> delegate,
85     const base::string16& message,
86     const base::string16& link,
87     int offset,
88     const ui::ResourceBundle::FontStyle& font_style) {
89   base::scoped_nsobject<HyperlinkTextView> textView(
90       [[HyperlinkTextView alloc] initWithFrame:NSZeroRect]);
91   NSFont* font = ui::ResourceBundle::GetSharedInstance().GetFont(
92       font_style).GetNativeFont();
93   NSColor* linkColor = gfx::SkColorToCalibratedNSColor(
94       chrome_style::GetLinkColor());
95   [textView setMessageAndLink:base::SysUTF16ToNSString(message)
96                      withLink:base::SysUTF16ToNSString(link)
97                      atOffset:offset
98                          font:font
99                  messageColor:[NSColor blackColor]
100                     linkColor:linkColor];
101   RemoveUnderlining(textView, offset, link.size());
102   [textView setDelegate:delegate];
103   [parent addSubview:textView];
104   return textView.autorelease();
107 // Create a new NSTextField and add it to the specified parent.
108 NSTextField* AddTextField(
109     NSView* parent,
110     const base::string16& message,
111     const ui::ResourceBundle::FontStyle& font_style) {
112   NSTextField* textField = constrained_window::CreateLabel();
113   [textField setAttributedStringValue:
114       constrained_window::GetAttributedLabelString(
115           SysUTF16ToNSString(message),
116           font_style,
117           NSNaturalTextAlignment,
118           NSLineBreakByWordWrapping)];
119   [parent addSubview:textField];
120   return textField;
123 }  // namespace
125 @interface ProfileSigninConfirmationViewController ()
126 - (void)learnMore;
127 - (void)addButton:(NSButton*)button
128         withTitle:(int)resourceID
129            target:(id)target
130            action:(SEL)action
131    shouldAutoSize:(BOOL)shouldAutoSize;
132 @end
134 @implementation ProfileSigninConfirmationViewController
136 - (id)initWithBrowser:(Browser*)browser
137              username:(const std::string&)username
138              delegate:(ui::ProfileSigninConfirmationDelegate*)delegate
139   closeDialogCallback:(const base::Closure&)closeDialogCallback
140  offerProfileCreation:(bool)offer {
141   if ((self = [super initWithNibName:nil bundle:nil])) {
142     browser_ = browser;
143     username_ = username;
144     delegate_ = delegate;
145     closeDialogCallback_ = closeDialogCallback;
146     offerProfileCreation_ = offer;
147   }
148   return self;
151 - (void)loadView {
152   self.view = [[[NSView alloc] initWithFrame:NSZeroRect] autorelease];
153   cancelButton_.reset(
154       [[ConstrainedWindowButton alloc] initWithFrame:NSZeroRect]);
155   okButton_.reset(
156       [[ConstrainedWindowButton alloc] initWithFrame:NSZeroRect]);
157   if (offerProfileCreation_) {
158     createProfileButton_.reset(
159         [[ConstrainedWindowButton alloc] initWithFrame:NSZeroRect]);
160   }
161   promptBox_.reset(
162       [[NSBox alloc] initWithFrame:NSZeroRect]);
163   closeButton_.reset(
164       [[WebUIHoverCloseButton alloc] initWithFrame:NSZeroRect]);
166   // -------------------------------
167   // | Title                     x |
168   // |-----------------------------| (1 px border)
169   // | Prompt    (box)             |
170   // |-----------------------------| (1 px border)
171   // | Explanation                 |
172   // |                             |
173   // | [create]      [cancel] [ok] |
174   // -------------------------------
176   // The width of the dialog should be sufficient to fit the buttons on
177   // one line and the title and the close button on one line, but not
178   // smaller than kWindowMinWidth.  Therefore we first layout the title
179   // and the buttons and then compute the necessary width.
181   // OK button.
182   [self addButton:okButton_
183         withTitle:IDS_ENTERPRISE_SIGNIN_CONTINUE_NEW_STYLE
184            target:self
185            action:@selector(ok:)
186    shouldAutoSize:YES];
188   // Cancel button.
189   [self addButton:cancelButton_
190         withTitle:IDS_ENTERPRISE_SIGNIN_CANCEL
191            target:self
192            action:@selector(cancel:)
193    shouldAutoSize:YES];
195   // Add the close button.
196   [self addButton:closeButton_
197         withTitle:0
198            target:self
199            action:@selector(close:)
200    shouldAutoSize:NO];
201   NSRect closeButtonFrame = [closeButton_ frame];
202   closeButtonFrame.size.width = chrome_style::GetCloseButtonSize();
203   closeButtonFrame.size.height = chrome_style::GetCloseButtonSize();
204   [closeButton_ setFrame:closeButtonFrame];
206   // Create Profile link.
207   if (offerProfileCreation_) {
208     [self addButton:createProfileButton_
209           withTitle:IDS_ENTERPRISE_SIGNIN_CREATE_NEW_PROFILE_NEW_STYLE
210              target:self
211              action:@selector(createProfile:)
212      shouldAutoSize:YES];
213   }
215   // Add the title label.
216   titleField_.reset(
217       [AddTextField([self view],
218                     l10n_util::GetStringUTF16(
219                         IDS_ENTERPRISE_SIGNIN_TITLE_NEW_STYLE),
220                     chrome_style::kTitleFontStyle) retain]);
221   [titleField_ setFrame:ComputeFrame(
222       [titleField_ attributedStringValue], 0.0, 0.0)];
224   // Compute the dialog width using the title and buttons.
225   const CGFloat buttonsWidth =
226       (offerProfileCreation_ ? NSWidth([createProfileButton_ frame]) : 0) +
227       kButtonGap + NSWidth([cancelButton_ frame]) +
228       kButtonGap + NSWidth([okButton_ frame]);
229   const CGFloat titleWidth =
230       NSWidth([titleField_ frame]) + NSWidth([closeButton_ frame]);
231   // Dialog minimum width must include the padding.
232   const CGFloat minWidth =
233       kWindowMinWidth - 2 * chrome_style::kHorizontalPadding;
234   const CGFloat width = std::max(minWidth,
235                                  std::max(buttonsWidth, titleWidth));
236   const CGFloat dialogWidth = width + 2 * chrome_style::kHorizontalPadding;
238   // Now setup the prompt and explanation text using the computed width.
240   // Prompt box.
241   [promptBox_ setBorderColor:gfx::SkColorToCalibratedNSColor(
242       ui::GetSigninConfirmationPromptBarColor(
243           ui::kSigninConfirmationPromptBarBorderAlpha))];
244   [promptBox_ setBorderWidth:kDialogAlertBarBorderWidth];
245   [promptBox_ setFillColor:gfx::SkColorToCalibratedNSColor(
246       ui::GetSigninConfirmationPromptBarColor(
247           ui::kSigninConfirmationPromptBarBackgroundAlpha))];
248   [promptBox_ setBoxType:NSBoxCustom];
249   [promptBox_ setTitlePosition:NSNoTitle];
250   [[self view] addSubview:promptBox_];
252   // Prompt text.
253   size_t offset;
254   const base::string16 domain =
255       base::ASCIIToUTF16(gaia::ExtractDomainName(username_));
256   const base::string16 username = base::ASCIIToUTF16(username_);
257   const base::string16 prompt_text =
258       l10n_util::GetStringFUTF16(
259           IDS_ENTERPRISE_SIGNIN_ALERT_NEW_STYLE,
260           domain, &offset);
261   promptField_.reset(
262       [AddTextField(promptBox_, prompt_text, chrome_style::kTextFontStyle)
263           retain]);
264   MakeTextBold(promptField_, offset, domain.size());
265   [promptField_ setFrame:ComputeFrame(
266         [promptField_ attributedStringValue], width, 0.0)];
268   // Set the height of the prompt box from the prompt text, padding, and border.
269   CGFloat boxHeight =
270       kDialogAlertBarBorderWidth +
271       chrome_style::kRowPadding +
272       NSHeight([promptField_ frame]) +
273       chrome_style::kRowPadding +
274       kDialogAlertBarBorderWidth;
275   [promptBox_ setFrame:NSMakeRect(0, 0, dialogWidth, boxHeight)];
277   // Explanation text.
278   std::vector<size_t> offsets;
279   const base::string16 learn_more_text =
280       l10n_util::GetStringUTF16(
281           IDS_ENTERPRISE_SIGNIN_PROFILE_LINK_LEARN_MORE);
282   const base::string16 explanation_text =
283       l10n_util::GetStringFUTF16(
284           offerProfileCreation_ ?
285           IDS_ENTERPRISE_SIGNIN_EXPLANATION_WITH_PROFILE_CREATION_NEW_STYLE :
286           IDS_ENTERPRISE_SIGNIN_EXPLANATION_WITHOUT_PROFILE_CREATION_NEW_STYLE,
287           username, learn_more_text, &offsets);
288   // HyperlinkTextView requires manually inserting the link text
289   // into the middle of the message text.  To do this we slice out
290   // the "learn more" string from the message so that it can be
291   // inserted again.
292   const base::string16 explanation_message_text =
293     explanation_text.substr(0, offsets[1]) +
294     explanation_text.substr(offsets[1] + learn_more_text.size());
295   explanationField_.reset(
296       [AddTextView([self view], self, explanation_message_text, learn_more_text,
297                    offsets[1], chrome_style::kTextFontStyle) retain]);
299   [explanationField_ setFrame:ComputeFrame(
300         [explanationField_ attributedString], width, 0.0)];
302   // Layout the elements, starting at the bottom and moving up.
304   CGFloat curX = dialogWidth - chrome_style::kHorizontalPadding;
305   CGFloat curY = chrome_style::kClientBottomPadding;
307   // Buttons should go |Cancel|Continue|CreateProfile|, unless
308   // |CreateProfile| isn't shown.
309   if (offerProfileCreation_) {
310     curX -= NSWidth([createProfileButton_ frame]);
311     [createProfileButton_ setFrameOrigin:NSMakePoint(curX, curY)];
312     curX -= kButtonGap;
313   }
314   curX -= NSWidth([okButton_ frame]);
315   [okButton_ setFrameOrigin:NSMakePoint(curX, curY)];
316   curX -= (kButtonGap + NSWidth([cancelButton_ frame]));
317   [cancelButton_ setFrameOrigin:NSMakePoint(curX, curY)];
319   curY += NSHeight([cancelButton_ frame]);
321   // Explanation text.
322   curY += chrome_style::kRowPadding;
323   [explanationField_
324       setFrameOrigin:NSMakePoint(chrome_style::kHorizontalPadding, curY)];
325   curY += NSHeight([explanationField_ frame]);
327   // Prompt box goes all the way to the edges.
328   curX = 0;
329   curY += chrome_style::kRowPadding;
330   [promptBox_ setFrameOrigin:NSMakePoint(curX, curY)];
331   curY += NSHeight([promptBox_ frame]);
333   // Prompt label fits in the middle of the box.
334   NSRect boxClientFrame = [[promptBox_ contentView] bounds];
335   CGFloat boxHorizontalMargin =
336       roundf((dialogWidth - NSWidth(boxClientFrame)) / 2);
337   CGFloat boxVerticalMargin =
338       roundf((boxHeight - NSHeight(boxClientFrame)) / 2);
339   [promptField_ setFrameOrigin:NSMakePoint(
340       chrome_style::kHorizontalPadding - boxHorizontalMargin,
341       chrome_style::kRowPadding - boxVerticalMargin)];
343   // Title goes at the top.
344   curY += chrome_style::kRowPadding;
345   [titleField_
346       setFrameOrigin:NSMakePoint(chrome_style::kHorizontalPadding, curY)];
347   curY += NSHeight([titleField_ frame]);
349   // Find the height required to fit everything with the necessary padding.
350   CGFloat dialogHeight = curY + chrome_style::kTitleTopPadding;
352   // Update the dialog frame with the computed dimensions.
353   [[self view] setFrame:NSMakeRect(0, 0, dialogWidth, dialogHeight)];
355   // Close button goes in the top-right corner.
356   NSPoint closeOrigin = NSMakePoint(
357       dialogWidth - chrome_style::kCloseButtonPadding -
358           NSWidth(closeButtonFrame),
359       dialogHeight - chrome_style::kCloseButtonPadding -
360           NSWidth(closeButtonFrame));
361   [closeButton_ setFrameOrigin:closeOrigin];
364 - (IBAction)cancel:(id)sender {
365   if (delegate_) {
366     delegate_->OnCancelSignin();
367     delegate_ = NULL;
368     closeDialogCallback_.Run();
369   }
372 - (IBAction)ok:(id)sender {
373   if (delegate_) {
374     delegate_->OnContinueSignin();
375     delegate_ = NULL;
376     closeDialogCallback_.Run();
377   }
380 - (IBAction)close:(id)sender {
381   if (delegate_) {
382     delegate_->OnCancelSignin();
383     delegate_ = NULL;
384   }
385   closeDialogCallback_.Run();
388 - (IBAction)createProfile:(id)sender {
389   if (delegate_) {
390     delegate_->OnSigninWithNewProfile();
391     delegate_ = NULL;
392     closeDialogCallback_.Run();
393   }
396 - (void)learnMore {
397   chrome::NavigateParams params(
398       browser_, GURL(chrome::kChromeEnterpriseSignInLearnMoreURL),
399       content::PAGE_TRANSITION_AUTO_TOPLEVEL);
400   params.disposition = NEW_POPUP;
401   params.window_action = chrome::NavigateParams::SHOW_WINDOW;
402   chrome::Navigate(&params);
405 - (BOOL)textView:(NSTextView*)textView
406    clickedOnLink:(id)link
407          atIndex:(NSUInteger)charIndex {
408   if (textView == explanationField_.get()) {
409     [self learnMore];
410     return YES;
411   }
412   return NO;
415 - (void)addButton:(NSButton*)button
416         withTitle:(int)resourceID
417            target:(id)target
418            action:(SEL)action
419    shouldAutoSize:(BOOL)shouldAutoSize {
420   if (resourceID)
421     [button setTitle:base::SysUTF16ToNSString(
422         l10n_util::GetStringUTF16(resourceID))];
423   [button setTarget:target];
424   [button setAction:action];
425   [[self view] addSubview:button];
426   if (shouldAutoSize)
427     [GTMUILocalizerAndLayoutTweaker sizeToFitView:button];
430 @end
432 @implementation ProfileSigninConfirmationViewController (TestingAPI)
434 - (ui::ProfileSigninConfirmationDelegate*)delegate {
435   return delegate_;
438 - (NSButton*)createProfileButton {
439   return createProfileButton_.get();
442 - (NSTextView*)explanationField {
443   return explanationField_.get();
446 @end