Don't show supervised user as "already on this device" while they're being imported.
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / profiles / profile_chooser_controller.mm
blob4c00e6e6a9a7c153ff6cfe67622fe1312145f7f4
1 // Copyright 2014 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/profiles/profile_chooser_controller.h"
7 #import <Carbon/Carbon.h>  // kVK_Return.
8 #import <Cocoa/Cocoa.h>
10 #include "base/mac/bundle_locations.h"
11 #include "base/prefs/pref_service.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/sys_string_conversions.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "chrome/app/chrome_command_ids.h"
16 #include "chrome/browser/browser_process.h"
17 #include "chrome/browser/chrome_notification_types.h"
18 #include "chrome/browser/lifetime/application_lifetime.h"
19 #include "chrome/browser/prefs/incognito_mode_prefs.h"
20 #include "chrome/browser/profiles/avatar_menu.h"
21 #include "chrome/browser/profiles/avatar_menu_observer.h"
22 #include "chrome/browser/profiles/profile_avatar_icon_util.h"
23 #include "chrome/browser/profiles/profile_info_cache.h"
24 #include "chrome/browser/profiles/profile_manager.h"
25 #include "chrome/browser/profiles/profile_metrics.h"
26 #include "chrome/browser/profiles/profile_window.h"
27 #include "chrome/browser/profiles/profiles_state.h"
28 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
29 #include "chrome/browser/signin/signin_error_controller_factory.h"
30 #include "chrome/browser/signin/signin_header_helper.h"
31 #include "chrome/browser/signin/signin_manager_factory.h"
32 #include "chrome/browser/signin/signin_promo.h"
33 #include "chrome/browser/signin/signin_ui_util.h"
34 #include "chrome/browser/ui/browser.h"
35 #include "chrome/browser/ui/browser_commands.h"
36 #include "chrome/browser/ui/browser_window.h"
37 #include "chrome/browser/ui/chrome_pages.h"
38 #include "chrome/browser/ui/chrome_style.h"
39 #import "chrome/browser/ui/cocoa/browser_window_utils.h"
40 #import "chrome/browser/ui/cocoa/info_bubble_view.h"
41 #import "chrome/browser/ui/cocoa/info_bubble_window.h"
42 #import "chrome/browser/ui/cocoa/profiles/user_manager_mac.h"
43 #include "chrome/browser/ui/singleton_tabs.h"
44 #include "chrome/browser/ui/user_manager.h"
45 #include "chrome/browser/ui/webui/signin/login_ui_service.h"
46 #include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
47 #include "chrome/common/pref_names.h"
48 #include "chrome/common/url_constants.h"
49 #include "chrome/grit/chromium_strings.h"
50 #include "chrome/grit/generated_resources.h"
51 #include "components/signin/core/browser/mutable_profile_oauth2_token_service.h"
52 #include "components/signin/core/browser/profile_oauth2_token_service.h"
53 #include "components/signin/core/browser/signin_manager.h"
54 #include "components/signin/core/common/profile_management_switches.h"
55 #include "content/public/browser/native_web_keyboard_event.h"
56 #include "content/public/browser/notification_service.h"
57 #include "content/public/browser/render_widget_host_view.h"
58 #include "content/public/browser/web_contents.h"
59 #include "google_apis/gaia/oauth2_token_service.h"
60 #include "grit/theme_resources.h"
61 #include "skia/ext/skia_utils_mac.h"
62 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h"
63 #import "ui/base/cocoa/cocoa_base_utils.h"
64 #import "ui/base/cocoa/controls/blue_label_button.h"
65 #import "ui/base/cocoa/controls/hyperlink_button_cell.h"
66 #import "ui/base/cocoa/controls/hyperlink_text_view.h"
67 #import "ui/base/cocoa/hover_image_button.h"
68 #include "ui/base/cocoa/window_size_constants.h"
69 #include "ui/base/l10n/l10n_util.h"
70 #include "ui/base/l10n/l10n_util_mac.h"
71 #include "ui/base/resource/resource_bundle.h"
72 #include "ui/gfx/image/image.h"
73 #include "ui/gfx/text_elider.h"
74 #include "ui/native_theme/common_theme.h"
75 #include "ui/native_theme/native_theme.h"
77 namespace {
79 // Constants taken from the Windows/Views implementation at:
80 // chrome/browser/ui/views/profile_chooser_view.cc
81 const int kLargeImageSide = 88;
82 const int kSmallImageSide = 32;
83 const CGFloat kFixedMenuWidth = 250;
85 const CGFloat kVerticalSpacing = 16.0;
86 const CGFloat kSmallVerticalSpacing = 10.0;
87 const CGFloat kHorizontalSpacing = 16.0;
88 const CGFloat kTitleFontSize = 15.0;
89 const CGFloat kTextFontSize = 12.0;
90 const CGFloat kProfileButtonHeight = 30;
91 const int kBezelThickness = 3;  // Width of the bezel on an NSButton.
92 const int kImageTitleSpacing = 10;
93 const int kBlueButtonHeight = 30;
94 const CGFloat kFocusRingLineWidth = 2;
96 // Fixed size for embedded sign in pages as defined in Gaia.
97 const CGFloat kFixedGaiaViewWidth = 360;
98 const CGFloat kFixedGaiaViewHeight = 440;
100 // Fixed size for the account removal view.
101 const CGFloat kFixedAccountRemovalViewWidth = 280;
103 // Fixed size for the switch user view.
104 const int kFixedSwitchUserViewWidth = 320;
106 // The tag number for the primary account.
107 const int kPrimaryProfileTag = -1;
109 gfx::Image CreateProfileImage(const gfx::Image& icon, int imageSize) {
110   return profiles::GetSizedAvatarIcon(
111       icon, true /* image is a square */, imageSize, imageSize);
114 // Updates the window size and position.
115 void SetWindowSize(NSWindow* window, NSSize size) {
116   NSRect frame = [window frame];
117   frame.origin.x += frame.size.width - size.width;
118   frame.origin.y += frame.size.height - size.height;
119   frame.size = size;
120   [window setFrame:frame display:YES];
123 NSString* ElideEmail(const std::string& email, CGFloat width) {
124   const base::string16 elidedEmail = gfx::ElideText(
125       base::UTF8ToUTF16(email), gfx::FontList(), width, gfx::ELIDE_EMAIL);
126   return base::SysUTF16ToNSString(elidedEmail);
129 NSString* ElideMessage(const base::string16& message, CGFloat width) {
130   return base::SysUTF16ToNSString(
131       gfx::ElideText(message, gfx::FontList(), width, gfx::ELIDE_TAIL));
134 // Builds a label with the given |title| anchored at |frame_origin|. Sets the
135 // text color to |text_color| if not null.
136 NSTextField* BuildLabel(NSString* title,
137                         NSPoint frame_origin,
138                         NSColor* text_color) {
139   base::scoped_nsobject<NSTextField> label(
140       [[NSTextField alloc] initWithFrame:NSZeroRect]);
141   [label setStringValue:title];
142   [label setEditable:NO];
143   [label setAlignment:NSLeftTextAlignment];
144   [label setBezeled:NO];
145   [label setFont:[NSFont labelFontOfSize:kTextFontSize]];
146   [label setDrawsBackground:NO];
147   [label setFrameOrigin:frame_origin];
148   [label sizeToFit];
150   if (text_color)
151     [[label cell] setTextColor:text_color];
153   return label.autorelease();
156 // Builds an NSTextView that has the contents set to the specified |message|,
157 // with a non-underlined |link| inserted at |link_offset|. The view is anchored
158 // at the specified |frame_origin| and has a fixed |frame_width|.
159 NSTextView* BuildFixedWidthTextViewWithLink(
160     id<NSTextViewDelegate> delegate,
161     NSString* message,
162     NSString* link,
163     int link_offset,
164     NSPoint frame_origin,
165     CGFloat frame_width) {
166   base::scoped_nsobject<HyperlinkTextView> text_view(
167       [[HyperlinkTextView alloc] initWithFrame:NSZeroRect]);
168   NSColor* link_color = gfx::SkColorToCalibratedNSColor(
169       chrome_style::GetLinkColor());
170   NSMutableString* finalMessage =
171       [NSMutableString stringWithFormat:@"%@\n", message];
172   [finalMessage insertString:link atIndex:link_offset];
173   // Adds a padding row at the bottom, because |boundingRectWithSize| below cuts
174   // off the last row sometimes.
175   [text_view setMessage:finalMessage
176                withFont:[NSFont labelFontOfSize:kTextFontSize]
177            messageColor:[NSColor blackColor]];
178   [text_view addLinkRange:NSMakeRange(link_offset, [link length])
179                  withName:@""
180                 linkColor:link_color];
182   // Removes the underlining from the link.
183   [text_view setLinkTextAttributes:nil];
184   NSTextStorage* text_storage = [text_view textStorage];
185   NSRange link_range = NSMakeRange(link_offset, [link length]);
186   [text_storage addAttribute:NSUnderlineStyleAttributeName
187                        value:[NSNumber numberWithInt:NSUnderlineStyleNone]
188                        range:link_range];
190   NSRect frame = [[text_view attributedString]
191       boundingRectWithSize:NSMakeSize(frame_width, 0)
192                    options:NSStringDrawingUsesLineFragmentOrigin];
193   frame.origin = frame_origin;
194   [text_view setFrame:frame];
195   [text_view setDelegate:delegate];
196   return text_view.autorelease();
199 // Returns the native dialog background color.
200 NSColor* GetDialogBackgroundColor() {
201   return gfx::SkColorToCalibratedNSColor(
202       ui::NativeTheme::instance()->GetSystemColor(
203           ui::NativeTheme::kColorId_DialogBackground));
206 // Builds a title card with one back button right aligned and one label center
207 // aligned.
208 NSView* BuildTitleCard(NSRect frame_rect,
209                        const base::string16& message,
210                        id back_button_target,
211                        SEL back_button_action) {
212   base::scoped_nsobject<NSView> container(
213       [[NSView alloc] initWithFrame:frame_rect]);
215   base::scoped_nsobject<HoverImageButton> button(
216       [[HoverImageButton alloc] initWithFrame:frame_rect]);
217   [button setBordered:NO];
218   ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
219   [button setDefaultImage:rb->GetNativeImageNamed(IDR_BACK).ToNSImage()];
220   [button setHoverImage:rb->GetNativeImageNamed(IDR_BACK_H).ToNSImage()];
221   [button setPressedImage:rb->GetNativeImageNamed(IDR_BACK_P).ToNSImage()];
222   [button setTarget:back_button_target];
223   [button setAction:back_button_action];
224   [button setFrameSize:NSMakeSize(kProfileButtonHeight, kProfileButtonHeight)];
225   [button setFrameOrigin:NSMakePoint(kHorizontalSpacing, 0)];
227   CGFloat max_label_width = frame_rect.size.width -
228       (kHorizontalSpacing * 2 + kProfileButtonHeight) * 2;
229   NSTextField* title_label = BuildLabel(
230       ElideMessage(message, max_label_width),
231       NSZeroPoint, nil);
232   [title_label setAlignment:NSCenterTextAlignment];
233   [title_label setFont:[NSFont labelFontOfSize:kTitleFontSize]];
234   [title_label sizeToFit];
235   CGFloat x_offset = (frame_rect.size.width - NSWidth([title_label frame])) / 2;
236   CGFloat y_offset =
237       (NSHeight([button frame]) - NSHeight([title_label frame])) / 2;
238   [title_label setFrameOrigin:NSMakePoint(x_offset, y_offset)];
240   [container addSubview:button];
241   [container addSubview:title_label];
242   CGFloat height = std::max(NSMaxY([title_label frame]),
243                             NSMaxY([button frame])) + kVerticalSpacing;
244   [container setFrameSize:NSMakeSize(NSWidth([container frame]), height)];
246   return container.autorelease();
249 bool HasAuthError(Profile* profile) {
250   const SigninErrorController* error_controller =
251       SigninErrorControllerFactory::GetForProfile(profile);
252   return error_controller && error_controller->HasError();
255 std::string GetAuthErrorAccountId(Profile* profile) {
256   const SigninErrorController* error_controller =
257       SigninErrorControllerFactory::GetForProfile(profile);
258   if (!error_controller)
259     return std::string();
261   return error_controller->error_account_id();
264 }  // namespace
266 // Custom WebContentsDelegate that allows handling of hotkeys and suppresses
267 // the context menu.
268 class GaiaWebContentsDelegate : public content::WebContentsDelegate {
269  public:
270   GaiaWebContentsDelegate() {}
271   ~GaiaWebContentsDelegate() override {}
273  private:
274   // content::WebContentsDelegate:
275   bool HandleContextMenu(const content::ContextMenuParams& params) override;
276   void HandleKeyboardEvent(
277       content::WebContents* source,
278       const content::NativeWebKeyboardEvent& event) override;
280   DISALLOW_COPY_AND_ASSIGN(GaiaWebContentsDelegate);
283 bool GaiaWebContentsDelegate::HandleContextMenu(
284     const content::ContextMenuParams& params) {
285   // Suppresses the context menu because some features, such as inspecting
286   // elements, are not appropriate in a bubble.
287   return true;
290 void GaiaWebContentsDelegate::HandleKeyboardEvent(
291     content::WebContents* source,
292     const content::NativeWebKeyboardEvent& event) {
293   if (![BrowserWindowUtils shouldHandleKeyboardEvent:event])
294     return;
296   int chrome_command_id = [BrowserWindowUtils getCommandId:event];
298   bool is_text_editing_command =
299       (event.modifiers & blink::WebInputEvent::MetaKey) &&
300       (event.windowsKeyCode == ui::VKEY_A ||
301        event.windowsKeyCode == ui::VKEY_V);
303   // TODO(guohui): maybe should add an accelerator for the back button.
304   if (chrome_command_id == IDC_CLOSE_WINDOW || chrome_command_id == IDC_EXIT ||
305       is_text_editing_command) {
306     [[NSApp mainMenu] performKeyEquivalent:event.os_event];
307   }
310 // Class that listens to changes to the OAuth2Tokens for the active profile,
311 // changes to the avatar menu model or browser close notifications.
312 class ActiveProfileObserverBridge : public AvatarMenuObserver,
313                                     public content::NotificationObserver,
314                                     public OAuth2TokenService::Observer {
315  public:
316   ActiveProfileObserverBridge(ProfileChooserController* controller,
317                               Browser* browser)
318       : controller_(controller),
319         browser_(browser),
320         token_observer_registered_(false) {
321     registrar_.Add(this, chrome::NOTIFICATION_BROWSER_CLOSING,
322                    content::NotificationService::AllSources());
323     if (!browser_->profile()->IsGuestSession())
324       AddTokenServiceObserver();
325   }
327   ~ActiveProfileObserverBridge() override { RemoveTokenServiceObserver(); }
329  private:
330   void AddTokenServiceObserver() {
331     ProfileOAuth2TokenService* oauth2_token_service =
332         ProfileOAuth2TokenServiceFactory::GetForProfile(browser_->profile());
333     DCHECK(oauth2_token_service);
334     oauth2_token_service->AddObserver(this);
335     token_observer_registered_ = true;
336   }
338   void RemoveTokenServiceObserver() {
339     if (!token_observer_registered_)
340       return;
341     DCHECK(browser_->profile());
342     ProfileOAuth2TokenService* oauth2_token_service =
343         ProfileOAuth2TokenServiceFactory::GetForProfile(browser_->profile());
344     DCHECK(oauth2_token_service);
345     oauth2_token_service->RemoveObserver(this);
346     token_observer_registered_ = false;
347   }
349   // OAuth2TokenService::Observer:
350   void OnRefreshTokenAvailable(const std::string& account_id) override {
351     // Tokens can only be added by adding an account through the inline flow,
352     // which is started from the account management view. Refresh it to show the
353     // update.
354     profiles::BubbleViewMode viewMode = [controller_ viewMode];
355     if (viewMode == profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT ||
356         viewMode == profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT ||
357         viewMode == profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH) {
358       [controller_ initMenuContentsWithView:
359           switches::IsEnableAccountConsistency() ?
360               profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT :
361               profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER];
362     }
363   }
365   void OnRefreshTokenRevoked(const std::string& account_id) override {
366     // Tokens can only be removed from the account management view. Refresh it
367     // to show the update.
368     if ([controller_ viewMode] == profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT)
369       [controller_ initMenuContentsWithView:
370           profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT];
371   }
373   // AvatarMenuObserver:
374   void OnAvatarMenuChanged(AvatarMenu* avatar_menu) override {
375     profiles::BubbleViewMode viewMode = [controller_ viewMode];
376     if (viewMode == profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER ||
377         viewMode == profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT) {
378       [controller_ initMenuContentsWithView:viewMode];
379     }
380   }
382   // content::NotificationObserver:
383   void Observe(int type,
384                const content::NotificationSource& source,
385                const content::NotificationDetails& details) override {
386     DCHECK_EQ(chrome::NOTIFICATION_BROWSER_CLOSING, type);
387     if (browser_ == content::Source<Browser>(source).ptr()) {
388       RemoveTokenServiceObserver();
389       // Clean up the bubble's WebContents (used by the Gaia embedded view), to
390       // make sure the guest profile doesn't have any dangling host renderers.
391       // This can happen if Chrome is quit using Command-Q while the bubble is
392       // still open, which won't give the bubble a chance to be closed and
393       // clean up the WebContents itself.
394       [controller_ cleanUpEmbeddedViewContents];
395     }
396   }
398   ProfileChooserController* controller_;  // Weak; owns this.
399   Browser* browser_;  // Weak.
400   content::NotificationRegistrar registrar_;
402   // The observer can be removed both when closing the browser, and by just
403   // closing the avatar bubble. However, in the case of closing the browser,
404   // the avatar bubble will also be closed afterwards, resulting in a second
405   // attempt to remove the observer. This ensures the observer is only
406   // removed once.
407   bool token_observer_registered_;
409   DISALLOW_COPY_AND_ASSIGN(ActiveProfileObserverBridge);
412 // Custom button cell that adds a left padding before the button image, and
413 // a custom spacing between the button image and title.
414 @interface CustomPaddingImageButtonCell : NSButtonCell {
415  @private
416   // Padding added to the left margin of the button.
417   int leftMarginSpacing_;
418   // Spacing between the cell image and title.
419   int imageTitleSpacing_;
422 - (id)initWithLeftMarginSpacing:(int)leftMarginSpacing
423               imageTitleSpacing:(int)imageTitleSpacing;
424 @end
426 @implementation CustomPaddingImageButtonCell
427 - (id)initWithLeftMarginSpacing:(int)leftMarginSpacing
428               imageTitleSpacing:(int)imageTitleSpacing {
429   if ((self = [super init])) {
430     leftMarginSpacing_ = leftMarginSpacing;
431     imageTitleSpacing_ = imageTitleSpacing;
432   }
433   return self;
436 - (NSRect)drawTitle:(NSAttributedString*)title
437           withFrame:(NSRect)frame
438              inView:(NSView*)controlView {
439   NSRect marginRect;
440   NSDivideRect(frame, &marginRect, &frame, leftMarginSpacing_, NSMinXEdge);
442   // The title frame origin isn't aware of the left margin spacing added
443   // in -drawImage, so it must be added when drawing the title as well.
444   if ([self imagePosition] == NSImageLeft)
445     NSDivideRect(frame, &marginRect, &frame, imageTitleSpacing_, NSMinXEdge);
447   return [super drawTitle:title withFrame:frame inView:controlView];
450 - (void)drawImage:(NSImage*)image
451         withFrame:(NSRect)frame
452            inView:(NSView*)controlView {
453   if ([self imagePosition] == NSImageLeft)
454     frame.origin.x = leftMarginSpacing_;
455   [super drawImage:image withFrame:frame inView:controlView];
458 - (NSSize)cellSize {
459   NSSize buttonSize = [super cellSize];
460   buttonSize.width += leftMarginSpacing_;
461   if ([self imagePosition] == NSImageLeft)
462     buttonSize.width += imageTitleSpacing_;
463   return buttonSize;
466 - (NSFocusRingType)focusRingType {
467   // This is taken care of by the custom drawing code.
468   return NSFocusRingTypeNone;
471 - (void)drawWithFrame:(NSRect)frame inView:(NSView *)controlView {
472   [super drawInteriorWithFrame:frame inView:controlView];
474   // Focus ring.
475   if ([self showsFirstResponder]) {
476     NSRect focusRingRect =
477         NSInsetRect(frame, kFocusRingLineWidth, kFocusRingLineWidth);
478     // TODO(noms): When we are targetting 10.7, we should change this to use
479     // -drawFocusRingMaskWithFrame instead.
480     [[[NSColor keyboardFocusIndicatorColor] colorWithAlphaComponent:1] set];
481     NSBezierPath* path = [NSBezierPath bezierPathWithRect:focusRingRect];
482     [path setLineWidth:kFocusRingLineWidth];
483     [path stroke];
484   }
487 @end
489 // A custom image view that has a transparent backround.
490 @interface TransparentBackgroundImageView : NSImageView
491 @end
493 @implementation TransparentBackgroundImageView
494 - (void)drawRect:(NSRect)dirtyRect {
495   NSColor* backgroundColor = [NSColor colorWithCalibratedWhite:1 alpha:0.6f];
496   [backgroundColor setFill];
497   NSRectFillUsingOperation(dirtyRect, NSCompositeSourceAtop);
498   [super drawRect:dirtyRect];
500 @end
502 @interface CustomCircleImageCell : NSButtonCell
503 @end
505 @implementation CustomCircleImageCell
506 - (void)drawWithFrame:(NSRect)frame inView:(NSView *)controlView {
507   // Display everything as a circle that spans the entire control.
508   NSBezierPath* path = [NSBezierPath bezierPathWithOvalInRect:frame];
509   [path addClip];
510   [super drawImage:[self image] withFrame:frame inView:controlView];
512 @end
514 // A custom image control that shows a "Change" button when moused over.
515 @interface EditableProfilePhoto : HoverImageButton {
516  @private
517   AvatarMenu* avatarMenu_;  // Weak; Owned by ProfileChooserController.
518   base::scoped_nsobject<TransparentBackgroundImageView> changePhotoImage_;
519   ProfileChooserController* controller_;
522 - (id)initWithFrame:(NSRect)frameRect
523          avatarMenu:(AvatarMenu*)avatarMenu
524         profileIcon:(const gfx::Image&)profileIcon
525      editingAllowed:(BOOL)editingAllowed
526      withController:(ProfileChooserController*)controller;
528 // Called when the "Change" button is clicked.
529 - (void)editPhoto:(id)sender;
531 @end
533 @implementation EditableProfilePhoto
534 - (id)initWithFrame:(NSRect)frameRect
535          avatarMenu:(AvatarMenu*)avatarMenu
536         profileIcon:(const gfx::Image&)profileIcon
537      editingAllowed:(BOOL)editingAllowed
538      withController:(ProfileChooserController*)controller {
539   if ((self = [super initWithFrame:frameRect])) {
540     avatarMenu_ = avatarMenu;
541     controller_ = controller;
543     [self setBordered:NO];
545     base::scoped_nsobject<CustomCircleImageCell> cell(
546         [[CustomCircleImageCell alloc] init]);
547     [self setCell:cell.get()];
549     [self setDefaultImage:CreateProfileImage(
550         profileIcon, kLargeImageSide).ToNSImage()];
551     [self setImagePosition:NSImageOnly];
553     NSRect bounds = NSMakeRect(0, 0, kLargeImageSide, kLargeImageSide);
554     if (editingAllowed) {
555       [self setTarget:self];
556       [self setAction:@selector(editPhoto:)];
557       changePhotoImage_.reset([[TransparentBackgroundImageView alloc]
558           initWithFrame:bounds]);
559       [changePhotoImage_ setImage:ui::ResourceBundle::GetSharedInstance().
560           GetNativeImageNamed(IDR_ICON_PROFILES_EDIT_CAMERA).AsNSImage()];
561       [self addSubview:changePhotoImage_];
563       // Hide the image until the button is hovered over.
564       [changePhotoImage_ setHidden:YES];
565     }
567     // Set the image cell's accessibility strings to be the same as the
568     // button's strings.
569     [[self cell] accessibilitySetOverrideValue:l10n_util::GetNSString(
570         editingAllowed ?
571         IDS_PROFILES_NEW_AVATAR_MENU_CHANGE_PHOTO_ACCESSIBLE_NAME :
572         IDS_PROFILES_NEW_AVATAR_MENU_PHOTO_ACCESSIBLE_NAME)
573                                   forAttribute:NSAccessibilityTitleAttribute];
574     [[self cell] accessibilitySetOverrideValue:
575         editingAllowed ? NSAccessibilityButtonRole : NSAccessibilityImageRole
576                                   forAttribute:NSAccessibilityRoleAttribute];
577     [[self cell] accessibilitySetOverrideValue:
578         NSAccessibilityRoleDescription(NSAccessibilityButtonRole, nil)
579             forAttribute:NSAccessibilityRoleDescriptionAttribute];
581     // The button and the cell should read the same thing.
582     [self accessibilitySetOverrideValue:l10n_util::GetNSString(
583         editingAllowed ?
584         IDS_PROFILES_NEW_AVATAR_MENU_CHANGE_PHOTO_ACCESSIBLE_NAME :
585         IDS_PROFILES_NEW_AVATAR_MENU_PHOTO_ACCESSIBLE_NAME)
586                                   forAttribute:NSAccessibilityTitleAttribute];
587     [self accessibilitySetOverrideValue:NSAccessibilityButtonRole
588                                   forAttribute:NSAccessibilityRoleAttribute];
589     [self accessibilitySetOverrideValue:
590         NSAccessibilityRoleDescription(NSAccessibilityButtonRole, nil)
591             forAttribute:NSAccessibilityRoleDescriptionAttribute];
592   }
593   return self;
596 - (void)editPhoto:(id)sender {
597   avatarMenu_->EditProfile(avatarMenu_->GetActiveProfileIndex());
598   [controller_
599       postActionPerformed:ProfileMetrics::PROFILE_DESKTOP_MENU_EDIT_IMAGE];
602 - (void)setHoverState:(HoverState)state {
603   [super setHoverState:state];
604   [changePhotoImage_ setHidden:([self hoverState] == kHoverStateNone)];
607 - (BOOL)canBecomeKeyView {
608   return false;
611 - (BOOL)accessibilityIsIgnored {
612   return NO;
615 - (NSArray*)accessibilityActionNames {
616   NSArray* parentActions = [super accessibilityActionNames];
617   return [parentActions arrayByAddingObject:NSAccessibilityPressAction];
620 - (void)accessibilityPerformAction:(NSString*)action {
621   if ([action isEqualToString:NSAccessibilityPressAction]) {
622     avatarMenu_->EditProfile(avatarMenu_->GetActiveProfileIndex());
623   }
625   [super accessibilityPerformAction:action];
628 @end
630 // A custom text control that turns into a textfield for editing when clicked.
631 @interface EditableProfileNameButton : HoverImageButton<NSTextFieldDelegate> {
632  @private
633   base::scoped_nsobject<NSTextField> profileNameTextField_;
634   Profile* profile_;  // Weak.
635   ProfileChooserController* controller_;
638 - (id)initWithFrame:(NSRect)frameRect
639             profile:(Profile*)profile
640         profileName:(NSString*)profileName
641      editingAllowed:(BOOL)editingAllowed
642      withController:(ProfileChooserController*)controller;
644 // Called when the button is clicked.
645 - (void)showEditableView:(id)sender;
647 // Called when enter is pressed in the text field.
648 - (void)saveProfileName;
650 @end
652 @implementation EditableProfileNameButton
653 - (id)initWithFrame:(NSRect)frameRect
654             profile:(Profile*)profile
655         profileName:(NSString*)profileName
656      editingAllowed:(BOOL)editingAllowed
657      withController:(ProfileChooserController*)controller {
658   if ((self = [super initWithFrame:frameRect])) {
659     profile_ = profile;
660     controller_ = controller;
662     if (editingAllowed) {
663       // Show an "edit" pencil icon when hovering over. In the default state,
664       // we need to create an empty placeholder of the correct size, so that
665       // the text doesn't jump around when the hovered icon appears.
666       ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
667       NSImage* hoverImage = rb->GetNativeImageNamed(
668           IDR_ICON_PROFILES_EDIT_HOVER).AsNSImage();
670       // In order to center the button title, we need to add a left padding of
671       // the same width as the pencil icon.
672       base::scoped_nsobject<CustomPaddingImageButtonCell> cell(
673           [[CustomPaddingImageButtonCell alloc]
674               initWithLeftMarginSpacing:[hoverImage size].width
675                       imageTitleSpacing:0]);
676       [self setCell:cell.get()];
678       NSImage* placeholder = [[NSImage alloc] initWithSize:[hoverImage size]];
679       [self setDefaultImage:placeholder];
680       [self setHoverImage:hoverImage];
681       [self setAlternateImage:
682           rb->GetNativeImageNamed(IDR_ICON_PROFILES_EDIT_PRESSED).AsNSImage()];
683       [self setImagePosition:NSImageRight];
684       [self setTarget:self];
685       [self setAction:@selector(showEditableView:)];
687       // We need to subtract the width of the bezel from the frame rect, so that
688       // the textfield can take the exact same space as the button.
689       frameRect.size.height -= 2 * kBezelThickness;
690       frameRect.origin = NSMakePoint(0, kBezelThickness);
691       profileNameTextField_.reset(
692           [[NSTextField alloc] initWithFrame:frameRect]);
693       [profileNameTextField_ setStringValue:profileName];
694       [profileNameTextField_ setFont:[NSFont labelFontOfSize:kTitleFontSize]];
695       [profileNameTextField_ setEditable:YES];
696       [profileNameTextField_ setDrawsBackground:YES];
697       [profileNameTextField_ setBezeled:YES];
698       [profileNameTextField_ setAlignment:NSCenterTextAlignment];
699       [[profileNameTextField_ cell] setWraps:NO];
700       [[profileNameTextField_ cell] setLineBreakMode:
701           NSLineBreakByTruncatingTail];
702       [[profileNameTextField_ cell] setUsesSingleLineMode:YES];
703       [self addSubview:profileNameTextField_];
704       [profileNameTextField_ setDelegate:self];
706       // Hide the textfield until the user clicks on the button.
707       [profileNameTextField_ setHidden:YES];
709       [[self cell] accessibilitySetOverrideValue:l10n_util::GetNSStringF(
710           IDS_PROFILES_NEW_AVATAR_MENU_EDIT_NAME_ACCESSIBLE_NAME,
711           base::SysNSStringToUTF16(profileName))
712                                     forAttribute:NSAccessibilityTitleAttribute];
714       NSSize textSize = [profileName sizeWithAttributes:@{
715         NSFontAttributeName : [profileNameTextField_ font]
716       }];
718       if (textSize.width > frameRect.size.width - [hoverImage size].width * 2)
719         [self setToolTip:profileName];
720     }
722     [[self cell] accessibilitySetOverrideValue:NSAccessibilityButtonRole
723                                   forAttribute:NSAccessibilityRoleAttribute];
724     [[self cell]
725         accessibilitySetOverrideValue:NSAccessibilityRoleDescription(
726                                           NSAccessibilityButtonRole, nil)
727                          forAttribute:NSAccessibilityRoleDescriptionAttribute];
729     [self setBordered:NO];
730     [self setFont:[NSFont labelFontOfSize:kTitleFontSize]];
731     [self setAlignment:NSCenterTextAlignment];
732     [[self cell] setLineBreakMode:NSLineBreakByTruncatingTail];
733     [self setTitle:profileName];
734   }
735   return self;
738 - (void)saveProfileName {
739   base::string16 newProfileName =
740       base::SysNSStringToUTF16([profileNameTextField_ stringValue]);
742   // Empty profile names are not allowed, and do nothing.
743   base::TrimWhitespace(newProfileName, base::TRIM_ALL, &newProfileName);
744   if (!newProfileName.empty()) {
745     profiles::UpdateProfileName(profile_, newProfileName);
746     [controller_
747         postActionPerformed:ProfileMetrics::PROFILE_DESKTOP_MENU_EDIT_NAME];
748     [profileNameTextField_ setHidden:YES];
749   }
752 - (void)showEditableView:(id)sender {
753   [profileNameTextField_ setHidden:NO];
754   [[self window] makeFirstResponder:profileNameTextField_];
757 - (BOOL)canBecomeKeyView {
758   return false;
761 - (BOOL)control:(NSControl*)control
762                textView:(NSTextView*)textView
763     doCommandBySelector:(SEL)commandSelector {
764   if (commandSelector == @selector(insertTab:) ||
765       commandSelector == @selector(insertNewline:)) {
766     [self saveProfileName];
767     return YES;
768   }
769   return NO;
772 @end
774 // A custom button that allows for setting a background color when hovered over.
775 @interface BackgroundColorHoverButton : HoverImageButton {
776  @private
777   base::scoped_nsobject<NSColor> backgroundColor_;
778   base::scoped_nsobject<NSColor> hoverColor_;
780 @end
782 @implementation BackgroundColorHoverButton
784 - (id)initWithFrame:(NSRect)frameRect
785   imageTitleSpacing:(int)imageTitleSpacing
786     backgroundColor:(NSColor*)backgroundColor {
787   if ((self = [super initWithFrame:frameRect])) {
788     backgroundColor_.reset([backgroundColor retain]);
789     // Use a color from the common theme, since this button is not trying to
790     // look like a native control.
791     SkColor hoverColor;
792     bool found = ui::CommonThemeGetSystemColor(
793         ui::NativeTheme::kColorId_ButtonHoverBackgroundColor, &hoverColor);
794     DCHECK(found);
795     hoverColor_.reset([gfx::SkColorToSRGBNSColor(hoverColor) retain]);
797     [self setBordered:NO];
798     [self setFont:[NSFont labelFontOfSize:kTextFontSize]];
799     [self setButtonType:NSMomentaryChangeButton];
801     base::scoped_nsobject<CustomPaddingImageButtonCell> cell(
802         [[CustomPaddingImageButtonCell alloc]
803             initWithLeftMarginSpacing:kHorizontalSpacing
804                     imageTitleSpacing:imageTitleSpacing]);
805     [cell setLineBreakMode:NSLineBreakByTruncatingTail];
806     [self setCell:cell.get()];
807   }
808   return self;
811 - (void)setHoverState:(HoverState)state {
812   [super setHoverState:state];
813   bool isHighlighted = ([self hoverState] != kHoverStateNone);
815   NSColor* backgroundColor = isHighlighted ? hoverColor_ : backgroundColor_;
816   [[self cell] setBackgroundColor:backgroundColor];
819 -(void)keyDown:(NSEvent*)event {
820   // Since there is no default button in the bubble, it is safe to activate
821   // all buttons on Enter as well, and be consistent with the Windows
822   // implementation.
823   if ([event keyCode] == kVK_Return)
824     [self performClick:self];
825   else
826     [super keyDown:event];
829 - (BOOL)canBecomeKeyView {
830   return YES;
833 @end
835 // A custom view with the given background color.
836 @interface BackgroundColorView : NSView {
837  @private
838   base::scoped_nsobject<NSColor> backgroundColor_;
840 @end
842 @implementation BackgroundColorView
843 - (id)initWithFrame:(NSRect)frameRect
844           withColor:(NSColor*)color {
845   if ((self = [super initWithFrame:frameRect]))
846     backgroundColor_.reset([color retain]);
847   return self;
850 - (void)drawRect:(NSRect)dirtyRect {
851   [backgroundColor_ setFill];
852   NSRectFill(dirtyRect);
853   [super drawRect:dirtyRect];
855 @end
857 // A custom dummy button that is used to clear focus from the bubble's controls.
858 @interface DummyWindowFocusButton : NSButton
859 @end
861 @implementation DummyWindowFocusButton
862 // Ignore accessibility, as this is a placeholder button.
863 - (BOOL)accessibilityIsIgnored {
864   return YES;
867 - (id)accessibilityAttributeValue:(NSString*)attribute {
868   return nil;
871 - (BOOL)canBecomeKeyView {
872   return NO;
875 @end
877 @interface ProfileChooserController ()
878 // Adds an horizontal separator to |container| at |yOffset| and returns the
879 // yOffset corresponding to after the separator.
880 - (CGFloat)addSeparatorToContainer:(NSView*)container
881                          atYOffset:(CGFloat)yOffset;
883 // Builds the right-click profile switcher.
884 - (void)buildFastUserSwitcherViewWithProfiles:(NSMutableArray*)otherProfiles
885                                     atYOffset:(CGFloat)yOffset
886                                   inContainer:(NSView*)container;
888 // Builds the regular profile chooser view.
889 - (void)buildProfileChooserViewWithProfileView:(NSView*)currentProfileView
890                                   tutorialView:(NSView*)tutorialView
891                                      atYOffset:(CGFloat)yOffset
892                                    inContainer:(NSView*)container
893                                    displayLock:(bool)displayLock;
895 // Builds the profile chooser view.
896 - (NSView*)buildProfileChooserView;
898 - (NSView*)buildTutorialViewIfNeededForItem:(const AvatarMenu::Item&)item;
900 // Builds a tutorial card with a title label using |titleMessage|, a content
901 // label using |contentMessage|, a link using |linkMessage|, and a button using
902 // |buttonMessage|. If |stackButton| is YES, places the button above the link.
903 // Otherwise places both on the same row with the link left aligned and button
904 // right aligned. On click, the link would execute |linkAction|, and the button
905 // would execute |buttonAction|. It sets |tutorialMode_| to the given |mode|.
906 - (NSView*)tutorialViewWithMode:(profiles::TutorialMode)mode
907                    titleMessage:(NSString*)titleMessage
908                  contentMessage:(NSString*)contentMessage
909                     linkMessage:(NSString*)linkMessage
910                   buttonMessage:(NSString*)buttonMessage
911                     stackButton:(BOOL)stackButton
912                  hasCloseButton:(BOOL)hasCloseButton
913                      linkAction:(SEL)linkAction
914                    buttonAction:(SEL)buttonAction;
916 // Builds a tutorial card to introduce an upgrade user to the new avatar menu if
917 // needed. |tutorial_shown| indicates if the tutorial has already been shown in
918 // the previous active view. |avatar_item| refers to the current profile.
919 - (NSView*)buildWelcomeUpgradeTutorialView:(const AvatarMenu::Item&)item;
921 // Builds a tutorial card to inform the user about right-click user switching if
922 // needed.
923 - (NSView*)buildRightClickTutorialView;
925 // Builds a tutorial card to have the user confirm the last Chrome signin,
926 // Chrome sync will be delayed until the user either dismisses the tutorial, or
927 // configures sync through the "Settings" link.
928 - (NSView*)buildSigninConfirmationView;
930 // Builds a tutorial card to show the last signin error.
931 - (NSView*)buildSigninErrorView;
933 // Creates the main profile card for the profile |item| at the top of
934 // the bubble.
935 - (NSView*)createCurrentProfileView:(const AvatarMenu::Item&)item;
937 // Creates the possible links for the main profile card with profile |item|.
938 - (NSView*)createCurrentProfileLinksForItem:(const AvatarMenu::Item&)item
939                                        rect:(NSRect)rect;
941 // Creates the disclaimer text for supervised users, telling them that the
942 // manager can view their history etc.
943 - (NSView*)createSupervisedUserDisclaimerView;
945 // Creates a main profile card for the guest user.
946 - (NSView*)createGuestProfileView;
948 // Creates an item for the profile |itemIndex| that is used in the fast profile
949 // switcher in the middle of the bubble.
950 - (NSButton*)createOtherProfileView:(int)itemIndex;
952 // Creates the "Not you" and Lock option buttons.
953 - (NSView*)createOptionsViewWithRect:(NSRect)rect
954                          displayLock:(BOOL)displayLock;
956 // Creates the account management view for the active profile.
957 - (NSView*)createCurrentProfileAccountsView:(NSRect)rect;
959 // Creates the list of accounts for the active profile.
960 - (NSView*)createAccountsListWithRect:(NSRect)rect;
962 // Creates the Gaia sign-in/add account view.
963 - (NSView*)buildGaiaEmbeddedView;
965 // Creates the account removal view.
966 - (NSView*)buildAccountRemovalView;
968 // Create a view that shows various options for an upgrade user who is not
969 // the same person as the currently signed in user.
970 - (NSView*)buildSwitchUserView;
972 // Creates a button with |text|, an icon given by |imageResourceId| and with
973 // |action|.
974 - (NSButton*)hoverButtonWithRect:(NSRect)rect
975                             text:(NSString*)text
976                  imageResourceId:(int)imageResourceId
977                           action:(SEL)action;
979 // Creates a generic link button with |title| and an |action| positioned at
980 // |frameOrigin|.
981 - (NSButton*)linkButtonWithTitle:(NSString*)title
982                      frameOrigin:(NSPoint)frameOrigin
983                           action:(SEL)action;
985 // Creates an email account button with |title| and a remove icon. If
986 // |reauthRequired| is true, the button also displays a warning icon. |tag|
987 // indicates which account the button refers to.
988 - (NSButton*)accountButtonWithRect:(NSRect)rect
989                          accountId:(const std::string&)accountId
990                                tag:(int)tag
991                     reauthRequired:(BOOL)reauthRequired;
993 - (bool)shouldShowGoIncognito;
994 @end
996 @implementation ProfileChooserController
997 - (profiles::BubbleViewMode) viewMode {
998   return viewMode_;
1001 - (void)setTutorialMode:(profiles::TutorialMode)tutorialMode {
1002   tutorialMode_ = tutorialMode;
1005 - (IBAction)switchToProfile:(id)sender {
1006   // Check the event flags to see if a new window should be created.
1007   bool alwaysCreate = ui::WindowOpenDispositionFromNSEvent(
1008       [NSApp currentEvent]) == NEW_WINDOW;
1009   avatarMenu_->SwitchToProfile([sender tag], alwaysCreate,
1010                                ProfileMetrics::SWITCH_PROFILE_ICON);
1013 - (IBAction)showUserManager:(id)sender {
1014   UserManager::Show(base::FilePath(),
1015                     profiles::USER_MANAGER_NO_TUTORIAL,
1016                     profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION);
1017   [self postActionPerformed:
1018       ProfileMetrics::PROFILE_DESKTOP_MENU_OPEN_USER_MANAGER];
1021 - (IBAction)exitGuest:(id)sender {
1022   DCHECK(browser_->profile()->IsGuestSession());
1023   UserManager::Show(base::FilePath(),
1024                     profiles::USER_MANAGER_NO_TUTORIAL,
1025                     profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION);
1026   profiles::CloseGuestProfileWindows();
1029 - (IBAction)goIncognito:(id)sender {
1030   DCHECK([self shouldShowGoIncognito]);
1031   chrome::NewIncognitoWindow(browser_);
1032   [self postActionPerformed:
1033       ProfileMetrics::PROFILE_DESKTOP_MENU_GO_INCOGNITO];
1036 - (IBAction)showAccountManagement:(id)sender {
1037   [self initMenuContentsWithView:profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT];
1040 - (IBAction)hideAccountManagement:(id)sender {
1041   [self initMenuContentsWithView:profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER];
1044 - (IBAction)lockProfile:(id)sender {
1045   profiles::LockProfile(browser_->profile());
1046   [self postActionPerformed:ProfileMetrics::PROFILE_DESKTOP_MENU_LOCK];
1049 - (IBAction)showInlineSigninPage:(id)sender {
1050   [self initMenuContentsWithView:profiles::BUBBLE_VIEW_MODE_GAIA_SIGNIN];
1053 - (IBAction)addAccount:(id)sender {
1054   [self initMenuContentsWithView:profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT];
1055   [self postActionPerformed:ProfileMetrics::PROFILE_DESKTOP_MENU_ADD_ACCT];
1058 - (IBAction)navigateBackFromSigninPage:(id)sender {
1059   std::string primaryAccount = SigninManagerFactory::GetForProfile(
1060       browser_->profile())->GetAuthenticatedAccountId();
1061   bool hasAccountManagement = !primaryAccount.empty() &&
1062       switches::IsEnableAccountConsistency();
1063   [self initMenuContentsWithView:hasAccountManagement ?
1064       profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT :
1065       profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER];
1068 - (IBAction)showAccountRemovalView:(id)sender {
1069   DCHECK(!isGuestSession_);
1071   // Tag is either |kPrimaryProfileTag| for the primary account, or equal to the
1072   // index in |currentProfileAccounts_| for a secondary account.
1073   int tag = [sender tag];
1074   if (tag == kPrimaryProfileTag) {
1075     accountIdToRemove_ = SigninManagerFactory::GetForProfile(
1076         browser_->profile())->GetAuthenticatedAccountId();
1077   } else {
1078     DCHECK(ContainsKey(currentProfileAccounts_, tag));
1079     accountIdToRemove_ = currentProfileAccounts_[tag];
1080   }
1082   [self initMenuContentsWithView:profiles::BUBBLE_VIEW_MODE_ACCOUNT_REMOVAL];
1085 - (IBAction)showAccountReauthenticationView:(id)sender {
1086   DCHECK(!isGuestSession_);
1087   [self initMenuContentsWithView:profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH];
1090 - (IBAction)removeAccount:(id)sender {
1091   DCHECK(!accountIdToRemove_.empty());
1092   ProfileOAuth2TokenServiceFactory::GetPlatformSpecificForProfile(
1093       browser_->profile())->RevokeCredentials(accountIdToRemove_);
1094   [self postActionPerformed:ProfileMetrics::PROFILE_DESKTOP_MENU_REMOVE_ACCT];
1095   accountIdToRemove_.clear();
1097   [self initMenuContentsWithView:profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT];
1100 - (IBAction)seeWhatsNew:(id)sender {
1101   UserManager::Show(base::FilePath(),
1102                     profiles::USER_MANAGER_TUTORIAL_OVERVIEW,
1103                     profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION);
1104   ProfileMetrics::LogProfileNewAvatarMenuUpgrade(
1105       ProfileMetrics::PROFILE_AVATAR_MENU_UPGRADE_WHATS_NEW);
1108 - (IBAction)showSwitchUserView:(id)sender {
1109   [self initMenuContentsWithView:profiles::BUBBLE_VIEW_MODE_SWITCH_USER];
1110   ProfileMetrics::LogProfileNewAvatarMenuUpgrade(
1111       ProfileMetrics::PROFILE_AVATAR_MENU_UPGRADE_NOT_YOU);
1114 - (IBAction)showLearnMorePage:(id)sender {
1115   signin_ui_util::ShowSigninErrorLearnMorePage(browser_->profile());
1118 - (IBAction)configureSyncSettings:(id)sender {
1119   tutorialMode_ = profiles::TUTORIAL_MODE_NONE;
1120   LoginUIServiceFactory::GetForProfile(browser_->profile())->
1121       SyncConfirmationUIClosed(true);
1122   ProfileMetrics::LogProfileNewAvatarMenuSignin(
1123       ProfileMetrics::PROFILE_AVATAR_MENU_SIGNIN_SETTINGS);
1126 - (IBAction)syncSettingsConfirmed:(id)sender {
1127   tutorialMode_ = profiles::TUTORIAL_MODE_NONE;
1128   LoginUIServiceFactory::GetForProfile(browser_->profile())->
1129       SyncConfirmationUIClosed(false);
1130   ProfileMetrics::LogProfileNewAvatarMenuSignin(
1131       ProfileMetrics::PROFILE_AVATAR_MENU_SIGNIN_OK);
1132   [self initMenuContentsWithView:profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER];
1135 - (IBAction)disconnectProfile:(id)sender {
1136   chrome::ShowSettings(browser_);
1137   ProfileMetrics::LogProfileNewAvatarMenuNotYou(
1138       ProfileMetrics::PROFILE_AVATAR_MENU_NOT_YOU_DISCONNECT);
1141 - (IBAction)navigateBackFromSwitchUserView:(id)sender {
1142   [self initMenuContentsWithView:profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER];
1143   ProfileMetrics::LogProfileNewAvatarMenuNotYou(
1144       ProfileMetrics::PROFILE_AVATAR_MENU_NOT_YOU_BACK);
1147 - (IBAction)dismissTutorial:(id)sender {
1148   // Never shows the upgrade tutorial again if manually closed.
1149   if (tutorialMode_ == profiles::TUTORIAL_MODE_WELCOME_UPGRADE) {
1150     browser_->profile()->GetPrefs()->SetInteger(
1151         prefs::kProfileAvatarTutorialShown,
1152         signin_ui_util::kUpgradeWelcomeTutorialShowMax + 1);
1153   }
1155   if(tutorialMode_ == profiles::TUTORIAL_MODE_RIGHT_CLICK_SWITCHING) {
1156     PrefService* localState = g_browser_process->local_state();
1157     localState->SetBoolean(
1158         prefs::kProfileAvatarRightClickTutorialDismissed, true);
1159   }
1161   tutorialMode_ = profiles::TUTORIAL_MODE_NONE;
1162   [self initMenuContentsWithView:profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER];
1165 - (void)windowWillClose:(NSNotification*)notification {
1166   if (tutorialMode_ == profiles::TUTORIAL_MODE_CONFIRM_SIGNIN) {
1167     LoginUIServiceFactory::GetForProfile(browser_->profile())->
1168         SyncConfirmationUIClosed(false);
1169   }
1171   [super windowWillClose:notification];
1174 - (void)moveDown:(id)sender {
1175   [[self window] selectNextKeyView:self];
1178 - (void)moveUp:(id)sender {
1179   [[self window] selectPreviousKeyView:self];
1182 - (void)cleanUpEmbeddedViewContents {
1183   webContents_.reset();
1184   webContentsDelegate_.reset();
1187 - (id)initWithBrowser:(Browser*)browser
1188            anchoredAt:(NSPoint)point
1189              viewMode:(profiles::BubbleViewMode)viewMode
1190          tutorialMode:(profiles::TutorialMode)tutorialMode
1191           serviceType:(signin::GAIAServiceType)serviceType {
1192   base::scoped_nsobject<InfoBubbleWindow> window([[InfoBubbleWindow alloc]
1193       initWithContentRect:ui::kWindowSizeDeterminedLater
1194                 styleMask:NSBorderlessWindowMask
1195                   backing:NSBackingStoreBuffered
1196                     defer:NO]);
1198   if ((self = [super initWithWindow:window
1199                        parentWindow:browser->window()->GetNativeWindow()
1200                          anchoredAt:point])) {
1201     browser_ = browser;
1202     viewMode_ = viewMode;
1203     tutorialMode_ = tutorialMode;
1204     observer_.reset(new ActiveProfileObserverBridge(self, browser_));
1205     serviceType_ = serviceType;
1207     avatarMenu_.reset(new AvatarMenu(
1208         &g_browser_process->profile_manager()->GetProfileInfoCache(),
1209         observer_.get(),
1210         browser_));
1211     avatarMenu_->RebuildMenu();
1213     // Guest profiles do not have a token service.
1214     isGuestSession_ = browser_->profile()->IsGuestSession();
1216     // If view mode is PROFILE_CHOOSER but there is an auth error, force
1217     // ACCOUNT_MANAGEMENT mode.
1218     if (viewMode_ == profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER &&
1219         HasAuthError(browser_->profile()) &&
1220         switches::IsEnableAccountConsistency() &&
1221         avatarMenu_->GetItemAt(avatarMenu_->GetActiveProfileIndex()).
1222             signed_in) {
1223       viewMode_ = profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT;
1224     }
1226     [window accessibilitySetOverrideValue:
1227         l10n_util::GetNSString(IDS_PROFILES_NEW_AVATAR_MENU_ACCESSIBLE_NAME)
1228                              forAttribute:NSAccessibilityTitleAttribute];
1229     [window accessibilitySetOverrideValue:
1230         l10n_util::GetNSString(IDS_PROFILES_NEW_AVATAR_MENU_ACCESSIBLE_NAME)
1231                              forAttribute:NSAccessibilityHelpAttribute];
1233     [[self bubble] setAlignment:info_bubble::kAlignRightEdgeToAnchorEdge];
1234     [[self bubble] setArrowLocation:info_bubble::kNoArrow];
1235     [[self bubble] setBackgroundColor:GetDialogBackgroundColor()];
1236     [self initMenuContentsWithView:viewMode_];
1237   }
1239   return self;
1242 - (void)initMenuContentsWithView:(profiles::BubbleViewMode)viewToDisplay {
1243   if (browser_->profile()->IsSupervised() &&
1244       (viewToDisplay == profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT ||
1245        viewToDisplay == profiles::BUBBLE_VIEW_MODE_ACCOUNT_REMOVAL)) {
1246     LOG(WARNING) << "Supervised user attempted to add/remove account";
1247     return;
1248   }
1249   viewMode_ = viewToDisplay;
1250   NSView* contentView = [[self window] contentView];
1251   [contentView setSubviews:[NSArray array]];
1252   NSView* subView;
1254   switch (viewMode_) {
1255     case profiles::BUBBLE_VIEW_MODE_GAIA_SIGNIN:
1256     case profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT:
1257     case profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH:
1258       subView = [self buildGaiaEmbeddedView];
1259       break;
1260     case profiles::BUBBLE_VIEW_MODE_ACCOUNT_REMOVAL:
1261       subView = [self buildAccountRemovalView];
1262       break;
1263     case profiles::BUBBLE_VIEW_MODE_SWITCH_USER:
1264       subView = [self buildSwitchUserView];
1265       break;
1266     case profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER:
1267     case profiles::BUBBLE_VIEW_MODE_FAST_PROFILE_CHOOSER:
1268     case profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT:
1269       subView = [self buildProfileChooserView];
1270       break;
1271   }
1273   // Clears tutorial mode for all non-profile-chooser views.
1274   if (viewMode_ != profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER)
1275     tutorialMode_ = profiles::TUTORIAL_MODE_NONE;
1277   if (viewMode_ == profiles::BUBBLE_VIEW_MODE_GAIA_SIGNIN ||
1278       viewMode_ == profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT ||
1279       viewMode_ == profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH ||
1280       tutorialMode_ == profiles::TUTORIAL_MODE_CONFIRM_SIGNIN) {
1281     [self setShouldCloseOnResignKey:NO];
1282   } else {
1283     [self setShouldCloseOnResignKey:YES];
1284   }
1286   // Add a dummy, empty element so that we don't initially display any
1287   // focus rings.
1288   NSButton* dummyFocusButton =
1289      [[[DummyWindowFocusButton alloc] initWithFrame:NSZeroRect] autorelease];
1290   [dummyFocusButton setNextKeyView:subView];
1291   [[self window] makeFirstResponder:dummyFocusButton];
1293   [contentView addSubview:subView];
1294   [contentView addSubview:dummyFocusButton];
1295   SetWindowSize([self window],
1296       NSMakeSize(NSWidth([subView frame]), NSHeight([subView frame])));
1299 - (CGFloat)addSeparatorToContainer:(NSView*)container
1300                          atYOffset:(CGFloat)yOffset {
1301   NSBox* separator = [self horizontalSeparatorWithFrame:NSMakeRect(
1302       0, yOffset, kFixedMenuWidth, 0)];
1303   [container addSubview:separator];
1304   return NSMaxY([separator frame]);
1307 // Builds the fast user switcher view in |container| at |yOffset| and populates
1308 // it with the entries for every profile in |otherProfiles|. Returns the new
1309 // yOffset after adding the elements.
1310 - (void)buildFastUserSwitcherViewWithProfiles:(NSMutableArray*)otherProfiles
1311                                     atYOffset:(CGFloat)yOffset
1312                                   inContainer:(NSView*)container {
1313   // Other profiles switcher. The profiles have already been sorted
1314   // by their y-coordinate, so they can be added in the existing order.
1315   for (NSView* otherProfileView in otherProfiles) {
1316    [otherProfileView setFrameOrigin:NSMakePoint(0, yOffset)];
1317    [container addSubview:otherProfileView];
1318    yOffset = NSMaxY([otherProfileView frame]);
1320    yOffset = [self addSeparatorToContainer:container atYOffset: yOffset];
1321   }
1323   [container setFrameSize:NSMakeSize(kFixedMenuWidth, yOffset)];
1326 - (void)buildProfileChooserViewWithProfileView:(NSView*)currentProfileView
1327                                   tutorialView:(NSView*)tutorialView
1328                                      atYOffset:(CGFloat)yOffset
1329                                    inContainer:(NSView*)container
1330                                    displayLock:(bool)displayLock {
1331   // Option buttons.
1332   NSRect rect = NSMakeRect(0, yOffset, kFixedMenuWidth, 0);
1333   NSView* optionsView = [self createOptionsViewWithRect:rect
1334                                             displayLock:displayLock];
1335   [container addSubview:optionsView];
1336   rect.origin.y = NSMaxY([optionsView frame]);
1338   NSBox* separator = [self horizontalSeparatorWithFrame:rect];
1339   [container addSubview:separator];
1340   yOffset = NSMaxY([separator frame]);
1342   // For supervised users, add the disclaimer text.
1343   if (browser_->profile()->IsSupervised()) {
1344     yOffset += kSmallVerticalSpacing;
1345     NSView* disclaimerContainer = [self createSupervisedUserDisclaimerView];
1346     [disclaimerContainer setFrameOrigin:NSMakePoint(0, yOffset)];
1347     [container addSubview:disclaimerContainer];
1348     yOffset = NSMaxY([disclaimerContainer frame]);
1349     yOffset += kSmallVerticalSpacing;
1351     yOffset = [self addSeparatorToContainer:container atYOffset: yOffset];
1352   }
1354   if (viewMode_ == profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT) {
1355     NSView* currentProfileAccountsView = [self createCurrentProfileAccountsView:
1356         NSMakeRect(0, yOffset, kFixedMenuWidth, 0)];
1357     [container addSubview:currentProfileAccountsView];
1358     yOffset = NSMaxY([currentProfileAccountsView frame]);
1360     yOffset = [self addSeparatorToContainer:container atYOffset: yOffset];
1361   }
1363   // Active profile card.
1364   if (currentProfileView) {
1365     yOffset += kVerticalSpacing;
1366     [currentProfileView setFrameOrigin:NSMakePoint(0, yOffset)];
1367     [container addSubview:currentProfileView];
1368     yOffset = NSMaxY([currentProfileView frame]) + kVerticalSpacing;
1369   }
1371   if (tutorialView) {
1372     [tutorialView setFrameOrigin:NSMakePoint(0, yOffset)];
1373     [container addSubview:tutorialView];
1374     yOffset = NSMaxY([tutorialView frame]);
1375     //TODO(mlerman): update UMA stats for the new tutorials.
1376   } else {
1377     tutorialMode_ = profiles::TUTORIAL_MODE_NONE;
1378   }
1380   [container setFrameSize:NSMakeSize(kFixedMenuWidth, yOffset)];
1383 - (NSView*)buildProfileChooserView {
1384   base::scoped_nsobject<NSView> container(
1385       [[NSView alloc] initWithFrame:NSZeroRect]);
1387   NSView* tutorialView = nil;
1388   NSView* currentProfileView = nil;
1389   base::scoped_nsobject<NSMutableArray> otherProfiles(
1390       [[NSMutableArray alloc] init]);
1391   // Local and guest profiles cannot lock their profile.
1392   bool displayLock = false;
1393   bool isFastProfileChooser =
1394       viewMode_ == profiles::BUBBLE_VIEW_MODE_FAST_PROFILE_CHOOSER;
1395   if (isFastProfileChooser) {
1396     // The user is using right-click switching, no need to tell them about it.
1397     PrefService* localState = g_browser_process->local_state();
1398     localState->SetBoolean(
1399         prefs::kProfileAvatarRightClickTutorialDismissed, true);
1400   }
1402   // Loop over the profiles in reverse, so that they are sorted by their
1403   // y-coordinate, and separate them into active and "other" profiles.
1404   for (int i = avatarMenu_->GetNumberOfItems() - 1; i >= 0; --i) {
1405     const AvatarMenu::Item& item = avatarMenu_->GetItemAt(i);
1406     if (item.active) {
1407       if (viewMode_ == profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER) {
1408         tutorialView = [self buildTutorialViewIfNeededForItem:item];
1409       }
1410       currentProfileView = [self createCurrentProfileView:item];
1411       displayLock = item.signed_in &&
1412           profiles::IsLockAvailable(browser_->profile());
1413     } else {
1414       [otherProfiles addObject:[self createOtherProfileView:i]];
1415     }
1416   }
1417   if (!currentProfileView)  // Guest windows don't have an active profile.
1418     currentProfileView = [self createGuestProfileView];
1420   // |yOffset| is the next position at which to draw in |container|
1421   // coordinates. Add a pixel offset so that the bottom option buttons don't
1422   // overlap the bubble's rounded corners.
1423   CGFloat yOffset = 1;
1425   if (isFastProfileChooser) {
1426     [self buildFastUserSwitcherViewWithProfiles:otherProfiles.get()
1427                                       atYOffset:yOffset
1428                                     inContainer:container.get()];
1429   } else {
1430     [self buildProfileChooserViewWithProfileView:currentProfileView
1431                                     tutorialView:tutorialView
1432                                        atYOffset:yOffset
1433                                      inContainer:container.get()
1434                                      displayLock:displayLock];
1435   }
1437   return container.autorelease();
1440 - (NSView*)buildSigninConfirmationView {
1441   ProfileMetrics::LogProfileNewAvatarMenuSignin(
1442       ProfileMetrics::PROFILE_AVATAR_MENU_SIGNIN_VIEW);
1444   NSString* titleMessage = l10n_util::GetNSString(
1445       IDS_PROFILES_CONFIRM_SIGNIN_TUTORIAL_TITLE);
1446   NSString* contentMessage = l10n_util::GetNSString(
1447       IDS_PROFILES_CONFIRM_SIGNIN_TUTORIAL_CONTENT_TEXT);
1448   NSString* linkMessage = l10n_util::GetNSString(
1449       IDS_PROFILES_SYNC_SETTINGS_LINK);
1450   NSString* buttonMessage = l10n_util::GetNSString(
1451       IDS_PROFILES_TUTORIAL_OK_BUTTON);
1452   return [self tutorialViewWithMode:profiles::TUTORIAL_MODE_CONFIRM_SIGNIN
1453                        titleMessage:titleMessage
1454                      contentMessage:contentMessage
1455                         linkMessage:linkMessage
1456                       buttonMessage:buttonMessage
1457                         stackButton:NO
1458                      hasCloseButton:NO
1459                          linkAction:@selector(configureSyncSettings:)
1460                        buttonAction:@selector(syncSettingsConfirmed:)];
1463 - (NSView*)buildSigninErrorView {
1464   NSString* titleMessage = l10n_util::GetNSString(
1465       IDS_PROFILES_ERROR_TUTORIAL_TITLE);
1466   LoginUIService* loginUiService =
1467       LoginUIServiceFactory::GetForProfile(browser_->profile());
1468   NSString* contentMessage =
1469       base::SysUTF16ToNSString(loginUiService->GetLastLoginResult());
1470   NSString* linkMessage = l10n_util::GetNSString(
1471       IDS_PROFILES_PROFILE_TUTORIAL_LEARN_MORE);
1472   return [self tutorialViewWithMode:profiles::TUTORIAL_MODE_CONFIRM_SIGNIN
1473                        titleMessage:titleMessage
1474                      contentMessage:contentMessage
1475                         linkMessage:linkMessage
1476                       buttonMessage:nil
1477                         stackButton:NO
1478                      hasCloseButton:YES
1479                          linkAction:@selector(showLearnMorePage:)
1480                        buttonAction:nil];
1483 - (NSView*)buildWelcomeUpgradeTutorialView:(const AvatarMenu::Item&)item {
1484   ProfileMetrics::LogProfileNewAvatarMenuUpgrade(
1485       ProfileMetrics::PROFILE_AVATAR_MENU_UPGRADE_VIEW);
1487   NSString* titleMessage = l10n_util::GetNSString(
1488       IDS_PROFILES_WELCOME_UPGRADE_TUTORIAL_TITLE);
1489   NSString* contentMessage = l10n_util::GetNSString(
1490       IDS_PROFILES_WELCOME_UPGRADE_TUTORIAL_CONTENT_TEXT);
1491   // For local profiles, the "Not you" link doesn't make sense.
1492   NSString* linkMessage = item.signed_in ?
1493       ElideMessage(
1494           l10n_util::GetStringFUTF16(IDS_PROFILES_NOT_YOU, item.name),
1495           kFixedMenuWidth - 2 * kHorizontalSpacing) :
1496       nil;
1497   NSString* buttonMessage = l10n_util::GetNSString(
1498       IDS_PROFILES_TUTORIAL_WHATS_NEW_BUTTON);
1499   return [self tutorialViewWithMode:profiles::TUTORIAL_MODE_WELCOME_UPGRADE
1500                        titleMessage:titleMessage
1501                      contentMessage:contentMessage
1502                         linkMessage:linkMessage
1503                       buttonMessage:buttonMessage
1504                         stackButton:YES
1505                      hasCloseButton:YES
1506                          linkAction:@selector(showSwitchUserView:)
1507                        buttonAction:@selector(seeWhatsNew:)];
1510 - (NSView*)buildRightClickTutorialView {
1511   NSString* titleMessage = l10n_util::GetNSString(
1512       IDS_PROFILES_RIGHT_CLICK_TUTORIAL_TITLE);
1513   NSString* contentMessage = l10n_util::GetNSString(
1514       IDS_PROFILES_RIGHT_CLICK_TUTORIAL_CONTENT_TEXT);
1515   NSString* buttonMessage = l10n_util::GetNSString(
1516       IDS_PROFILES_TUTORIAL_OK_BUTTON);
1518   return
1519       [self tutorialViewWithMode:profiles::TUTORIAL_MODE_RIGHT_CLICK_SWITCHING
1520                              titleMessage:titleMessage
1521                            contentMessage:contentMessage
1522                               linkMessage:nil
1523                             buttonMessage:buttonMessage
1524                               stackButton:NO
1525                            hasCloseButton:NO
1526                                linkAction:nil
1527                              buttonAction:@selector(dismissTutorial:)];
1530 - (NSView*)buildTutorialViewIfNeededForItem:(const AvatarMenu::Item&)item {
1531   if (tutorialMode_ == profiles::TUTORIAL_MODE_CONFIRM_SIGNIN)
1532     return [self buildSigninConfirmationView];
1534   if (tutorialMode_ == profiles::TUTORIAL_MODE_SHOW_ERROR)
1535     return [self buildSigninErrorView];
1537   if (profiles::ShouldShowWelcomeUpgradeTutorial(
1538       browser_->profile(), tutorialMode_)) {
1539     if (tutorialMode_ != profiles::TUTORIAL_MODE_WELCOME_UPGRADE) {
1540       Profile* profile = browser_->profile();
1541       const int showCount = profile->GetPrefs()->GetInteger(
1542           prefs::kProfileAvatarTutorialShown);
1543       profile->GetPrefs()->SetInteger(
1544           prefs::kProfileAvatarTutorialShown, showCount + 1);
1545     }
1547     return [self buildWelcomeUpgradeTutorialView:item];
1548   }
1550   if (profiles::ShouldShowRightClickTutorial(browser_->profile()))
1551     return [self buildRightClickTutorialView];
1553   return nil;
1556 - (NSView*)tutorialViewWithMode:(profiles::TutorialMode)mode
1557                    titleMessage:(NSString*)titleMessage
1558                  contentMessage:(NSString*)contentMessage
1559                     linkMessage:(NSString*)linkMessage
1560                   buttonMessage:(NSString*)buttonMessage
1561                     stackButton:(BOOL)stackButton
1562                  hasCloseButton:(BOOL)hasCloseButton
1563                      linkAction:(SEL)linkAction
1564                    buttonAction:(SEL)buttonAction {
1565   tutorialMode_ = mode;
1567   NSColor* tutorialBackgroundColor =
1568       gfx::SkColorToSRGBNSColor(profiles::kAvatarTutorialBackgroundColor);
1569   base::scoped_nsobject<NSView> container([[BackgroundColorView alloc]
1570       initWithFrame:NSMakeRect(0, 0, kFixedMenuWidth, 0)
1571           withColor:tutorialBackgroundColor]);
1572   CGFloat availableWidth = kFixedMenuWidth - 2 * kHorizontalSpacing;
1573   CGFloat yOffset = kVerticalSpacing;
1575   // Adds links and buttons at the bottom.
1576   base::scoped_nsobject<NSButton> tutorialOkButton;
1577   if (buttonMessage) {
1578     tutorialOkButton.reset([[HoverButton alloc] initWithFrame:NSZeroRect]);
1579     [tutorialOkButton setTitle:buttonMessage];
1580     [tutorialOkButton setBezelStyle:NSRoundedBezelStyle];
1581     [tutorialOkButton setTarget:self];
1582     [tutorialOkButton setAction:buttonAction];
1583     [tutorialOkButton setAlignment:NSCenterTextAlignment];
1584     [tutorialOkButton sizeToFit];
1585   }
1587   NSButton* learnMoreLink = nil;
1588   if (linkMessage) {
1589     learnMoreLink = [self linkButtonWithTitle:linkMessage
1590                                   frameOrigin:NSZeroPoint
1591                                        action:linkAction];
1592     [[learnMoreLink cell] setTextColor:[NSColor whiteColor]];
1593   }
1595   if (stackButton) {
1596     if (linkMessage) {
1597       [learnMoreLink setFrameOrigin:NSMakePoint(
1598           (kFixedMenuWidth - NSWidth([learnMoreLink frame])) / 2, yOffset)];
1599     }
1600     [tutorialOkButton setFrameSize:NSMakeSize(
1601         availableWidth, NSHeight([tutorialOkButton frame]))];
1602     [tutorialOkButton setFrameOrigin:NSMakePoint(
1603         kHorizontalSpacing,
1604         yOffset + (learnMoreLink ? NSHeight([learnMoreLink frame]) : 0))];
1605   } else {
1606     if (buttonMessage) {
1607       NSSize buttonSize = [tutorialOkButton frame].size;
1608       const CGFloat kTopBottomTextPadding = 6;
1609       const CGFloat kLeftRightTextPadding = 15;
1610       buttonSize.width += 2 * kLeftRightTextPadding;
1611       buttonSize.height += 2 * kTopBottomTextPadding;
1612       [tutorialOkButton setFrameSize:buttonSize];
1613       CGFloat buttonXOffset = kFixedMenuWidth -
1614           NSWidth([tutorialOkButton frame]) - kHorizontalSpacing;
1615       [tutorialOkButton setFrameOrigin:NSMakePoint(buttonXOffset, yOffset)];
1616     }
1618     if (linkMessage) {
1619       CGFloat linkYOffset = yOffset;
1620       if (buttonMessage) {
1621         linkYOffset += (NSHeight([tutorialOkButton frame]) -
1622                         NSHeight([learnMoreLink frame])) / 2;
1623       }
1624       [learnMoreLink setFrameOrigin:NSMakePoint(
1625           kHorizontalSpacing, linkYOffset)];
1626     }
1627   }
1629   if (buttonMessage) {
1630     [container addSubview:tutorialOkButton];
1631     yOffset = NSMaxY([tutorialOkButton frame]);
1632   }
1634   if (linkMessage) {
1635     [container addSubview:learnMoreLink];
1636     yOffset = std::max(NSMaxY([learnMoreLink frame]), yOffset);
1637   }
1639   yOffset += kVerticalSpacing;
1641   // Adds body content.
1642   NSTextField* contentLabel = BuildLabel(
1643       contentMessage,
1644       NSMakePoint(kHorizontalSpacing, yOffset),
1645       gfx::SkColorToSRGBNSColor(profiles::kAvatarTutorialContentTextColor));
1646   [contentLabel setFrameSize:NSMakeSize(availableWidth, 0)];
1647   [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:contentLabel];
1648   [container addSubview:contentLabel];
1649   yOffset = NSMaxY([contentLabel frame]) + kSmallVerticalSpacing;
1651   // Adds title.
1652   NSTextField* titleLabel =
1653       BuildLabel(titleMessage,
1654                  NSMakePoint(kHorizontalSpacing, yOffset),
1655                  [NSColor whiteColor] /* text_color */);
1656   [titleLabel setFont:[NSFont labelFontOfSize:kTitleFontSize]];
1658   if (hasCloseButton) {
1659     base::scoped_nsobject<HoverImageButton> closeButton(
1660         [[HoverImageButton alloc] initWithFrame:NSZeroRect]);
1661     [closeButton setBordered:NO];
1663     ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
1664     NSImage* closeImage = rb->GetNativeImageNamed(IDR_CLOSE_1).ToNSImage();
1665     CGFloat closeImageWidth = [closeImage size].width;
1666     [closeButton setDefaultImage:closeImage];
1667     [closeButton setHoverImage:
1668         rb->GetNativeImageNamed(IDR_CLOSE_1_H).ToNSImage()];
1669     [closeButton setPressedImage:
1670         rb->GetNativeImageNamed(IDR_CLOSE_1_P).ToNSImage()];
1671     [closeButton setTarget:self];
1672     [closeButton setAction:@selector(dismissTutorial:)];
1673     [closeButton setFrameSize:[closeImage size]];
1674     [closeButton setFrameOrigin:NSMakePoint(
1675         kFixedMenuWidth - kHorizontalSpacing - closeImageWidth, yOffset)];
1676     [container addSubview:closeButton];
1678     [titleLabel setFrameSize:NSMakeSize(
1679         availableWidth - closeImageWidth - kHorizontalSpacing, 0)];
1680   } else {
1681     [titleLabel setFrameSize:NSMakeSize(availableWidth, 0)];
1682   }
1684   [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:titleLabel];
1685   [container addSubview:titleLabel];
1686   yOffset = NSMaxY([titleLabel frame]) + kVerticalSpacing;
1688   [container setFrameSize:NSMakeSize(kFixedMenuWidth, yOffset)];
1689   [container setFrameOrigin:NSZeroPoint];
1690   return container.autorelease();
1693 - (NSView*)createCurrentProfileView:(const AvatarMenu::Item&)item {
1694   base::scoped_nsobject<NSView> container([[NSView alloc]
1695       initWithFrame:NSZeroRect]);
1697   CGFloat xOffset = kHorizontalSpacing;
1698   CGFloat yOffset = 0;
1699   CGFloat availableTextWidth = kFixedMenuWidth - 2 * kHorizontalSpacing;
1701   // Profile options. This can be a link to the accounts view, the profile's
1702   // username for signed in users, or a "Sign in" button for local profiles.
1703   SigninManagerBase* signinManager =
1704       SigninManagerFactory::GetForProfile(
1705           browser_->profile()->GetOriginalProfile());
1706   if (!isGuestSession_ && signinManager->IsSigninAllowed()) {
1707     NSView* linksContainer =
1708         [self createCurrentProfileLinksForItem:item
1709                                           rect:NSMakeRect(xOffset, yOffset,
1710                                                           availableTextWidth,
1711                                                           0)];
1712     [container addSubview:linksContainer];
1713     yOffset = NSMaxY([linksContainer frame]);
1714   }
1716   // Profile name, centered.
1717   bool editingAllowed = !isGuestSession_ &&
1718                         !browser_->profile()->IsLegacySupervised();
1719   base::scoped_nsobject<EditableProfileNameButton> profileName(
1720       [[EditableProfileNameButton alloc]
1721           initWithFrame:NSMakeRect(xOffset,
1722                                    yOffset,
1723                                    availableTextWidth,
1724                                    kProfileButtonHeight)
1725                 profile:browser_->profile()
1726             profileName:base::SysUTF16ToNSString(
1727                             profiles::GetAvatarNameForProfile(
1728                                 browser_->profile()->GetPath()))
1729          editingAllowed:editingAllowed
1730          withController:self]);
1732   [container addSubview:profileName];
1733   yOffset = NSMaxY([profileName frame]) + 4;  // Adds a small vertical padding.
1735   // Profile icon, centered.
1736   xOffset = (kFixedMenuWidth - kLargeImageSide) / 2;
1737   base::scoped_nsobject<EditableProfilePhoto> iconView(
1738       [[EditableProfilePhoto alloc]
1739           initWithFrame:NSMakeRect(xOffset, yOffset,
1740                                    kLargeImageSide, kLargeImageSide)
1741              avatarMenu:avatarMenu_.get()
1742             profileIcon:item.icon
1743          editingAllowed:!isGuestSession_
1744          withController:self]);
1746   [container addSubview:iconView];
1747   yOffset = NSMaxY([iconView frame]);
1749   if (browser_->profile()->IsSupervised()) {
1750     base::scoped_nsobject<NSImageView> supervisedIcon(
1751         [[NSImageView alloc] initWithFrame:NSZeroRect]);
1752     int imageId = browser_->profile()->IsChild()
1753         ? IDR_ICON_PROFILES_MENU_CHILD
1754         : IDR_ICON_PROFILES_MENU_LEGACY_SUPERVISED;
1755     ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
1756     [supervisedIcon setImage:rb->GetNativeImageNamed(imageId).ToNSImage()];
1757     NSSize size = [[supervisedIcon image] size];
1758     [supervisedIcon setFrameSize:size];
1759     NSRect parentFrame = [iconView frame];
1760     [supervisedIcon setFrameOrigin:NSMakePoint(NSMaxX(parentFrame) - size.width,
1761                                                NSMinY(parentFrame))];
1762     [container addSubview:supervisedIcon];
1763   }
1765   [container setFrameSize:NSMakeSize(kFixedMenuWidth, yOffset)];
1766   return container.autorelease();
1769 - (NSView*)createCurrentProfileLinksForItem:(const AvatarMenu::Item&)item
1770                                        rect:(NSRect)rect {
1771   base::scoped_nsobject<NSView> container([[NSView alloc] initWithFrame:rect]);
1773   // Don't double-apply the left margin to the sub-views.
1774   rect.origin.x = 0;
1776   // The available links depend on the type of profile that is active.
1777   if (item.signed_in) {
1778     // Signed in profiles with no authentication errors do not have a clickable
1779     // email link.
1780     NSButton* link = nil;
1781     if (switches::IsEnableAccountConsistency()) {
1782       NSString* linkTitle = l10n_util::GetNSString(
1783           viewMode_ == profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER ?
1784               IDS_PROFILES_PROFILE_MANAGE_ACCOUNTS_BUTTON :
1785               IDS_PROFILES_PROFILE_HIDE_MANAGE_ACCOUNTS_BUTTON);
1786       SEL linkSelector =
1787           (viewMode_ == profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER) ?
1788           @selector(showAccountManagement:) : @selector(hideAccountManagement:);
1789       link = [self linkButtonWithTitle:linkTitle
1790                            frameOrigin:rect.origin
1791                                 action:linkSelector];
1792     } else {
1793       link = [self linkButtonWithTitle:base::SysUTF16ToNSString(item.username)
1794                            frameOrigin:rect.origin
1795                                 action:nil];
1796       if (HasAuthError(browser_->profile())) {
1797         [link setImage:ui::ResourceBundle::GetSharedInstance().
1798             GetNativeImageNamed(IDR_ICON_PROFILES_ACCOUNT_BUTTON_ERROR).
1799             ToNSImage()];
1800         [link setImagePosition:NSImageRight];
1801         [link setTarget:self];
1802         [link setAction:@selector(showAccountReauthenticationView:)];
1803         [link setTag:kPrimaryProfileTag];
1804         [[link cell]
1805             accessibilitySetOverrideValue:l10n_util::GetNSStringF(
1806             IDS_PROFILES_ACCOUNT_BUTTON_AUTH_ERROR_ACCESSIBLE_NAME,
1807             item.username)
1808                              forAttribute:NSAccessibilityTitleAttribute];
1809       } else {
1810         [link setEnabled:NO];
1811       }
1812     }
1813     // -linkButtonWithTitle sizeToFit's the link. We can use the height, but
1814     // need to re-stretch the width so that the link can be centered correctly
1815     // in the view.
1816     rect.size.height = [link frame].size.height;
1817     [link setAlignment:NSCenterTextAlignment];
1818     [link setFrame:rect];
1819     [container addSubview:link];
1820     [container setFrameSize:rect.size];
1821   } else {
1822     rect.size.height = kBlueButtonHeight;
1823     NSButton* signinButton = [[BlueLabelButton alloc] initWithFrame:rect];
1825     // Manually elide the button text so that the contents fit inside the bubble
1826     // This is needed because the BlueLabelButton cell resets the style on
1827     // every call to -cellSize, which prevents setting a custom lineBreakMode.
1828     NSString* elidedButtonText = base::SysUTF16ToNSString(gfx::ElideText(
1829         l10n_util::GetStringFUTF16(
1830             IDS_SYNC_START_SYNC_BUTTON_LABEL,
1831             l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME)),
1832         gfx::FontList(), rect.size.width, gfx::ELIDE_TAIL));
1834     [signinButton setTitle:elidedButtonText];
1835     [signinButton setTarget:self];
1836     [signinButton setAction:@selector(showInlineSigninPage:)];
1837     [container addSubview:signinButton];
1839     // Sign-in promo text.
1840     NSTextField* promo = BuildLabel(
1841         l10n_util::GetNSString(IDS_PROFILES_SIGNIN_PROMO),
1842         NSMakePoint(0, NSMaxY([signinButton frame]) + kVerticalSpacing),
1843         nil);
1844     [promo setFrameSize:NSMakeSize(rect.size.width, 0)];
1845     [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:promo];
1846     [container addSubview:promo];
1848     [container setFrameSize:NSMakeSize(
1849         rect.size.width,
1850         NSMaxY([promo frame]) + 4)];  // Adds a small vertical padding.
1851   }
1853   return container.autorelease();
1856 - (NSView*)createSupervisedUserDisclaimerView {
1857   base::scoped_nsobject<NSView> container(
1858       [[NSView alloc] initWithFrame:NSZeroRect]);
1860   int yOffset = 0;
1861   int availableTextWidth = kFixedMenuWidth - 2 * kHorizontalSpacing;
1863   NSTextField* disclaimer = BuildLabel(
1864       base::SysUTF16ToNSString(avatarMenu_->GetSupervisedUserInformation()),
1865       NSMakePoint(kHorizontalSpacing, yOffset), nil);
1866   [disclaimer setFrameSize:NSMakeSize(availableTextWidth, 0)];
1867   [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:disclaimer];
1868   yOffset = NSMaxY([disclaimer frame]);
1870   [container addSubview:disclaimer];
1871   [container setFrameSize:NSMakeSize(kFixedMenuWidth, yOffset)];
1872   return container.autorelease();
1875 - (NSView*)createGuestProfileView {
1876   gfx::Image guestIcon =
1877       ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed(
1878           profiles::GetPlaceholderAvatarIconResourceID());
1879   AvatarMenu::Item guestItem(std::string::npos, /* menu_index, not used */
1880                              std::string::npos, /* profile_index, not used */
1881                              guestIcon);
1882   guestItem.active = true;
1883   guestItem.name = base::SysNSStringToUTF16(
1884       l10n_util::GetNSString(IDS_PROFILES_GUEST_PROFILE_NAME));
1886   return [self createCurrentProfileView:guestItem];
1889 - (NSButton*)createOtherProfileView:(int)itemIndex {
1890   const AvatarMenu::Item& item = avatarMenu_->GetItemAt(itemIndex);
1892   NSRect rect = NSMakeRect(
1893       0, 0, kFixedMenuWidth, kBlueButtonHeight + kSmallVerticalSpacing);
1894   base::scoped_nsobject<BackgroundColorHoverButton> profileButton(
1895       [[BackgroundColorHoverButton alloc]
1896           initWithFrame:rect
1897       imageTitleSpacing:kImageTitleSpacing
1898         backgroundColor:GetDialogBackgroundColor()]);
1900   NSString* title = base::SysUTF16ToNSString(
1901       profiles::GetProfileSwitcherTextForItem(item));
1902   [profileButton setTitle:title];
1904   // Use the low-res, small default avatars in the fast user switcher, like
1905   // we do in the menu bar.
1906   gfx::Image itemIcon;
1907   bool isRectangle;
1908   AvatarMenu::GetImageForMenuButton(item.profile_path, &itemIcon, &isRectangle);
1910   [profileButton setDefaultImage:CreateProfileImage(
1911       itemIcon, kSmallImageSide).ToNSImage()];
1912   [profileButton setImagePosition:NSImageLeft];
1913   [profileButton setAlignment:NSLeftTextAlignment];
1914   [profileButton setBordered:NO];
1915   [profileButton setTag:itemIndex];
1916   [profileButton setTarget:self];
1917   [profileButton setAction:@selector(switchToProfile:)];
1919   NSSize textSize = [[profileButton title] sizeWithAttributes:@{
1920     NSFontAttributeName : [profileButton font]
1921   }];
1923   CGFloat availableWidth = rect.size.width - kSmallImageSide -
1924                            kImageTitleSpacing - kHorizontalSpacing;
1926   if (std::ceil(textSize.width) > availableWidth)
1927     [profileButton setToolTip:[profileButton title]];
1929   return profileButton.autorelease();
1932 - (NSView*)createOptionsViewWithRect:(NSRect)rect
1933                          displayLock:(BOOL)displayLock {
1934   NSRect viewRect = NSMakeRect(0, 0,
1935                                rect.size.width,
1936                                kBlueButtonHeight + kSmallVerticalSpacing);
1937   base::scoped_nsobject<NSView> container([[NSView alloc] initWithFrame:rect]);
1939   if (displayLock) {
1940     NSButton* lockButton =
1941         [self hoverButtonWithRect:viewRect
1942                              text:l10n_util::GetNSString(
1943                                   IDS_PROFILES_PROFILE_SIGNOUT_BUTTON)
1944                   imageResourceId:IDR_ICON_PROFILES_MENU_LOCK
1945                            action:@selector(lockProfile:)];
1946     [container addSubview:lockButton];
1947     viewRect.origin.y = NSMaxY([lockButton frame]);
1949     NSBox* separator = [self horizontalSeparatorWithFrame:viewRect];
1950     [container addSubview:separator];
1951     viewRect.origin.y = NSMaxY([separator frame]);
1952   }
1954   if ([self shouldShowGoIncognito]) {
1955     NSButton* goIncognitoButton =
1956         [self hoverButtonWithRect:viewRect
1957                              text:l10n_util::GetNSString(
1958                                   IDS_PROFILES_GO_INCOGNITO_BUTTON)
1959                   imageResourceId:IDR_ICON_PROFILES_MENU_INCOGNITO
1960                            action:@selector(goIncognito:)];
1961     viewRect.origin.y = NSMaxY([goIncognitoButton frame]);
1962     [container addSubview:goIncognitoButton];
1964     NSBox* separator = [self horizontalSeparatorWithFrame:viewRect];
1965     [container addSubview:separator];
1966     viewRect.origin.y = NSMaxY([separator frame]);
1967   }
1969   NSString* text = isGuestSession_ ?
1970       l10n_util::GetNSString(IDS_PROFILES_EXIT_GUEST) :
1971       l10n_util::GetNSString(IDS_PROFILES_SWITCH_USERS_BUTTON);
1972   NSButton* switchUsersButton =
1973       [self hoverButtonWithRect:viewRect
1974                            text:text
1975                 imageResourceId:IDR_ICON_PROFILES_MENU_AVATAR
1976                          action:isGuestSession_? @selector(exitGuest:) :
1977                                                  @selector(showUserManager:)];
1978   viewRect.origin.y = NSMaxY([switchUsersButton frame]);
1979   [container addSubview:switchUsersButton];
1981   [container setFrameSize:NSMakeSize(rect.size.width, viewRect.origin.y)];
1982   return container.autorelease();
1985 - (NSView*)createCurrentProfileAccountsView:(NSRect)rect {
1986   const CGFloat kAccountButtonHeight = 34;
1988   const AvatarMenu::Item& item =
1989       avatarMenu_->GetItemAt(avatarMenu_->GetActiveProfileIndex());
1990   DCHECK(item.signed_in);
1992   NSColor* backgroundColor = gfx::SkColorToCalibratedNSColor(
1993       profiles::kAvatarBubbleAccountsBackgroundColor);
1994   base::scoped_nsobject<NSView> container([[BackgroundColorView alloc]
1995       initWithFrame:rect
1996           withColor:backgroundColor]);
1998   rect.origin.y = 0;
1999   if (!browser_->profile()->IsSupervised()) {
2000     // Manually elide the button text so the contents fit inside the bubble.
2001     // This is needed because the BlueLabelButton cell resets the style on
2002     // every call to -cellSize, which prevents setting a custom lineBreakMode.
2003     NSString* elidedButtonText = base::SysUTF16ToNSString(gfx::ElideText(
2004         l10n_util::GetStringFUTF16(
2005             IDS_PROFILES_PROFILE_ADD_ACCOUNT_BUTTON, item.name),
2006         gfx::FontList(), rect.size.width, gfx::ELIDE_TAIL));
2008     NSButton* addAccountsButton =
2009         [self linkButtonWithTitle:elidedButtonText
2010                       frameOrigin:NSMakePoint(
2011             kHorizontalSpacing, kSmallVerticalSpacing)
2012                            action:@selector(addAccount:)];
2013     [container addSubview:addAccountsButton];
2014     rect.origin.y += kAccountButtonHeight;
2015   }
2017   NSView* accountEmails = [self createAccountsListWithRect:NSMakeRect(
2018       0, rect.origin.y, rect.size.width, kAccountButtonHeight)];
2019   [container addSubview:accountEmails];
2021   [container setFrameSize:NSMakeSize(rect.size.width,
2022                                      NSMaxY([accountEmails frame]))];
2023   return container.autorelease();
2026 - (NSView*)createAccountsListWithRect:(NSRect)rect {
2027   base::scoped_nsobject<NSView> container([[NSView alloc] initWithFrame:rect]);
2028   currentProfileAccounts_.clear();
2030   Profile* profile = browser_->profile();
2031   std::string primaryAccount =
2032       SigninManagerFactory::GetForProfile(profile)->GetAuthenticatedAccountId();
2033   DCHECK(!primaryAccount.empty());
2034   std::vector<std::string>accounts =
2035       profiles::GetSecondaryAccountsForProfile(profile, primaryAccount);
2037   // If there is an account with an authentication error, it needs to be
2038   // badged with a warning icon.
2039   std::string errorAccountId = GetAuthErrorAccountId(profile);
2041   rect.origin.y = 0;
2042   for (size_t i = 0; i < accounts.size(); ++i) {
2043     // Save the original email address, as the button text could be elided.
2044     currentProfileAccounts_[i] = accounts[i];
2045     NSButton* accountButton =
2046         [self accountButtonWithRect:rect
2047                          accountId:accounts[i]
2048                                 tag:i
2049                      reauthRequired:errorAccountId == accounts[i]];
2050     [container addSubview:accountButton];
2051     rect.origin.y = NSMaxY([accountButton frame]);
2052   }
2054   // The primary account should always be listed first.
2055   NSButton* accountButton =
2056       [self accountButtonWithRect:rect
2057                         accountId:primaryAccount
2058                               tag:kPrimaryProfileTag
2059                    reauthRequired:errorAccountId == primaryAccount];
2060   [container addSubview:accountButton];
2061   [container setFrameSize:NSMakeSize(NSWidth([container frame]),
2062                                      NSMaxY([accountButton frame]))];
2063   return container.autorelease();
2066 - (NSView*)buildGaiaEmbeddedView {
2067   base::scoped_nsobject<NSView> container(
2068       [[NSView alloc] initWithFrame:NSZeroRect]);
2069   CGFloat yOffset = 0;
2071   GURL url;
2072   int messageId = -1;
2073   switch (viewMode_) {
2074     case profiles::BUBBLE_VIEW_MODE_GAIA_SIGNIN:
2075       url = signin::GetPromoURL(signin_metrics::SOURCE_AVATAR_BUBBLE_SIGN_IN,
2076                                 false /* auto_close */,
2077                                 true /* is_constrained */);
2078       messageId = IDS_PROFILES_GAIA_SIGNIN_TITLE;
2079       break;
2080     case profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT:
2081       url = signin::GetPromoURL(
2082           signin_metrics::SOURCE_AVATAR_BUBBLE_ADD_ACCOUNT,
2083           false /* auto_close */,
2084           true /* is_constrained */);
2085       messageId = IDS_PROFILES_GAIA_ADD_ACCOUNT_TITLE;
2086       break;
2087     case profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH:
2088       DCHECK(HasAuthError(browser_->profile()));
2089       url = signin::GetReauthURL(
2090           browser_->profile(), GetAuthErrorAccountId(browser_->profile()));
2091       messageId = IDS_PROFILES_GAIA_REAUTH_TITLE;
2092       break;
2093     default:
2094       NOTREACHED() << "Called with invalid mode=" << viewMode_;
2095       break;
2096   }
2098   webContents_.reset(content::WebContents::Create(
2099       content::WebContents::CreateParams(browser_->profile())));
2101   webContentsDelegate_.reset(new GaiaWebContentsDelegate());
2102   webContents_->SetDelegate(webContentsDelegate_.get());
2103   webContents_->GetController().LoadURL(url,
2104                                         content::Referrer(),
2105                                         ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
2106                                         std::string());
2107   NSView* webview = webContents_->GetNativeView();
2108   [webview setFrameSize:NSMakeSize(kFixedGaiaViewWidth, kFixedGaiaViewHeight)];
2109   [container addSubview:webview];
2110   content::RenderWidgetHostView* rwhv = webContents_->GetRenderWidgetHostView();
2111   if (rwhv)
2112     rwhv->SetBackgroundColor(profiles::kAvatarBubbleGaiaBackgroundColor);
2113   yOffset = NSMaxY([webview frame]);
2115   // Adds the title card.
2116   NSBox* separator = [self horizontalSeparatorWithFrame:
2117       NSMakeRect(0, yOffset, kFixedGaiaViewWidth, 0)];
2118   [container addSubview:separator];
2119   yOffset = NSMaxY([separator frame]) + kVerticalSpacing;
2121   NSView* titleView = BuildTitleCard(
2122       NSMakeRect(0, yOffset, kFixedGaiaViewWidth, 0),
2123       l10n_util::GetStringUTF16(messageId),
2124       self /* backButtonTarget*/,
2125       @selector(navigateBackFromSigninPage:) /* backButtonAction */);
2126   [container addSubview:titleView];
2127   yOffset = NSMaxY([titleView frame]);
2129   [container setFrameSize:NSMakeSize(kFixedGaiaViewWidth, yOffset)];
2130   return container.autorelease();
2133 - (NSView*)buildAccountRemovalView {
2134   DCHECK(!accountIdToRemove_.empty());
2136   base::scoped_nsobject<NSView> container(
2137       [[NSView alloc] initWithFrame:NSZeroRect]);
2138   CGFloat availableWidth =
2139       kFixedAccountRemovalViewWidth - 2 * kHorizontalSpacing;
2140   CGFloat yOffset = kVerticalSpacing;
2142   const std::string& primaryAccount = SigninManagerFactory::GetForProfile(
2143       browser_->profile())->GetAuthenticatedAccountId();
2144   bool isPrimaryAccount = primaryAccount == accountIdToRemove_;
2146   // Adds "remove account" button at the bottom if needed.
2147   if (!isPrimaryAccount) {
2148     base::scoped_nsobject<NSButton> removeAccountButton(
2149         [[BlueLabelButton alloc] initWithFrame:NSZeroRect]);
2150     [removeAccountButton setTitle:l10n_util::GetNSString(
2151         IDS_PROFILES_ACCOUNT_REMOVAL_BUTTON)];
2152     [removeAccountButton setTarget:self];
2153     [removeAccountButton setAction:@selector(removeAccount:)];
2154     [removeAccountButton sizeToFit];
2155     [removeAccountButton setAlignment:NSCenterTextAlignment];
2156     CGFloat xOffset = (kFixedAccountRemovalViewWidth -
2157         NSWidth([removeAccountButton frame])) / 2;
2158     [removeAccountButton setFrameOrigin:NSMakePoint(xOffset, yOffset)];
2159     [container addSubview:removeAccountButton];
2161     yOffset = NSMaxY([removeAccountButton frame]) + kVerticalSpacing;
2162   }
2164   NSView* contentView;
2165   NSPoint contentFrameOrigin = NSMakePoint(kHorizontalSpacing, yOffset);
2166   if (isPrimaryAccount) {
2167     std::string email = signin_ui_util::GetDisplayEmail(browser_->profile(),
2168                                                         accountIdToRemove_);
2169     std::vector<size_t> offsets;
2170     NSString* contentStr = l10n_util::GetNSStringF(
2171         IDS_PROFILES_PRIMARY_ACCOUNT_REMOVAL_TEXT,
2172         base::UTF8ToUTF16(email), base::string16(), &offsets);
2173     NSString* linkStr = l10n_util::GetNSString(IDS_PROFILES_SETTINGS_LINK);
2174     contentView = BuildFixedWidthTextViewWithLink(self, contentStr, linkStr,
2175         offsets[1], contentFrameOrigin, availableWidth);
2176   } else {
2177     NSString* contentStr =
2178         l10n_util::GetNSString(IDS_PROFILES_ACCOUNT_REMOVAL_TEXT);
2179     NSTextField* contentLabel = BuildLabel(contentStr, contentFrameOrigin, nil);
2180     [contentLabel setFrameSize:NSMakeSize(availableWidth, 0)];
2181     [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:contentLabel];
2182     contentView = contentLabel;
2183   }
2184   [container addSubview:contentView];
2185   yOffset = NSMaxY([contentView frame]) + kVerticalSpacing;
2187   // Adds the title card.
2188   NSBox* separator = [self horizontalSeparatorWithFrame:
2189       NSMakeRect(0, yOffset, kFixedAccountRemovalViewWidth, 0)];
2190   [container addSubview:separator];
2191   yOffset = NSMaxY([separator frame]) + kVerticalSpacing;
2193   NSView* titleView = BuildTitleCard(
2194       NSMakeRect(0, yOffset, kFixedAccountRemovalViewWidth,0),
2195       l10n_util::GetStringUTF16(IDS_PROFILES_ACCOUNT_REMOVAL_TITLE),
2196       self /* backButtonTarget*/,
2197       @selector(showAccountManagement:) /* backButtonAction */);
2198   [container addSubview:titleView];
2199   yOffset = NSMaxY([titleView frame]);
2201   [container setFrameSize:NSMakeSize(kFixedAccountRemovalViewWidth, yOffset)];
2202   return container.autorelease();
2205 - (NSView*)buildSwitchUserView {
2206   ProfileMetrics::LogProfileNewAvatarMenuNotYou(
2207       ProfileMetrics::PROFILE_AVATAR_MENU_NOT_YOU_VIEW);
2208   base::scoped_nsobject<NSView> container(
2209       [[NSView alloc] initWithFrame:NSZeroRect]);
2210   CGFloat availableWidth =
2211       kFixedSwitchUserViewWidth - 2 * kHorizontalSpacing;
2212   CGFloat yOffset = 0;
2213   NSRect viewRect = NSMakeRect(0, yOffset,
2214                                kFixedSwitchUserViewWidth,
2215                                kBlueButtonHeight + kSmallVerticalSpacing);
2217   const AvatarMenu::Item& avatarItem =
2218       avatarMenu_->GetItemAt(avatarMenu_->GetActiveProfileIndex());
2220   // Adds "Disconnect your Google Account" button at the bottom.
2221   NSButton* disconnectButton =
2222       [self hoverButtonWithRect:viewRect
2223                            text:l10n_util::GetNSString(
2224                                     IDS_PROFILES_DISCONNECT_BUTTON)
2225                 imageResourceId:IDR_ICON_PROFILES_MENU_DISCONNECT
2226                          action:@selector(disconnectProfile:)];
2227   [container addSubview:disconnectButton];
2228   yOffset = NSMaxY([disconnectButton frame]);
2230   NSBox* separator = [self horizontalSeparatorWithFrame:
2231       NSMakeRect(0, yOffset, kFixedSwitchUserViewWidth, 0)];
2232   [container addSubview:separator];
2233   yOffset = NSMaxY([separator frame]);
2235   // Adds "Add person" button.
2236   viewRect.origin.y = yOffset;
2237   NSButton* addPersonButton =
2238       [self hoverButtonWithRect:viewRect
2239                            text:l10n_util::GetNSString(
2240                                     IDS_PROFILES_ADD_PERSON_BUTTON)
2241                 imageResourceId:IDR_ICON_PROFILES_MENU_AVATAR
2242                          action:@selector(showUserManager:)];
2243   [container addSubview:addPersonButton];
2244   yOffset = NSMaxY([addPersonButton frame]);
2246   separator = [self horizontalSeparatorWithFrame:
2247       NSMakeRect(0, yOffset, kFixedSwitchUserViewWidth, 0)];
2248   [container addSubview:separator];
2249   yOffset = NSMaxY([separator frame]);
2251   // Adds the content text.
2252   base::string16 elidedName(gfx::ElideText(
2253       avatarItem.name, gfx::FontList(), availableWidth, gfx::ELIDE_TAIL));
2254   NSTextField* contentLabel = BuildLabel(
2255       l10n_util::GetNSStringF(IDS_PROFILES_NOT_YOU_CONTENT_TEXT, elidedName),
2256       NSMakePoint(kHorizontalSpacing, yOffset + kVerticalSpacing),
2257       nil);
2258   [contentLabel setFrameSize:NSMakeSize(availableWidth, 0)];
2259   [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:contentLabel];
2260   [container addSubview:contentLabel];
2261   yOffset = NSMaxY([contentLabel frame]) + kVerticalSpacing;
2263   // Adds the title card.
2264   separator = [self horizontalSeparatorWithFrame:
2265       NSMakeRect(0, yOffset, kFixedSwitchUserViewWidth, 0)];
2266   [container addSubview:separator];
2267   yOffset = NSMaxY([separator frame]) + kVerticalSpacing;
2269   NSView* titleView = BuildTitleCard(
2270       NSMakeRect(0, yOffset, kFixedSwitchUserViewWidth,0),
2271       l10n_util::GetStringFUTF16(IDS_PROFILES_NOT_YOU, avatarItem.name),
2272       self /* backButtonTarget*/,
2273       @selector(navigateBackFromSwitchUserView:) /* backButtonAction */);
2274   [container addSubview:titleView];
2275   yOffset = NSMaxY([titleView frame]);
2277   [container setFrameSize:NSMakeSize(kFixedSwitchUserViewWidth, yOffset)];
2278   return container.autorelease();
2281 // Called when clicked on the settings link.
2282 - (BOOL)textView:(NSTextView*)textView
2283    clickedOnLink:(id)link
2284          atIndex:(NSUInteger)charIndex {
2285   chrome::ShowSettings(browser_);
2286   return YES;
2289 - (NSButton*)hoverButtonWithRect:(NSRect)rect
2290                             text:(NSString*)text
2291                  imageResourceId:(int)imageResourceId
2292                           action:(SEL)action {
2293   base::scoped_nsobject<BackgroundColorHoverButton> button(
2294       [[BackgroundColorHoverButton alloc]
2295           initWithFrame:rect
2296       imageTitleSpacing:kImageTitleSpacing
2297         backgroundColor:GetDialogBackgroundColor()]);
2299   [button setTitle:text];
2300   ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
2301   NSImage* image = rb->GetNativeImageNamed(imageResourceId).ToNSImage();
2302   [button setDefaultImage:image];
2303   [button setHoverImage:image];
2304   [button setPressedImage:image];
2305   [button setImagePosition:NSImageLeft];
2306   [button setAlignment:NSLeftTextAlignment];
2307   [button setBordered:NO];
2308   [button setTarget:self];
2309   [button setAction:action];
2311   return button.autorelease();
2314 - (NSButton*)linkButtonWithTitle:(NSString*)title
2315                      frameOrigin:(NSPoint)frameOrigin
2316                           action:(SEL)action {
2317   base::scoped_nsobject<NSButton> link(
2318       [[HyperlinkButtonCell buttonWithString:title] retain]);
2320   [[link cell] setShouldUnderline:NO];
2321   [[link cell] setTextColor:gfx::SkColorToCalibratedNSColor(
2322       chrome_style::GetLinkColor())];
2323   [link setTitle:title];
2324   [link setBordered:NO];
2325   [link setFont:[NSFont labelFontOfSize:kTextFontSize]];
2326   [link setTarget:self];
2327   [link setAction:action];
2328   [link setFrameOrigin:frameOrigin];
2329   [link sizeToFit];
2331   return link.autorelease();
2334 - (NSButton*)accountButtonWithRect:(NSRect)rect
2335                          accountId:(const std::string&)accountId
2336                                tag:(int)tag
2337                     reauthRequired:(BOOL)reauthRequired {
2338   // Get display email address for account.
2339   std::string email = signin_ui_util::GetDisplayEmail(browser_->profile(),
2340                                                       accountId);
2342   ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
2343   NSImage* deleteImage = rb->GetNativeImageNamed(IDR_CLOSE_1).ToNSImage();
2344   CGFloat deleteImageWidth = [deleteImage size].width;
2345   NSImage* warningImage = reauthRequired ? rb->GetNativeImageNamed(
2346       IDR_ICON_PROFILES_ACCOUNT_BUTTON_ERROR).ToNSImage() : nil;
2347   CGFloat warningImageWidth = [warningImage size].width;
2349   CGFloat availableTextWidth = rect.size.width - kHorizontalSpacing -
2350       warningImageWidth - deleteImageWidth;
2351   if (warningImage)
2352     availableTextWidth -= kHorizontalSpacing;
2354   NSColor* backgroundColor = gfx::SkColorToCalibratedNSColor(
2355       profiles::kAvatarBubbleAccountsBackgroundColor);
2356   base::scoped_nsobject<BackgroundColorHoverButton> button(
2357       [[BackgroundColorHoverButton alloc] initWithFrame:rect
2358                                       imageTitleSpacing:0
2359                                         backgroundColor:backgroundColor]);
2360   [button setTitle:ElideEmail(email, availableTextWidth)];
2361   [button setAlignment:NSLeftTextAlignment];
2362   [button setBordered:NO];
2363   if (reauthRequired) {
2364     [button setDefaultImage:warningImage];
2365     [button setImagePosition:NSImageLeft];
2366     [button setTarget:self];
2367     [button setAction:@selector(showAccountReauthenticationView:)];
2368     [button setTag:tag];
2369   }
2371   // Delete button.
2372   if (!browser_->profile()->IsSupervised()) {
2373     NSRect buttonRect;
2374     NSDivideRect(rect, &buttonRect, &rect,
2375         deleteImageWidth + kHorizontalSpacing, NSMaxXEdge);
2376     buttonRect.origin.y = 0;
2378     base::scoped_nsobject<HoverImageButton> deleteButton(
2379         [[HoverImageButton alloc] initWithFrame:buttonRect]);
2380     [deleteButton setBordered:NO];
2381     [deleteButton setDefaultImage:deleteImage];
2382     [deleteButton setHoverImage:rb->GetNativeImageNamed(
2383         IDR_CLOSE_1_H).ToNSImage()];
2384     [deleteButton setPressedImage:rb->GetNativeImageNamed(
2385         IDR_CLOSE_1_P).ToNSImage()];
2386     [deleteButton setTarget:self];
2387     [deleteButton setAction:@selector(showAccountRemovalView:)];
2388     [deleteButton setTag:tag];
2390     [button addSubview:deleteButton];
2391   }
2393   return button.autorelease();
2396 - (void)postActionPerformed:(ProfileMetrics::ProfileDesktopMenu)action {
2397   ProfileMetrics::LogProfileDesktopMenu(action, serviceType_);
2398   serviceType_ = signin::GAIA_SERVICE_TYPE_NONE;
2401 - (bool)shouldShowGoIncognito {
2402   bool incognitoAvailable =
2403       IncognitoModePrefs::GetAvailability(browser_->profile()->GetPrefs()) !=
2404           IncognitoModePrefs::DISABLED;
2405   return incognitoAvailable && !browser_->profile()->IsGuestSession();
2408 @end