7198d96d9d1d6146d5026bdfd79803edcfec0e48
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / cocoa / profiles / profile_chooser_controller.mm
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.
4
5 #import <Cocoa/Cocoa.h>
6
7 #import "chrome/browser/ui/cocoa/profiles/profile_chooser_controller.h"
8
9 #include "base/mac/bundle_locations.h"
10 #include "base/prefs/pref_service.h"
11 #include "base/strings/sys_string_conversions.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "chrome/browser/browser_process.h"
14 #include "chrome/browser/chrome_notification_types.h"
15 #include "chrome/browser/lifetime/application_lifetime.h"
16 #include "chrome/browser/prefs/incognito_mode_prefs.h"
17 #include "chrome/browser/profiles/avatar_menu.h"
18 #include "chrome/browser/profiles/avatar_menu_observer.h"
19 #include "chrome/browser/profiles/profile_avatar_icon_util.h"
20 #include "chrome/browser/profiles/profile_info_cache.h"
21 #include "chrome/browser/profiles/profile_manager.h"
22 #include "chrome/browser/profiles/profile_metrics.h"
23 #include "chrome/browser/profiles/profile_window.h"
24 #include "chrome/browser/profiles/profiles_state.h"
25 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
26 #include "chrome/browser/signin/signin_header_helper.h"
27 #include "chrome/browser/signin/signin_manager_factory.h"
28 #include "chrome/browser/signin/signin_promo.h"
29 #include "chrome/browser/signin/signin_ui_util.h"
30 #include "chrome/browser/ui/browser.h"
31 #include "chrome/browser/ui/browser_commands.h"
32 #include "chrome/browser/ui/browser_dialogs.h"
33 #include "chrome/browser/ui/browser_window.h"
34 #include "chrome/browser/ui/chrome_pages.h"
35 #include "chrome/browser/ui/chrome_style.h"
36 #include "chrome/browser/ui/webui/signin/login_ui_service.h"
37 #include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
38 #import "chrome/browser/ui/cocoa/hyperlink_text_view.h"
39 #import "chrome/browser/ui/cocoa/info_bubble_view.h"
40 #import "chrome/browser/ui/cocoa/info_bubble_window.h"
41 #import "chrome/browser/ui/cocoa/profiles/user_manager_mac.h"
42 #include "chrome/browser/ui/singleton_tabs.h"
43 #include "chrome/common/pref_names.h"
44 #include "chrome/common/url_constants.h"
45 #include "components/signin/core/common/profile_management_switches.h"
46 #include "components/signin/core/browser/mutable_profile_oauth2_token_service.h"
47 #include "components/signin/core/browser/profile_oauth2_token_service.h"
48 #include "components/signin/core/browser/signin_manager.h"
49 #include "content/public/browser/notification_service.h"
50 #include "content/public/browser/web_contents.h"
51 #include "google_apis/gaia/oauth2_token_service.h"
52 #include "grit/chromium_strings.h"
53 #include "grit/generated_resources.h"
54 #include "grit/theme_resources.h"
55 #include "skia/ext/skia_utils_mac.h"
56 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h"
57 #import "ui/base/cocoa/cocoa_base_utils.h"
58 #import "ui/base/cocoa/controls/blue_label_button.h"
59 #import "ui/base/cocoa/controls/hyperlink_button_cell.h"
60 #import "ui/base/cocoa/hover_image_button.h"
61 #include "ui/base/cocoa/window_size_constants.h"
62 #include "ui/base/l10n/l10n_util.h"
63 #include "ui/base/l10n/l10n_util_mac.h"
64 #include "ui/base/resource/resource_bundle.h"
65 #include "ui/gfx/image/image.h"
66 #include "ui/gfx/text_elider.h"
67 #include "ui/native_theme/common_theme.h"
68 #include "ui/native_theme/native_theme.h"
69
70 namespace {
71
72 // Constants taken from the Windows/Views implementation at:
73 // chrome/browser/ui/views/profile_chooser_view.cc
74 const int kLargeImageSide = 88;
75 const int kSmallImageSide = 32;
76 const CGFloat kFixedMenuWidth = 250;
77
78 const CGFloat kVerticalSpacing = 16.0;
79 const CGFloat kSmallVerticalSpacing = 10.0;
80 const CGFloat kHorizontalSpacing = 16.0;
81 const CGFloat kTitleFontSize = 15.0;
82 const CGFloat kTextFontSize = 12.0;
83 const CGFloat kProfileButtonHeight = 30;
84 const int kBezelThickness = 3;  // Width of the bezel on an NSButton.
85 const int kImageTitleSpacing = 10;
86 const int kBlueButtonHeight = 30;
87
88 // Fixed size for embedded sign in pages as defined in Gaia.
89 const CGFloat kFixedGaiaViewWidth = 360;
90 const CGFloat kFixedGaiaViewHeight = 440;
91
92 // Fixed size for the account removal view.
93 const CGFloat kFixedAccountRemovalViewWidth = 280;
94
95 // Fixed size for the switch user view.
96 const int kFixedSwitchUserViewWidth = 280;
97
98 // The tag number for the primary account.
99 const int kPrimaryProfileTag = -1;
100
101 gfx::Image CreateProfileImage(const gfx::Image& icon, int imageSize) {
102   return profiles::GetSizedAvatarIcon(
103       icon, true /* image is a square */, imageSize, imageSize);
104 }
105
106 // Updates the window size and position.
107 void SetWindowSize(NSWindow* window, NSSize size) {
108   NSRect frame = [window frame];
109   frame.origin.x += frame.size.width - size.width;
110   frame.origin.y += frame.size.height - size.height;
111   frame.size = size;
112   [window setFrame:frame display:YES];
113 }
114
115 NSString* ElideEmail(const std::string& email, CGFloat width) {
116   const base::string16 elidedEmail = gfx::ElideText(
117       base::UTF8ToUTF16(email), gfx::FontList(), width, gfx::ELIDE_EMAIL);
118   return base::SysUTF16ToNSString(elidedEmail);
119 }
120
121 NSString* ElideMessage(const base::string16& message, CGFloat width) {
122   return base::SysUTF16ToNSString(
123       gfx::ElideText(message, gfx::FontList(), width, gfx::ELIDE_TAIL));
124 }
125
126 // Builds a label with the given |title| anchored at |frame_origin|. Sets the
127 // text color to |text_color| if not null.
128 NSTextField* BuildLabel(NSString* title,
129                         NSPoint frame_origin,
130                         NSColor* text_color) {
131   base::scoped_nsobject<NSTextField> label(
132       [[NSTextField alloc] initWithFrame:NSZeroRect]);
133   [label setStringValue:title];
134   [label setEditable:NO];
135   [label setAlignment:NSLeftTextAlignment];
136   [label setBezeled:NO];
137   [label setFont:[NSFont labelFontOfSize:kTextFontSize]];
138   [label setDrawsBackground:NO];
139   [label setFrameOrigin:frame_origin];
140   [label sizeToFit];
141
142   if (text_color)
143     [[label cell] setTextColor:text_color];
144
145   return label.autorelease();
146 }
147
148 // Builds an NSTextView that has the contents set to the specified |message|,
149 // with a non-underlined |link| inserted at |link_offset|. The view is anchored
150 // at the specified |frame_origin| and has a fixed |frame_width|.
151 NSTextView* BuildFixedWidthTextViewWithLink(
152     id<NSTextViewDelegate> delegate,
153     NSString* message,
154     NSString* link,
155     int link_offset,
156     NSPoint frame_origin,
157     CGFloat frame_width) {
158   base::scoped_nsobject<HyperlinkTextView> text_view(
159       [[HyperlinkTextView alloc] initWithFrame:NSZeroRect]);
160   NSColor* link_color = gfx::SkColorToCalibratedNSColor(
161       chrome_style::GetLinkColor());
162   // Adds a padding row at the bottom, because |boundingRectWithSize| below cuts
163   // off the last row sometimes.
164   [text_view setMessageAndLink:[NSString stringWithFormat:@"%@\n", message]
165                       withLink:link
166                       atOffset:link_offset
167                           font:[NSFont labelFontOfSize:kTextFontSize]
168                   messageColor:[NSColor blackColor]
169                      linkColor:link_color];
170
171   // Removes the underlining from the link.
172   [text_view setLinkTextAttributes:nil];
173   NSTextStorage* text_storage = [text_view textStorage];
174   NSRange link_range = NSMakeRange(link_offset, [link length]);
175   [text_storage addAttribute:NSUnderlineStyleAttributeName
176                        value:[NSNumber numberWithInt:NSUnderlineStyleNone]
177                        range:link_range];
178
179   NSRect frame = [[text_view attributedString]
180       boundingRectWithSize:NSMakeSize(frame_width, 0)
181                    options:NSStringDrawingUsesLineFragmentOrigin];
182   frame.origin = frame_origin;
183   [text_view setFrame:frame];
184   [text_view setDelegate:delegate];
185   return text_view.autorelease();
186 }
187
188 // Returns the native dialog background color.
189 NSColor* GetDialogBackgroundColor() {
190   return gfx::SkColorToCalibratedNSColor(
191       ui::NativeTheme::instance()->GetSystemColor(
192           ui::NativeTheme::kColorId_DialogBackground));
193 }
194
195 // Builds a title card with one back button right aligned and one label center
196 // aligned.
197 NSView* BuildTitleCard(NSRect frame_rect,
198                        const base::string16& message,
199                        id back_button_target,
200                        SEL back_button_action) {
201   base::scoped_nsobject<NSView> container(
202       [[NSView alloc] initWithFrame:frame_rect]);
203
204   base::scoped_nsobject<HoverImageButton> button(
205       [[HoverImageButton alloc] initWithFrame:frame_rect]);
206   [button setBordered:NO];
207   ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
208   [button setDefaultImage:rb->GetNativeImageNamed(IDR_BACK).ToNSImage()];
209   [button setHoverImage:rb->GetNativeImageNamed(IDR_BACK_H).ToNSImage()];
210   [button setPressedImage:rb->GetNativeImageNamed(IDR_BACK_P).ToNSImage()];
211   [button setTarget:back_button_target];
212   [button setAction:back_button_action];
213   [button setFrameSize:NSMakeSize(kProfileButtonHeight, kProfileButtonHeight)];
214   [button setFrameOrigin:NSMakePoint(kHorizontalSpacing, 0)];
215
216   CGFloat max_label_width = frame_rect.size.width -
217       (kHorizontalSpacing * 2 + kProfileButtonHeight) * 2;
218   NSTextField* title_label = BuildLabel(
219       ElideMessage(message, max_label_width),
220       NSZeroPoint, nil);
221   [title_label setAlignment:NSCenterTextAlignment];
222   [title_label setFont:[NSFont labelFontOfSize:kTitleFontSize]];
223   [title_label sizeToFit];
224   CGFloat x_offset = (frame_rect.size.width - NSWidth([title_label frame])) / 2;
225   CGFloat y_offset =
226       (NSHeight([button frame]) - NSHeight([title_label frame])) / 2;
227   [title_label setFrameOrigin:NSMakePoint(x_offset, y_offset)];
228
229   [container addSubview:button];
230   [container addSubview:title_label];
231   CGFloat height = std::max(NSMaxY([title_label frame]),
232                             NSMaxY([button frame])) + kSmallVerticalSpacing;
233   [container setFrameSize:NSMakeSize(NSWidth([container frame]), height)];
234
235   return container.autorelease();
236 }
237
238 bool HasAuthError(Profile* profile) {
239   const SigninErrorController* error_controller =
240       profiles::GetSigninErrorController(profile);
241   return error_controller && error_controller->HasError();
242 }
243
244 }  // namespace
245
246 // Class that listens to changes to the OAuth2Tokens for the active profile,
247 // changes to the avatar menu model or browser close notifications.
248 class ActiveProfileObserverBridge : public AvatarMenuObserver,
249                                     public content::NotificationObserver,
250                                     public OAuth2TokenService::Observer {
251  public:
252   ActiveProfileObserverBridge(ProfileChooserController* controller,
253                               Browser* browser)
254       : controller_(controller),
255         browser_(browser),
256         token_observer_registered_(false) {
257     registrar_.Add(this, chrome::NOTIFICATION_BROWSER_CLOSING,
258                    content::NotificationService::AllSources());
259     if (!browser_->profile()->IsGuestSession())
260       AddTokenServiceObserver();
261   }
262
263   virtual ~ActiveProfileObserverBridge() {
264     RemoveTokenServiceObserver();
265   }
266
267  private:
268   void AddTokenServiceObserver() {
269     ProfileOAuth2TokenService* oauth2_token_service =
270         ProfileOAuth2TokenServiceFactory::GetForProfile(browser_->profile());
271     DCHECK(oauth2_token_service);
272     oauth2_token_service->AddObserver(this);
273     token_observer_registered_ = true;
274   }
275
276   void RemoveTokenServiceObserver() {
277     if (!token_observer_registered_)
278       return;
279     DCHECK(browser_->profile());
280     ProfileOAuth2TokenService* oauth2_token_service =
281         ProfileOAuth2TokenServiceFactory::GetForProfile(browser_->profile());
282     DCHECK(oauth2_token_service);
283     oauth2_token_service->RemoveObserver(this);
284     token_observer_registered_ = false;
285   }
286
287   // OAuth2TokenService::Observer:
288   virtual void OnRefreshTokenAvailable(const std::string& account_id) OVERRIDE {
289     // Tokens can only be added by adding an account through the inline flow,
290     // which is started from the account management view. Refresh it to show the
291     // update.
292     profiles::BubbleViewMode viewMode = [controller_ viewMode];
293     if (viewMode == profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT ||
294         viewMode == profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT ||
295         viewMode == profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH) {
296       [controller_ initMenuContentsWithView:
297           switches::IsEnableAccountConsistency() ?
298               profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT :
299               profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER];
300     }
301   }
302
303   virtual void OnRefreshTokenRevoked(const std::string& account_id) OVERRIDE {
304     // Tokens can only be removed from the account management view. Refresh it
305     // to show the update.
306     if ([controller_ viewMode] == profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT)
307       [controller_ initMenuContentsWithView:
308           profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT];
309   }
310
311   // AvatarMenuObserver:
312   virtual void OnAvatarMenuChanged(AvatarMenu* avatar_menu) OVERRIDE {
313     // Do not refresh the avatar menu if the user is on a signin related view.
314     profiles::BubbleViewMode viewMode = [controller_ viewMode];
315     if (viewMode == profiles::BUBBLE_VIEW_MODE_GAIA_SIGNIN ||
316         viewMode == profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT ||
317         viewMode == profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH) {
318       return;
319     }
320
321     // While the bubble is open, the avatar menu can only change from the
322     // profile chooser view by modifying the current profile's photo or name.
323     [controller_
324         initMenuContentsWithView:profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER];
325   }
326
327   // content::NotificationObserver:
328   virtual void Observe(
329       int type,
330       const content::NotificationSource& source,
331       const content::NotificationDetails& details) OVERRIDE {
332     DCHECK_EQ(chrome::NOTIFICATION_BROWSER_CLOSING, type);
333     if (browser_ == content::Source<Browser>(source).ptr()) {
334       RemoveTokenServiceObserver();
335       // Clean up the bubble's WebContents (used by the Gaia embedded view), to
336       // make sure the guest profile doesn't have any dangling host renderers.
337       // This can happen if Chrome is quit using Command-Q while the bubble is
338       // still open, which won't give the bubble a chance to be closed and
339       // clean up the WebContents itself.
340       [controller_ cleanUpEmbeddedViewContents];
341     }
342   }
343
344   ProfileChooserController* controller_;  // Weak; owns this.
345   Browser* browser_;  // Weak.
346   content::NotificationRegistrar registrar_;
347
348   // The observer can be removed both when closing the browser, and by just
349   // closing the avatar bubble. However, in the case of closing the browser,
350   // the avatar bubble will also be closed afterwards, resulting in a second
351   // attempt to remove the observer. This ensures the observer is only
352   // removed once.
353   bool token_observer_registered_;
354
355   DISALLOW_COPY_AND_ASSIGN(ActiveProfileObserverBridge);
356 };
357
358 // Custom button cell that adds a left padding before the button image, and
359 // a custom spacing between the button image and title.
360 @interface CustomPaddingImageButtonCell : NSButtonCell {
361  @private
362   // Padding added to the left margin of the button.
363   int leftMarginSpacing_;
364   // Spacing between the cell image and title.
365   int imageTitleSpacing_;
366 }
367
368 - (id)initWithLeftMarginSpacing:(int)leftMarginSpacing
369               imageTitleSpacing:(int)imageTitleSpacing;
370 @end
371
372 @implementation CustomPaddingImageButtonCell
373 - (id)initWithLeftMarginSpacing:(int)leftMarginSpacing
374               imageTitleSpacing:(int)imageTitleSpacing {
375   if ((self = [super init])) {
376     leftMarginSpacing_ = leftMarginSpacing;
377     imageTitleSpacing_ = imageTitleSpacing;
378   }
379   return self;
380 }
381
382 - (NSRect)drawTitle:(NSAttributedString*)title
383           withFrame:(NSRect)frame
384              inView:(NSView*)controlView {
385   NSRect marginRect;
386   NSDivideRect(frame, &marginRect, &frame, leftMarginSpacing_, NSMinXEdge);
387
388   // The title frame origin isn't aware of the left margin spacing added
389   // in -drawImage, so it must be added when drawing the title as well.
390   if ([self imagePosition] == NSImageLeft)
391     NSDivideRect(frame, &marginRect, &frame, imageTitleSpacing_, NSMinXEdge);
392
393   return [super drawTitle:title withFrame:frame inView:controlView];
394 }
395
396 - (void)drawImage:(NSImage*)image
397         withFrame:(NSRect)frame
398            inView:(NSView*)controlView {
399   if ([self imagePosition] == NSImageLeft)
400     frame.origin.x = leftMarginSpacing_;
401   [super drawImage:image withFrame:frame inView:controlView];
402 }
403
404 - (NSSize)cellSize {
405   NSSize buttonSize = [super cellSize];
406   buttonSize.width += leftMarginSpacing_;
407   if ([self imagePosition] == NSImageLeft)
408     buttonSize.width += imageTitleSpacing_;
409   return buttonSize;
410 }
411
412 @end
413
414 // A custom button that has a transparent backround.
415 @interface TransparentBackgroundButton : NSButton
416 @end
417
418 @implementation TransparentBackgroundButton
419 - (id)initWithFrame:(NSRect)frameRect {
420   if ((self = [super initWithFrame:frameRect])) {
421     [self setBordered:NO];
422     [self setFont:[NSFont labelFontOfSize:kTextFontSize]];
423     [self setButtonType:NSMomentaryChangeButton];
424   }
425   return self;
426 }
427
428 - (void)drawRect:(NSRect)dirtyRect {
429   NSColor* backgroundColor = [NSColor colorWithCalibratedWhite:1 alpha:0.6f];
430   [backgroundColor setFill];
431   NSRectFillUsingOperation(dirtyRect, NSCompositeSourceAtop);
432   [super drawRect:dirtyRect];
433 }
434 @end
435
436 // A custom image control that shows a "Change" button when moused over.
437 @interface EditableProfilePhoto : NSImageView {
438  @private
439   AvatarMenu* avatarMenu_;  // Weak; Owned by ProfileChooserController.
440   base::scoped_nsobject<TransparentBackgroundButton> changePhotoButton_;
441   // Used to display the "Change" button on hover.
442   ui::ScopedCrTrackingArea trackingArea_;
443   ProfileChooserController* controller_;
444 }
445
446 - (id)initWithFrame:(NSRect)frameRect
447          avatarMenu:(AvatarMenu*)avatarMenu
448         profileIcon:(const gfx::Image&)profileIcon
449      editingAllowed:(BOOL)editingAllowed
450      withController:(ProfileChooserController*)controller;
451
452 // Called when the "Change" button is clicked.
453 - (void)editPhoto:(id)sender;
454
455 // When hovering over the profile photo, show the "Change" button.
456 - (void)mouseEntered:(NSEvent*)event;
457
458 // When hovering away from the profile photo, hide the "Change" button.
459 - (void)mouseExited:(NSEvent*)event;
460 @end
461
462 @interface EditableProfilePhoto (Private)
463 // Create the "Change" avatar photo button.
464 - (TransparentBackgroundButton*)changePhotoButtonWithRect:(NSRect)rect;
465 @end
466
467 @implementation EditableProfilePhoto
468 - (id)initWithFrame:(NSRect)frameRect
469          avatarMenu:(AvatarMenu*)avatarMenu
470         profileIcon:(const gfx::Image&)profileIcon
471      editingAllowed:(BOOL)editingAllowed
472      withController:(ProfileChooserController*)controller {
473   if ((self = [super initWithFrame:frameRect])) {
474     avatarMenu_ = avatarMenu;
475     controller_ = controller;
476     [self setImage:CreateProfileImage(
477         profileIcon, kLargeImageSide).ToNSImage()];
478
479     // Add a tracking area so that we can show/hide the button when hovering.
480     trackingArea_.reset([[CrTrackingArea alloc]
481         initWithRect:[self bounds]
482              options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways
483                owner:self
484             userInfo:nil]);
485     [self addTrackingArea:trackingArea_.get()];
486
487     NSRect bounds = NSMakeRect(0, 0, kLargeImageSide, kLargeImageSide);
488     if (editingAllowed) {
489       changePhotoButton_.reset([self changePhotoButtonWithRect:bounds]);
490       [self addSubview:changePhotoButton_];
491
492       // Hide the button until the image is hovered over.
493       [changePhotoButton_ setHidden:YES];
494     }
495
496     // Set the image cell's accessibility strings to be the same as the
497     // button's strings.
498     [[self cell] accessibilitySetOverrideValue:l10n_util::GetNSString(
499         editingAllowed ?
500         IDS_PROFILES_NEW_AVATAR_MENU_CHANGE_PHOTO_ACCESSIBLE_NAME :
501         IDS_PROFILES_NEW_AVATAR_MENU_PHOTO_ACCESSIBLE_NAME)
502                                   forAttribute:NSAccessibilityTitleAttribute];
503     [[self cell] accessibilitySetOverrideValue:
504         editingAllowed ? NSAccessibilityButtonRole : NSAccessibilityImageRole
505                                   forAttribute:NSAccessibilityRoleAttribute];
506     [[self cell] accessibilitySetOverrideValue:
507         NSAccessibilityRoleDescription(NSAccessibilityButtonRole, nil)
508             forAttribute:NSAccessibilityRoleDescriptionAttribute];
509
510     // The button and the cell should read the same thing.
511     [self accessibilitySetOverrideValue:l10n_util::GetNSString(
512         editingAllowed ?
513         IDS_PROFILES_NEW_AVATAR_MENU_CHANGE_PHOTO_ACCESSIBLE_NAME :
514         IDS_PROFILES_NEW_AVATAR_MENU_PHOTO_ACCESSIBLE_NAME)
515                                   forAttribute:NSAccessibilityTitleAttribute];
516     [self accessibilitySetOverrideValue:NSAccessibilityButtonRole
517                                   forAttribute:NSAccessibilityRoleAttribute];
518     [self accessibilitySetOverrideValue:
519         NSAccessibilityRoleDescription(NSAccessibilityButtonRole, nil)
520             forAttribute:NSAccessibilityRoleDescriptionAttribute];
521   }
522   return self;
523 }
524
525 - (void)drawRect:(NSRect)dirtyRect {
526   NSRect bounds = [self bounds];
527
528   // Display the profile picture as a circle.
529   NSBezierPath* path = [NSBezierPath bezierPathWithOvalInRect:bounds];
530   [path addClip];
531   [self.image drawAtPoint:bounds.origin
532                  fromRect:bounds
533                 operation:NSCompositeSourceOver
534                  fraction:1.0];
535
536 }
537
538 - (void)editPhoto:(id)sender {
539   avatarMenu_->EditProfile(avatarMenu_->GetActiveProfileIndex());
540   [controller_
541       postActionPerformed:ProfileMetrics::PROFILE_DESKTOP_MENU_EDIT_IMAGE];
542 }
543
544 - (void)mouseEntered:(NSEvent*)event {
545   [changePhotoButton_ setHidden:NO];
546 }
547
548 - (void)mouseExited:(NSEvent*)event {
549   [changePhotoButton_ setHidden:YES];
550 }
551
552 // Make sure the element is focusable for accessibility.
553 - (BOOL)canBecomeKeyView {
554   return YES;
555 }
556
557 - (BOOL)accessibilityIsIgnored {
558   return NO;
559 }
560
561 - (NSArray*)accessibilityActionNames {
562   NSArray* parentActions = [super accessibilityActionNames];
563   return [parentActions arrayByAddingObject:NSAccessibilityPressAction];
564 }
565
566 - (void)accessibilityPerformAction:(NSString*)action {
567   if ([action isEqualToString:NSAccessibilityPressAction]) {
568     avatarMenu_->EditProfile(avatarMenu_->GetActiveProfileIndex());
569   }
570
571   [super accessibilityPerformAction:action];
572 }
573
574 - (TransparentBackgroundButton*)changePhotoButtonWithRect:(NSRect)rect {
575   TransparentBackgroundButton* button =
576       [[TransparentBackgroundButton alloc] initWithFrame:rect];
577   [button setImage:ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed(
578       IDR_ICON_PROFILES_EDIT_CAMERA).AsNSImage()];
579   [button setImagePosition:NSImageOnly];
580   [button setTarget:self];
581   [button setAction:@selector(editPhoto:)];
582   return button;
583 }
584 @end
585
586 // A custom text control that turns into a textfield for editing when clicked.
587 @interface EditableProfileNameButton : HoverImageButton {
588  @private
589   base::scoped_nsobject<NSTextField> profileNameTextField_;
590   Profile* profile_;  // Weak.
591   ProfileChooserController* controller_;
592 }
593
594 - (id)initWithFrame:(NSRect)frameRect
595             profile:(Profile*)profile
596         profileName:(NSString*)profileName
597      editingAllowed:(BOOL)editingAllowed
598      withController:(ProfileChooserController*)controller;
599
600 // Called when the button is clicked.
601 - (void)showEditableView:(id)sender;
602
603 // Called when enter is pressed in the text field.
604 - (void)saveProfileName:(id)sender;
605
606 @end
607
608 @implementation EditableProfileNameButton
609 - (id)initWithFrame:(NSRect)frameRect
610             profile:(Profile*)profile
611         profileName:(NSString*)profileName
612      editingAllowed:(BOOL)editingAllowed
613      withController:(ProfileChooserController*)controller {
614   if ((self = [super initWithFrame:frameRect])) {
615     profile_ = profile;
616     controller_ = controller;
617
618     if (editingAllowed) {
619       // Show an "edit" pencil icon when hovering over. In the default state,
620       // we need to create an empty placeholder of the correct size, so that
621       // the text doesn't jump around when the hovered icon appears.
622       ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
623       NSImage* hoverImage = rb->GetNativeImageNamed(
624           IDR_ICON_PROFILES_EDIT_HOVER).AsNSImage();
625
626       // In order to center the button title, we need to add a left padding of
627       // the same width as the pencil icon.
628       base::scoped_nsobject<CustomPaddingImageButtonCell> cell(
629           [[CustomPaddingImageButtonCell alloc]
630               initWithLeftMarginSpacing:[hoverImage size].width
631                       imageTitleSpacing:0]);
632       [self setCell:cell.get()];
633
634       NSImage* placeholder = [[NSImage alloc] initWithSize:[hoverImage size]];
635       [self setDefaultImage:placeholder];
636       [self setHoverImage:hoverImage];
637       [self setAlternateImage:
638           rb->GetNativeImageNamed(IDR_ICON_PROFILES_EDIT_PRESSED).AsNSImage()];
639       [self setImagePosition:NSImageRight];
640       [self setTarget:self];
641       [self setAction:@selector(showEditableView:)];
642
643       // We need to subtract the width of the bezel from the frame rect, so that
644       // the textfield can take the exact same space as the button.
645       frameRect.size.height -= 2 * kBezelThickness;
646       frameRect.origin = NSMakePoint(0, kBezelThickness);
647       profileNameTextField_.reset(
648           [[NSTextField alloc] initWithFrame:frameRect]);
649       [profileNameTextField_ setStringValue:profileName];
650       [profileNameTextField_ setFont:[NSFont labelFontOfSize:kTitleFontSize]];
651       [profileNameTextField_ setEditable:YES];
652       [profileNameTextField_ setDrawsBackground:YES];
653       [profileNameTextField_ setBezeled:YES];
654       [profileNameTextField_ setAlignment:NSCenterTextAlignment];
655       [[profileNameTextField_ cell] setWraps:NO];
656       [[profileNameTextField_ cell] setLineBreakMode:
657           NSLineBreakByTruncatingTail];
658       [self addSubview:profileNameTextField_];
659       [profileNameTextField_ setTarget:self];
660       [profileNameTextField_ setAction:@selector(saveProfileName:)];
661
662       // Hide the textfield until the user clicks on the button.
663       [profileNameTextField_ setHidden:YES];
664     }
665
666     [[self cell] accessibilitySetOverrideValue:NSAccessibilityButtonRole
667                            forAttribute:NSAccessibilityRoleAttribute];
668     [[self cell] accessibilitySetOverrideValue:
669         NSAccessibilityRoleDescription(NSAccessibilityButtonRole, nil)
670           forAttribute:NSAccessibilityRoleDescriptionAttribute];
671
672     [self setBordered:NO];
673     [self setFont:[NSFont labelFontOfSize:kTitleFontSize]];
674     [self setAlignment:NSCenterTextAlignment];
675     [[self cell] setLineBreakMode:NSLineBreakByTruncatingTail];
676     [self setTitle:profileName];
677   }
678   return self;
679 }
680
681 - (void)saveProfileName:(id)sender {
682   base::string16 newProfileName =
683       base::SysNSStringToUTF16([profileNameTextField_ stringValue]);
684
685   // Empty profile names are not allowed, and are treated as a cancel.
686   base::TrimWhitespace(newProfileName, base::TRIM_ALL, &newProfileName);
687   if (!newProfileName.empty()) {
688     profiles::UpdateProfileName(profile_, newProfileName);
689     [controller_
690         postActionPerformed:ProfileMetrics::PROFILE_DESKTOP_MENU_EDIT_NAME];
691     [self setTitle:base::SysUTF16ToNSString(newProfileName)];
692   } else {
693     // Since the text is empty and not allowed, revert it from the textbox.
694     [profileNameTextField_ setStringValue:[self title]];
695   }
696   [profileNameTextField_ setHidden:YES];
697 }
698
699 - (void)showEditableView:(id)sender {
700   [profileNameTextField_ setHidden:NO];
701   [[self window] makeFirstResponder:profileNameTextField_];
702 }
703
704 @end
705
706 // A custom button that allows for setting a background color when hovered over.
707 @interface BackgroundColorHoverButton : HoverImageButton {
708  @private
709   base::scoped_nsobject<NSColor> backgroundColor_;
710   base::scoped_nsobject<NSColor> hoverColor_;
711 }
712 @end
713
714 @implementation BackgroundColorHoverButton
715
716 - (id)initWithFrame:(NSRect)frameRect
717   imageTitleSpacing:(int)imageTitleSpacing
718     backgroundColor:(NSColor*)backgroundColor {
719   if ((self = [super initWithFrame:frameRect])) {
720     backgroundColor_.reset([backgroundColor retain]);
721     // Use a color from the common theme, since this button is not trying to
722     // look like a native control.
723     SkColor hoverColor;
724     bool found = ui::CommonThemeGetSystemColor(
725         ui::NativeTheme::kColorId_ButtonHoverBackgroundColor, &hoverColor);
726     DCHECK(found);
727     hoverColor_.reset([gfx::SkColorToSRGBNSColor(hoverColor) retain]);
728
729     [self setBordered:NO];
730     [self setFont:[NSFont labelFontOfSize:kTextFontSize]];
731     [self setButtonType:NSMomentaryChangeButton];
732
733     base::scoped_nsobject<CustomPaddingImageButtonCell> cell(
734         [[CustomPaddingImageButtonCell alloc]
735             initWithLeftMarginSpacing:kHorizontalSpacing
736                     imageTitleSpacing:imageTitleSpacing]);
737     [cell setLineBreakMode:NSLineBreakByTruncatingTail];
738     [self setCell:cell.get()];
739   }
740   return self;
741 }
742
743 - (void)setHoverState:(HoverState)state {
744   [super setHoverState:state];
745   bool isHighlighted = ([self hoverState] != kHoverStateNone);
746
747   NSColor* backgroundColor = isHighlighted ? hoverColor_ : backgroundColor_;
748   [[self cell] setBackgroundColor:backgroundColor];
749 }
750
751 @end
752
753 // A custom view with the given background color.
754 @interface BackgroundColorView : NSView {
755  @private
756   base::scoped_nsobject<NSColor> backgroundColor_;
757 }
758 @end
759
760 @implementation BackgroundColorView
761 - (id)initWithFrame:(NSRect)frameRect
762           withColor:(NSColor*)color {
763   if ((self = [super initWithFrame:frameRect]))
764     backgroundColor_.reset([color retain]);
765   return self;
766 }
767
768 - (void)drawRect:(NSRect)dirtyRect {
769   [backgroundColor_ setFill];
770   NSRectFill(dirtyRect);
771   [super drawRect:dirtyRect];
772 }
773 @end
774
775 @interface ProfileChooserController ()
776 // Builds the profile chooser view.
777 - (NSView*)buildProfileChooserView;
778
779 // Builds a tutorial card with a title label using |titleMessage|, a content
780 // label using |contentMessage|, a link using |linkMessage|, and a button using
781 // |buttonMessage|. If |stackButton| is YES, places the button above the link.
782 // Otherwise places both on the same row with the link left aligned and button
783 // right aligned. On click, the link would execute |linkAction|, and the button
784 // would execute |buttonAction|. It sets |tutorialMode_| to the given |mode|.
785 - (NSView*)tutorialViewWithMode:(profiles::TutorialMode)mode
786                    titleMessage:(NSString*)titleMessage
787                  contentMessage:(NSString*)contentMessage
788                     linkMessage:(NSString*)linkMessage
789                   buttonMessage:(NSString*)buttonMessage
790                     stackButton:(BOOL)stackButton
791                  hasCloseButton:(BOOL)hasCloseButton
792                      linkAction:(SEL)linkAction
793                    buttonAction:(SEL)buttonAction;
794
795 // Builds a tutorial card to introduce an upgrade user to the new avatar menu if
796 // needed. |tutorial_shown| indicates if the tutorial has already been shown in
797 // the previous active view. |avatar_item| refers to the current profile.
798 - (NSView*)buildWelcomeUpgradeTutorialViewIfNeeded;
799
800 // Builds a tutorial card to have the user confirm the last Chrome signin,
801 // Chrome sync will be delayed until the user either dismisses the tutorial, or
802 // configures sync through the "Settings" link.
803 - (NSView*)buildSigninConfirmationView;
804
805 // Builds a tutorial card to show the last signin error.
806 - (NSView*)buildSigninErrorView;
807
808 // Creates the main profile card for the profile |item| at the top of
809 // the bubble.
810 - (NSView*)createCurrentProfileView:(const AvatarMenu::Item&)item;
811
812 // Creates the possible links for the main profile card with profile |item|.
813 - (NSView*)createCurrentProfileLinksForItem:(const AvatarMenu::Item&)item
814                                        rect:(NSRect)rect;
815
816 // Creates the disclaimer text for supervised users, telling them that the
817 // manager can view their history etc.
818 - (NSView*)createSupervisedUserDisclaimerView;
819
820 // Creates a main profile card for the guest user.
821 - (NSView*)createGuestProfileView;
822
823 // Creates an item for the profile |itemIndex| that is used in the fast profile
824 // switcher in the middle of the bubble.
825 - (NSButton*)createOtherProfileView:(int)itemIndex;
826
827 // Creates the "Not you" and Lock option buttons.
828 - (NSView*)createOptionsViewWithRect:(NSRect)rect
829                           enableLock:(BOOL)enableLock;
830
831 // Creates the account management view for the active profile.
832 - (NSView*)createCurrentProfileAccountsView:(NSRect)rect;
833
834 // Creates the list of accounts for the active profile.
835 - (NSView*)createAccountsListWithRect:(NSRect)rect;
836
837 // Creates the Gaia sign-in/add account view.
838 - (NSView*)buildGaiaEmbeddedView;
839
840 // Creates the account removal view.
841 - (NSView*)buildAccountRemovalView;
842
843 // Create a view that shows various options for an upgrade user who is not
844 // the same person as the currently signed in user.
845 - (NSView*)buildSwitchUserView;
846
847 // Creates a button with |text|, an icon given by |imageResourceId| and with
848 // |action|.
849 - (NSButton*)hoverButtonWithRect:(NSRect)rect
850                             text:(NSString*)text
851                  imageResourceId:(int)imageResourceId
852                           action:(SEL)action;
853
854 // Creates a generic link button with |title| and an |action| positioned at
855 // |frameOrigin|.
856 - (NSButton*)linkButtonWithTitle:(NSString*)title
857                      frameOrigin:(NSPoint)frameOrigin
858                           action:(SEL)action;
859
860 // Creates an email account button with |title| and a remove icon. If
861 // |reauthRequired| is true, the button also displays a warning icon. |tag|
862 // indicates which account the button refers to.
863 - (NSButton*)accountButtonWithRect:(NSRect)rect
864                              title:(const std::string&)title
865                                tag:(int)tag
866                     reauthRequired:(BOOL)reauthRequired;
867
868 - (bool)shouldShowGoIncognito;
869 @end
870
871 @implementation ProfileChooserController
872 - (profiles::BubbleViewMode) viewMode {
873   return viewMode_;
874 }
875
876 - (void)setTutorialMode:(profiles::TutorialMode)tutorialMode {
877   tutorialMode_ = tutorialMode;
878 }
879
880 - (IBAction)switchToProfile:(id)sender {
881   // Check the event flags to see if a new window should be created.
882   bool alwaysCreate = ui::WindowOpenDispositionFromNSEvent(
883       [NSApp currentEvent]) == NEW_WINDOW;
884   avatarMenu_->SwitchToProfile([sender tag], alwaysCreate,
885                                ProfileMetrics::SWITCH_PROFILE_ICON);
886 }
887
888 - (IBAction)showUserManager:(id)sender {
889   chrome::ShowUserManager(browser_->profile()->GetPath());
890   [self postActionPerformed:
891       ProfileMetrics::PROFILE_DESKTOP_MENU_OPEN_USER_MANAGER];
892 }
893
894 - (IBAction)exitGuest:(id)sender {
895   DCHECK(browser_->profile()->IsGuestSession());
896   chrome::ShowUserManager(base::FilePath());
897   profiles::CloseGuestProfileWindows();
898 }
899
900 - (IBAction)goIncognito:(id)sender {
901   DCHECK([self shouldShowGoIncognito]);
902   chrome::NewIncognitoWindow(browser_);
903 }
904
905 - (IBAction)showAccountManagement:(id)sender {
906   [self initMenuContentsWithView:profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT];
907 }
908
909 - (IBAction)hideAccountManagement:(id)sender {
910   [self initMenuContentsWithView:profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER];
911 }
912
913 - (IBAction)lockProfile:(id)sender {
914   profiles::LockProfile(browser_->profile());
915   [self postActionPerformed:ProfileMetrics::PROFILE_DESKTOP_MENU_LOCK];
916 }
917
918 - (IBAction)showInlineSigninPage:(id)sender {
919   [self initMenuContentsWithView:profiles::BUBBLE_VIEW_MODE_GAIA_SIGNIN];
920 }
921
922 - (IBAction)addAccount:(id)sender {
923   [self initMenuContentsWithView:profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT];
924   [self postActionPerformed:ProfileMetrics::PROFILE_DESKTOP_MENU_ADD_ACCT];
925 }
926
927 - (IBAction)navigateBackFromSigninPage:(id)sender {
928   std::string primaryAccount = SigninManagerFactory::GetForProfile(
929       browser_->profile())->GetAuthenticatedUsername();
930   bool hasAccountManagement = !primaryAccount.empty() &&
931       switches::IsEnableAccountConsistency();
932   [self initMenuContentsWithView:hasAccountManagement ?
933       profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT :
934       profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER];
935 }
936
937 - (IBAction)showAccountRemovalView:(id)sender {
938   DCHECK(!isGuestSession_);
939
940   // Tag is either |kPrimaryProfileTag| for the primary account, or equal to the
941   // index in |currentProfileAccounts_| for a secondary account.
942   int tag = [sender tag];
943   if (tag == kPrimaryProfileTag) {
944     accountIdToRemove_ = SigninManagerFactory::GetForProfile(
945         browser_->profile())->GetAuthenticatedUsername();
946   } else {
947     DCHECK(ContainsKey(currentProfileAccounts_, tag));
948     accountIdToRemove_ = currentProfileAccounts_[tag];
949   }
950
951   [self initMenuContentsWithView:profiles::BUBBLE_VIEW_MODE_ACCOUNT_REMOVAL];
952 }
953
954 - (IBAction)showAccountReauthenticationView:(id)sender {
955   DCHECK(!isGuestSession_);
956   [self initMenuContentsWithView:profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH];
957 }
958
959 - (IBAction)removeAccount:(id)sender {
960   DCHECK(!accountIdToRemove_.empty());
961   ProfileOAuth2TokenServiceFactory::GetPlatformSpecificForProfile(
962       browser_->profile())->RevokeCredentials(accountIdToRemove_);
963   [self postActionPerformed:ProfileMetrics::PROFILE_DESKTOP_MENU_REMOVE_ACCT];
964   accountIdToRemove_.clear();
965
966   [self initMenuContentsWithView:profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT];
967 }
968
969 - (IBAction)seeWhatsNew:(id)sender {
970   chrome::ShowUserManagerWithTutorial(
971       profiles::USER_MANAGER_TUTORIAL_OVERVIEW);
972   ProfileMetrics::LogProfileNewAvatarMenuUpgrade(
973       ProfileMetrics::PROFILE_AVATAR_MENU_UPGRADE_WHATS_NEW);
974 }
975
976 - (IBAction)showSwitchUserView:(id)sender {
977   [self initMenuContentsWithView:profiles::BUBBLE_VIEW_MODE_SWITCH_USER];
978   ProfileMetrics::LogProfileNewAvatarMenuUpgrade(
979       ProfileMetrics::PROFILE_AVATAR_MENU_UPGRADE_NOT_YOU);
980 }
981
982 - (IBAction)showLearnMorePage:(id)sender {
983   signin_ui_util::ShowSigninErrorLearnMorePage(browser_->profile());
984 }
985
986 - (IBAction)configureSyncSettings:(id)sender {
987   tutorialMode_ = profiles::TUTORIAL_MODE_NONE;
988   LoginUIServiceFactory::GetForProfile(browser_->profile())->
989       SyncConfirmationUIClosed(true);
990   ProfileMetrics::LogProfileNewAvatarMenuSignin(
991       ProfileMetrics::PROFILE_AVATAR_MENU_SIGNIN_SETTINGS);
992 }
993
994 - (IBAction)syncSettingsConfirmed:(id)sender {
995   tutorialMode_ = profiles::TUTORIAL_MODE_NONE;
996   LoginUIServiceFactory::GetForProfile(browser_->profile())->
997       SyncConfirmationUIClosed(false);
998   ProfileMetrics::LogProfileNewAvatarMenuSignin(
999       ProfileMetrics::PROFILE_AVATAR_MENU_SIGNIN_OK);
1000   [self initMenuContentsWithView:profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER];
1001 }
1002
1003 - (IBAction)disconnectProfile:(id)sender {
1004   chrome::ShowSettings(browser_);
1005   ProfileMetrics::LogProfileNewAvatarMenuNotYou(
1006       ProfileMetrics::PROFILE_AVATAR_MENU_NOT_YOU_DISCONNECT);
1007 }
1008
1009 - (IBAction)navigateBackFromSwitchUserView:(id)sender {
1010   [self initMenuContentsWithView:profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER];
1011   ProfileMetrics::LogProfileNewAvatarMenuNotYou(
1012       ProfileMetrics::PROFILE_AVATAR_MENU_NOT_YOU_BACK);
1013 }
1014
1015 - (IBAction)dismissTutorial:(id)sender {
1016   // Never shows the upgrade tutorial again if manually closed.
1017   if (tutorialMode_ == profiles::TUTORIAL_MODE_WELCOME_UPGRADE) {
1018     browser_->profile()->GetPrefs()->SetInteger(
1019         prefs::kProfileAvatarTutorialShown,
1020         signin_ui_util::kUpgradeWelcomeTutorialShowMax + 1);
1021   }
1022
1023   tutorialMode_ = profiles::TUTORIAL_MODE_NONE;
1024   [self initMenuContentsWithView:profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER];
1025 }
1026
1027 - (void)windowWillClose:(NSNotification*)notification {
1028   if (tutorialMode_ == profiles::TUTORIAL_MODE_CONFIRM_SIGNIN) {
1029     LoginUIServiceFactory::GetForProfile(browser_->profile())->
1030         SyncConfirmationUIClosed(false);
1031   }
1032
1033   [super windowWillClose:notification];
1034 }
1035
1036 - (void)cleanUpEmbeddedViewContents {
1037   webContents_.reset();
1038 }
1039
1040 - (id)initWithBrowser:(Browser*)browser
1041            anchoredAt:(NSPoint)point
1042              viewMode:(profiles::BubbleViewMode)viewMode
1043          tutorialMode:(profiles::TutorialMode)tutorialMode
1044           serviceType:(signin::GAIAServiceType)serviceType {
1045   base::scoped_nsobject<InfoBubbleWindow> window([[InfoBubbleWindow alloc]
1046       initWithContentRect:ui::kWindowSizeDeterminedLater
1047                 styleMask:NSBorderlessWindowMask
1048                   backing:NSBackingStoreBuffered
1049                     defer:NO]);
1050
1051   if ((self = [super initWithWindow:window
1052                        parentWindow:browser->window()->GetNativeWindow()
1053                          anchoredAt:point])) {
1054     browser_ = browser;
1055     viewMode_ = viewMode;
1056     tutorialMode_ = tutorialMode;
1057     observer_.reset(new ActiveProfileObserverBridge(self, browser_));
1058     serviceType_ = serviceType;
1059
1060     avatarMenu_.reset(new AvatarMenu(
1061         &g_browser_process->profile_manager()->GetProfileInfoCache(),
1062         observer_.get(),
1063         browser_));
1064     avatarMenu_->RebuildMenu();
1065
1066     // Guest profiles do not have a token service.
1067     isGuestSession_ = browser_->profile()->IsGuestSession();
1068
1069     // If view mode is PROFILE_CHOOSER but there is an auth error, force
1070     // ACCOUNT_MANAGEMENT mode.
1071     if (viewMode_ == profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER &&
1072         HasAuthError(browser_->profile()) &&
1073         switches::IsEnableAccountConsistency() &&
1074         avatarMenu_->GetItemAt(avatarMenu_->GetActiveProfileIndex()).
1075             signed_in) {
1076       viewMode_ = profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT;
1077     }
1078
1079     [window accessibilitySetOverrideValue:
1080         l10n_util::GetNSString(IDS_PROFILES_NEW_AVATAR_MENU_ACCESSIBLE_NAME)
1081                              forAttribute:NSAccessibilityTitleAttribute];
1082     [window accessibilitySetOverrideValue:
1083         l10n_util::GetNSString(IDS_PROFILES_NEW_AVATAR_MENU_ACCESSIBLE_NAME)
1084                              forAttribute:NSAccessibilityHelpAttribute];
1085
1086     [[self bubble] setAlignment:info_bubble::kAlignRightEdgeToAnchorEdge];
1087     [[self bubble] setArrowLocation:info_bubble::kNoArrow];
1088     [[self bubble] setBackgroundColor:GetDialogBackgroundColor()];
1089     [self initMenuContentsWithView:viewMode_];
1090   }
1091
1092   return self;
1093 }
1094
1095 - (void)initMenuContentsWithView:(profiles::BubbleViewMode)viewToDisplay {
1096   if (browser_->profile()->IsSupervised() &&
1097       (viewToDisplay == profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT ||
1098        viewToDisplay == profiles::BUBBLE_VIEW_MODE_ACCOUNT_REMOVAL)) {
1099     LOG(WARNING) << "Supervised user attempted to add/remove account";
1100     return;
1101   }
1102   viewMode_ = viewToDisplay;
1103   NSView* contentView = [[self window] contentView];
1104   [contentView setSubviews:[NSArray array]];
1105   NSView* subView;
1106
1107   switch (viewMode_) {
1108     case profiles::BUBBLE_VIEW_MODE_GAIA_SIGNIN:
1109     case profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT:
1110     case profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH:
1111       subView = [self buildGaiaEmbeddedView];
1112       break;
1113     case profiles::BUBBLE_VIEW_MODE_ACCOUNT_REMOVAL:
1114       subView = [self buildAccountRemovalView];
1115       break;
1116     case profiles::BUBBLE_VIEW_MODE_SWITCH_USER:
1117       subView = [self buildSwitchUserView];
1118       break;
1119     case profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER:
1120     case profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT:
1121       subView = [self buildProfileChooserView];
1122       break;
1123   }
1124
1125   // Clears tutorial mode for all non-profile-chooser views.
1126   if (viewMode_ != profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER)
1127     tutorialMode_ = profiles::TUTORIAL_MODE_NONE;
1128
1129   [contentView addSubview:subView];
1130   SetWindowSize([self window],
1131       NSMakeSize(NSWidth([subView frame]), NSHeight([subView frame])));
1132 }
1133
1134 - (NSView*)buildProfileChooserView {
1135   base::scoped_nsobject<NSView> container(
1136       [[NSView alloc] initWithFrame:NSZeroRect]);
1137
1138   NSView* tutorialView = nil;
1139   NSView* currentProfileView = nil;
1140   base::scoped_nsobject<NSMutableArray> otherProfiles(
1141       [[NSMutableArray alloc] init]);
1142   // Local and guest profiles cannot lock their profile.
1143   bool enableLock = false;
1144
1145   // Loop over the profiles in reverse, so that they are sorted by their
1146   // y-coordinate, and separate them into active and "other" profiles.
1147   for (int i = avatarMenu_->GetNumberOfItems() - 1; i >= 0; --i) {
1148     const AvatarMenu::Item& item = avatarMenu_->GetItemAt(i);
1149     if (item.active) {
1150       if (viewMode_ == profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER) {
1151         switch (tutorialMode_) {
1152           case profiles::TUTORIAL_MODE_NONE:
1153           case profiles::TUTORIAL_MODE_WELCOME_UPGRADE:
1154             tutorialView =
1155                 [self buildWelcomeUpgradeTutorialViewIfNeeded];
1156             break;
1157           case profiles::TUTORIAL_MODE_CONFIRM_SIGNIN:
1158             tutorialView = [self buildSigninConfirmationView];
1159             break;
1160           case profiles::TUTORIAL_MODE_SHOW_ERROR:
1161             tutorialView = [self buildSigninErrorView];
1162         }
1163       }
1164       currentProfileView = [self createCurrentProfileView:item];
1165       enableLock = switches::IsNewProfileManagement() && item.signed_in;
1166     } else {
1167       [otherProfiles addObject:[self createOtherProfileView:i]];
1168     }
1169   }
1170   if (!currentProfileView)  // Guest windows don't have an active profile.
1171     currentProfileView = [self createGuestProfileView];
1172
1173   // |yOffset| is the next position at which to draw in |container|
1174   // coordinates. Add a pixel offset so that the bottom option buttons don't
1175   // overlap the bubble's rounded corners.
1176   CGFloat yOffset = 1;
1177
1178   // Option buttons.
1179   NSRect rect = NSMakeRect(0, yOffset, kFixedMenuWidth, 0);
1180   NSView* optionsView = [self createOptionsViewWithRect:rect
1181                                              enableLock:enableLock];
1182   [container addSubview:optionsView];
1183   rect.origin.y = NSMaxY([optionsView frame]);
1184
1185   NSBox* separator = [self horizontalSeparatorWithFrame:rect];
1186   [container addSubview:separator];
1187   yOffset = NSMaxY([separator frame]);
1188
1189   if (viewMode_ == profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER &&
1190       switches::IsFastUserSwitching()) {
1191     // Other profiles switcher. The profiles have already been sorted
1192     // by their y-coordinate, so they can be added in the existing order.
1193     for (NSView *otherProfileView in otherProfiles.get()) {
1194       [otherProfileView setFrameOrigin:NSMakePoint(0, yOffset)];
1195       [container addSubview:otherProfileView];
1196       yOffset = NSMaxY([otherProfileView frame]);
1197
1198       NSBox* separator = [self horizontalSeparatorWithFrame:NSMakeRect(
1199           0, yOffset, kFixedMenuWidth, 0)];
1200       [container addSubview:separator];
1201       yOffset = NSMaxY([separator frame]);
1202     }
1203   }
1204
1205   // For supervised users, add the disclaimer text.
1206   if (browser_->profile()->IsSupervised()) {
1207     yOffset += kSmallVerticalSpacing;
1208     NSView* disclaimerContainer = [self createSupervisedUserDisclaimerView];
1209     [disclaimerContainer setFrameOrigin:NSMakePoint(0, yOffset)];
1210     [container addSubview:disclaimerContainer];
1211     yOffset = NSMaxY([disclaimerContainer frame]);
1212     yOffset += kSmallVerticalSpacing;
1213
1214     NSBox* separator = [self horizontalSeparatorWithFrame:NSMakeRect(
1215         0, yOffset, kFixedMenuWidth, 0)];
1216     [container addSubview:separator];
1217     yOffset = NSMaxY([separator frame]);
1218   }
1219
1220   if (viewMode_ == profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT) {
1221     NSView* currentProfileAccountsView = [self createCurrentProfileAccountsView:
1222         NSMakeRect(0, yOffset, kFixedMenuWidth, 0)];
1223     [container addSubview:currentProfileAccountsView];
1224     yOffset = NSMaxY([currentProfileAccountsView frame]);
1225
1226     NSBox* accountsSeparator = [self horizontalSeparatorWithFrame:
1227         NSMakeRect(0, yOffset, kFixedMenuWidth, 0)];
1228     [container addSubview:accountsSeparator];
1229     yOffset = NSMaxY([accountsSeparator frame]);
1230   }
1231
1232   // Active profile card.
1233   if (currentProfileView) {
1234     yOffset += kVerticalSpacing;
1235     [currentProfileView setFrameOrigin:NSMakePoint(0, yOffset)];
1236     [container addSubview:currentProfileView];
1237     yOffset = NSMaxY([currentProfileView frame]) + kVerticalSpacing;
1238   }
1239
1240   if (tutorialView) {
1241     [tutorialView setFrameOrigin:NSMakePoint(0, yOffset)];
1242     [container addSubview:tutorialView];
1243     yOffset = NSMaxY([tutorialView frame]);
1244     //TODO(mlerman): update UMA stats for the new tutorials.
1245   } else {
1246     tutorialMode_ = profiles::TUTORIAL_MODE_NONE;
1247   }
1248
1249   [container setFrameSize:NSMakeSize(kFixedMenuWidth, yOffset)];
1250   return container.autorelease();
1251 }
1252
1253 - (NSView*)buildSigninConfirmationView {
1254   ProfileMetrics::LogProfileNewAvatarMenuSignin(
1255       ProfileMetrics::PROFILE_AVATAR_MENU_SIGNIN_VIEW);
1256
1257   NSString* titleMessage = l10n_util::GetNSString(
1258       IDS_PROFILES_CONFIRM_SIGNIN_TUTORIAL_TITLE);
1259   NSString* contentMessage = l10n_util::GetNSString(
1260       IDS_PROFILES_CONFIRM_SIGNIN_TUTORIAL_CONTENT_TEXT);
1261   NSString* linkMessage = l10n_util::GetNSString(
1262       IDS_PROFILES_SYNC_SETTINGS_LINK);
1263   NSString* buttonMessage = l10n_util::GetNSString(
1264       IDS_PROFILES_TUTORIAL_OK_BUTTON);
1265   return [self tutorialViewWithMode:profiles::TUTORIAL_MODE_CONFIRM_SIGNIN
1266                        titleMessage:titleMessage
1267                      contentMessage:contentMessage
1268                         linkMessage:linkMessage
1269                       buttonMessage:buttonMessage
1270                         stackButton:NO
1271                      hasCloseButton:NO
1272                          linkAction:@selector(configureSyncSettings:)
1273                        buttonAction:@selector(syncSettingsConfirmed:)];
1274 }
1275
1276 - (NSView*)buildSigninErrorView {
1277   NSString* titleMessage = l10n_util::GetNSString(
1278       IDS_PROFILES_ERROR_TUTORIAL_TITLE);
1279   LoginUIService* loginUiService =
1280       LoginUIServiceFactory::GetForProfile(browser_->profile());
1281   NSString* contentMessage =
1282       base::SysUTF16ToNSString(loginUiService->GetLastLoginResult());
1283   NSString* linkMessage = l10n_util::GetNSString(
1284       IDS_PROFILES_PROFILE_TUTORIAL_LEARN_MORE);
1285   return [self tutorialViewWithMode:profiles::TUTORIAL_MODE_CONFIRM_SIGNIN
1286                        titleMessage:titleMessage
1287                      contentMessage:contentMessage
1288                         linkMessage:linkMessage
1289                       buttonMessage:nil
1290                         stackButton:NO
1291                      hasCloseButton:YES
1292                          linkAction:@selector(showLearnMorePage:)
1293                        buttonAction:nil];
1294 }
1295
1296 - (NSView*)buildWelcomeUpgradeTutorialViewIfNeeded {
1297   Profile* profile = browser_->profile();
1298   const AvatarMenu::Item& avatarItem =
1299       avatarMenu_->GetItemAt(avatarMenu_->GetActiveProfileIndex());
1300
1301   const int showCount = profile->GetPrefs()->GetInteger(
1302       prefs::kProfileAvatarTutorialShown);
1303   // Do not show the tutorial if user has dismissed it.
1304   if (showCount > signin_ui_util::kUpgradeWelcomeTutorialShowMax)
1305     return nil;
1306
1307   if (tutorialMode_ != profiles::TUTORIAL_MODE_WELCOME_UPGRADE) {
1308     if (showCount == signin_ui_util::kUpgradeWelcomeTutorialShowMax)
1309       return nil;
1310     profile->GetPrefs()->SetInteger(
1311         prefs::kProfileAvatarTutorialShown, showCount + 1);
1312   }
1313
1314   ProfileMetrics::LogProfileNewAvatarMenuUpgrade(
1315       ProfileMetrics::PROFILE_AVATAR_MENU_UPGRADE_VIEW);
1316
1317   NSString* titleMessage = l10n_util::GetNSString(
1318       IDS_PROFILES_WELCOME_UPGRADE_TUTORIAL_TITLE);
1319   NSString* contentMessage = l10n_util::GetNSString(
1320       IDS_PROFILES_WELCOME_UPGRADE_TUTORIAL_CONTENT_TEXT);
1321   // For local profiles, the "Not you" link doesn't make sense.
1322   NSString* linkMessage = avatarItem.signed_in ?
1323       ElideMessage(
1324           l10n_util::GetStringFUTF16(IDS_PROFILES_NOT_YOU, avatarItem.name),
1325           kFixedMenuWidth - 2 * kHorizontalSpacing) :
1326       nil;
1327   NSString* buttonMessage = l10n_util::GetNSString(
1328       IDS_PROFILES_TUTORIAL_WHATS_NEW_BUTTON);
1329   return [self tutorialViewWithMode:profiles::TUTORIAL_MODE_WELCOME_UPGRADE
1330                        titleMessage:titleMessage
1331                      contentMessage:contentMessage
1332                         linkMessage:linkMessage
1333                       buttonMessage:buttonMessage
1334                         stackButton:YES
1335                      hasCloseButton:YES
1336                          linkAction:@selector(showSwitchUserView:)
1337                        buttonAction:@selector(seeWhatsNew:)];
1338 }
1339
1340 - (NSView*)tutorialViewWithMode:(profiles::TutorialMode)mode
1341                    titleMessage:(NSString*)titleMessage
1342                  contentMessage:(NSString*)contentMessage
1343                     linkMessage:(NSString*)linkMessage
1344                   buttonMessage:(NSString*)buttonMessage
1345                     stackButton:(BOOL)stackButton
1346                  hasCloseButton:(BOOL)hasCloseButton
1347                      linkAction:(SEL)linkAction
1348                    buttonAction:(SEL)buttonAction {
1349   tutorialMode_ = mode;
1350
1351   NSColor* tutorialBackgroundColor =
1352       gfx::SkColorToSRGBNSColor(profiles::kAvatarTutorialBackgroundColor);
1353   base::scoped_nsobject<NSView> container([[BackgroundColorView alloc]
1354       initWithFrame:NSMakeRect(0, 0, kFixedMenuWidth, 0)
1355           withColor:tutorialBackgroundColor]);
1356   CGFloat availableWidth = kFixedMenuWidth - 2 * kHorizontalSpacing;
1357   CGFloat yOffset = kVerticalSpacing;
1358
1359   // Adds links and buttons at the bottom.
1360   base::scoped_nsobject<NSButton> tutorialOkButton;
1361   if (buttonMessage) {
1362     tutorialOkButton.reset([[HoverButton alloc] initWithFrame:NSZeroRect]);
1363     [tutorialOkButton setTitle:buttonMessage];
1364     [tutorialOkButton setBezelStyle:NSRoundedBezelStyle];
1365     [tutorialOkButton setTarget:self];
1366     [tutorialOkButton setAction:buttonAction];
1367     [tutorialOkButton setAlignment:NSCenterTextAlignment];
1368     [tutorialOkButton sizeToFit];
1369   }
1370
1371   NSButton* learnMoreLink = nil;
1372   if (linkMessage) {
1373     learnMoreLink = [self linkButtonWithTitle:linkMessage
1374                                   frameOrigin:NSZeroPoint
1375                                        action:linkAction];
1376     [[learnMoreLink cell] setTextColor:[NSColor whiteColor]];
1377   }
1378
1379   if (stackButton) {
1380     if (linkMessage) {
1381       [learnMoreLink setFrameOrigin:NSMakePoint(
1382           (kFixedMenuWidth - NSWidth([learnMoreLink frame])) / 2, yOffset)];
1383     }
1384     [tutorialOkButton setFrameSize:NSMakeSize(
1385         availableWidth, NSHeight([tutorialOkButton frame]))];
1386     [tutorialOkButton setFrameOrigin:NSMakePoint(
1387         kHorizontalSpacing,
1388         yOffset + (learnMoreLink ? NSHeight([learnMoreLink frame]) : 0))];
1389   } else {
1390     if (buttonMessage) {
1391       NSSize buttonSize = [tutorialOkButton frame].size;
1392       const CGFloat kTopBottomTextPadding = 6;
1393       const CGFloat kLeftRightTextPadding = 15;
1394       buttonSize.width += 2 * kLeftRightTextPadding;
1395       buttonSize.height += 2 * kTopBottomTextPadding;
1396       [tutorialOkButton setFrameSize:buttonSize];
1397       CGFloat buttonXOffset = kFixedMenuWidth -
1398           NSWidth([tutorialOkButton frame]) - kHorizontalSpacing;
1399       [tutorialOkButton setFrameOrigin:NSMakePoint(buttonXOffset, yOffset)];
1400     }
1401
1402     if (linkMessage) {
1403       CGFloat linkYOffset = yOffset;
1404       if (buttonMessage) {
1405         linkYOffset += (NSHeight([tutorialOkButton frame]) -
1406                         NSHeight([learnMoreLink frame])) / 2;
1407       }
1408       [learnMoreLink setFrameOrigin:NSMakePoint(
1409           kHorizontalSpacing, linkYOffset)];
1410     }
1411   }
1412
1413   if (buttonMessage) {
1414     [container addSubview:tutorialOkButton];
1415     yOffset = NSMaxY([tutorialOkButton frame]);
1416   }
1417
1418   if (linkMessage) {
1419     [container addSubview:learnMoreLink];
1420     yOffset = std::max(NSMaxY([learnMoreLink frame]), yOffset);
1421   }
1422
1423   yOffset += kVerticalSpacing;
1424
1425   // Adds body content.
1426   NSTextField* contentLabel = BuildLabel(
1427       contentMessage,
1428       NSMakePoint(kHorizontalSpacing, yOffset),
1429       gfx::SkColorToSRGBNSColor(profiles::kAvatarTutorialContentTextColor));
1430   [contentLabel setFrameSize:NSMakeSize(availableWidth, 0)];
1431   [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:contentLabel];
1432   [container addSubview:contentLabel];
1433   yOffset = NSMaxY([contentLabel frame]) + kSmallVerticalSpacing;
1434
1435   // Adds title.
1436   NSTextField* titleLabel =
1437       BuildLabel(titleMessage,
1438                  NSMakePoint(kHorizontalSpacing, yOffset),
1439                  [NSColor whiteColor] /* text_color */);
1440   [titleLabel setFont:[NSFont labelFontOfSize:kTitleFontSize]];
1441
1442   if (hasCloseButton) {
1443     base::scoped_nsobject<HoverImageButton> closeButton(
1444         [[HoverImageButton alloc] initWithFrame:NSZeroRect]);
1445     [closeButton setBordered:NO];
1446
1447     ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
1448     NSImage* closeImage = rb->GetNativeImageNamed(IDR_CLOSE_1).ToNSImage();
1449     CGFloat closeImageWidth = [closeImage size].width;
1450     [closeButton setDefaultImage:closeImage];
1451     [closeButton setHoverImage:
1452         rb->GetNativeImageNamed(IDR_CLOSE_1_H).ToNSImage()];
1453     [closeButton setPressedImage:
1454         rb->GetNativeImageNamed(IDR_CLOSE_1_P).ToNSImage()];
1455     [closeButton setTarget:self];
1456     [closeButton setAction:@selector(dismissTutorial:)];
1457     [closeButton setFrameSize:[closeImage size]];
1458     [closeButton setFrameOrigin:NSMakePoint(
1459         kFixedMenuWidth - kHorizontalSpacing - closeImageWidth, yOffset)];
1460     [container addSubview:closeButton];
1461
1462     [titleLabel setFrameSize:NSMakeSize(
1463         availableWidth - closeImageWidth - kHorizontalSpacing, 0)];
1464   } else {
1465     [titleLabel setFrameSize:NSMakeSize(availableWidth, 0)];
1466   }
1467
1468   [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:titleLabel];
1469   [container addSubview:titleLabel];
1470   yOffset = NSMaxY([titleLabel frame]) + kVerticalSpacing;
1471
1472   [container setFrameSize:NSMakeSize(kFixedMenuWidth, yOffset)];
1473   [container setFrameOrigin:NSZeroPoint];
1474   return container.autorelease();
1475 }
1476
1477 - (NSView*)createCurrentProfileView:(const AvatarMenu::Item&)item {
1478   base::scoped_nsobject<NSView> container([[NSView alloc]
1479       initWithFrame:NSZeroRect]);
1480
1481   CGFloat xOffset = kHorizontalSpacing;
1482   CGFloat yOffset = 0;
1483   CGFloat availableTextWidth = kFixedMenuWidth - 2 * kHorizontalSpacing;
1484
1485   // Profile options. This can be a link to the accounts view, the profile's
1486   // username for signed in users, or a "Sign in" button for local profiles.
1487   SigninManagerBase* signinManager =
1488       SigninManagerFactory::GetForProfile(
1489           browser_->profile()->GetOriginalProfile());
1490   if (!isGuestSession_ && signinManager->IsSigninAllowed()) {
1491     NSView* linksContainer =
1492         [self createCurrentProfileLinksForItem:item
1493                                           rect:NSMakeRect(xOffset, yOffset,
1494                                                           availableTextWidth,
1495                                                           0)];
1496     [container addSubview:linksContainer];
1497     yOffset = NSMaxY([linksContainer frame]);
1498   }
1499
1500   // Profile name, centered.
1501   bool editingAllowed = !isGuestSession_ &&
1502                         !browser_->profile()->IsSupervised();
1503   base::scoped_nsobject<EditableProfileNameButton> profileName(
1504       [[EditableProfileNameButton alloc]
1505           initWithFrame:NSMakeRect(xOffset,
1506                                    yOffset,
1507                                    availableTextWidth,
1508                                    kProfileButtonHeight)
1509                 profile:browser_->profile()
1510             profileName:base::SysUTF16ToNSString(
1511                             profiles::GetAvatarNameForProfile(
1512                                 browser_->profile()->GetPath()))
1513          editingAllowed:editingAllowed
1514          withController:self]);
1515
1516   [container addSubview:profileName];
1517   yOffset = NSMaxY([profileName frame]) + 4;  // Adds a small vertical padding.
1518
1519   // Profile icon, centered.
1520   xOffset = (kFixedMenuWidth - kLargeImageSide) / 2;
1521   base::scoped_nsobject<EditableProfilePhoto> iconView(
1522       [[EditableProfilePhoto alloc]
1523           initWithFrame:NSMakeRect(xOffset, yOffset,
1524                                    kLargeImageSide, kLargeImageSide)
1525              avatarMenu:avatarMenu_.get()
1526             profileIcon:item.icon
1527          editingAllowed:!isGuestSession_
1528          withController:self]);
1529
1530   [container addSubview:iconView];
1531   yOffset = NSMaxY([iconView frame]);
1532
1533   if (browser_->profile()->IsSupervised()) {
1534     base::scoped_nsobject<NSImageView> supervisedIcon(
1535         [[NSImageView alloc] initWithFrame:NSZeroRect]);
1536     ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
1537     [supervisedIcon setImage:rb->GetNativeImageNamed(
1538         IDR_ICON_PROFILES_MENU_SUPERVISED).ToNSImage()];
1539     NSSize size = [[supervisedIcon image] size];
1540     [supervisedIcon setFrameSize:size];
1541     NSRect parentFrame = [iconView frame];
1542     [supervisedIcon setFrameOrigin:NSMakePoint(NSMaxX(parentFrame) - size.width,
1543                                                NSMinY(parentFrame))];
1544     [container addSubview:supervisedIcon];
1545   }
1546
1547   [container setFrameSize:NSMakeSize(kFixedMenuWidth, yOffset)];
1548   return container.autorelease();
1549 }
1550
1551 - (NSView*)createCurrentProfileLinksForItem:(const AvatarMenu::Item&)item
1552                                        rect:(NSRect)rect {
1553   base::scoped_nsobject<NSView> container([[NSView alloc] initWithFrame:rect]);
1554
1555   // Don't double-apply the left margin to the sub-views.
1556   rect.origin.x = 0;
1557
1558   // The available links depend on the type of profile that is active.
1559   if (item.signed_in) {
1560     rect.size.height = kBlueButtonHeight / 2;
1561     // Signed in profiles with no authentication errors do not have a clickable
1562     // email link.
1563     NSButton* link = nil;
1564     if (switches::IsEnableAccountConsistency()) {
1565       NSString* linkTitle = l10n_util::GetNSString(
1566           viewMode_ == profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER ?
1567               IDS_PROFILES_PROFILE_MANAGE_ACCOUNTS_BUTTON :
1568               IDS_PROFILES_PROFILE_HIDE_MANAGE_ACCOUNTS_BUTTON);
1569       SEL linkSelector =
1570           (viewMode_ == profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER) ?
1571           @selector(showAccountManagement:) : @selector(hideAccountManagement:);
1572       link = [self linkButtonWithTitle:linkTitle
1573                            frameOrigin:rect.origin
1574                                 action:linkSelector];
1575     } else {
1576       link = [self linkButtonWithTitle:base::SysUTF16ToNSString(item.sync_state)
1577                            frameOrigin:rect.origin
1578                                 action:nil];
1579       if (HasAuthError(browser_->profile())) {
1580         [link setImage:ui::ResourceBundle::GetSharedInstance().
1581             GetNativeImageNamed(IDR_ICON_PROFILES_ACCOUNT_BUTTON_ERROR).
1582             ToNSImage()];
1583         [link setImagePosition:NSImageRight];
1584         [link setTarget:self];
1585         [link setAction:@selector(showAccountReauthenticationView:)];
1586         [link setTag:kPrimaryProfileTag];
1587         [[link cell]
1588             accessibilitySetOverrideValue:l10n_util::GetNSStringF(
1589             IDS_PROFILES_ACCOUNT_BUTTON_AUTH_ERROR_ACCESSIBLE_NAME,
1590             item.sync_state)
1591                              forAttribute:NSAccessibilityTitleAttribute];
1592       } else {
1593         [link setEnabled:NO];
1594       }
1595     }
1596     // -linkButtonWithTitle sizeToFit's the link, so re-stretch it so that it
1597     // can be centered correctly in the view.
1598     [link setAlignment:NSCenterTextAlignment];
1599     [link setFrame:rect];
1600     [container addSubview:link];
1601     [container setFrameSize:rect.size];
1602   } else {
1603     rect.size.height = kBlueButtonHeight;
1604     NSButton* signinButton = [[BlueLabelButton alloc] initWithFrame:rect];
1605
1606     // Manually elide the button text so that the contents fit inside the bubble
1607     // This is needed because the BlueLabelButton cell resets the style on
1608     // every call to -cellSize, which prevents setting a custom lineBreakMode.
1609     NSString* elidedButtonText = base::SysUTF16ToNSString(gfx::ElideText(
1610         l10n_util::GetStringFUTF16(
1611             IDS_SYNC_START_SYNC_BUTTON_LABEL,
1612             l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME)),
1613         gfx::FontList(), rect.size.width, gfx::ELIDE_TAIL));
1614
1615     [signinButton setTitle:elidedButtonText];
1616     [signinButton setTarget:self];
1617     [signinButton setAction:@selector(showInlineSigninPage:)];
1618     [container addSubview:signinButton];
1619
1620     // Sign-in promo text.
1621     NSTextField* promo = BuildLabel(
1622         l10n_util::GetNSString(IDS_PROFILES_SIGNIN_PROMO),
1623         NSMakePoint(0, NSMaxY([signinButton frame]) + kVerticalSpacing),
1624         nil);
1625     [promo setFrameSize:NSMakeSize(rect.size.width, 0)];
1626     [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:promo];
1627     [container addSubview:promo];
1628
1629     [container setFrameSize:NSMakeSize(
1630         rect.size.width,
1631         NSMaxY([promo frame]) + 4)];  // Adds a small vertical padding.
1632   }
1633
1634   return container.autorelease();
1635 }
1636
1637 - (NSView*)createSupervisedUserDisclaimerView {
1638   base::scoped_nsobject<NSView> container(
1639       [[NSView alloc] initWithFrame:NSZeroRect]);
1640
1641   int yOffset = 0;
1642   int availableTextWidth = kFixedMenuWidth - 2 * kHorizontalSpacing;
1643
1644   NSTextField* disclaimer = BuildLabel(
1645       base::SysUTF16ToNSString(avatarMenu_->GetSupervisedUserInformation()),
1646       NSMakePoint(kHorizontalSpacing, yOffset), nil);
1647   [disclaimer setFrameSize:NSMakeSize(availableTextWidth, 0)];
1648   [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:disclaimer];
1649   yOffset = NSMaxY([disclaimer frame]);
1650
1651   [container addSubview:disclaimer];
1652   [container setFrameSize:NSMakeSize(kFixedMenuWidth, yOffset)];
1653   return container.autorelease();
1654 }
1655
1656 - (NSView*)createGuestProfileView {
1657   gfx::Image guestIcon =
1658       ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed(
1659           profiles::GetPlaceholderAvatarIconResourceID());
1660   AvatarMenu::Item guestItem(std::string::npos, /* menu_index, not used */
1661                              std::string::npos, /* profile_index, not used */
1662                              guestIcon);
1663   guestItem.active = true;
1664   guestItem.name = base::SysNSStringToUTF16(
1665       l10n_util::GetNSString(IDS_PROFILES_GUEST_PROFILE_NAME));
1666
1667   return [self createCurrentProfileView:guestItem];
1668 }
1669
1670 - (NSButton*)createOtherProfileView:(int)itemIndex {
1671   const AvatarMenu::Item& item = avatarMenu_->GetItemAt(itemIndex);
1672
1673   NSRect rect = NSMakeRect(0, 0, kFixedMenuWidth, kBlueButtonHeight);
1674   base::scoped_nsobject<BackgroundColorHoverButton> profileButton(
1675       [[BackgroundColorHoverButton alloc]
1676           initWithFrame:rect
1677       imageTitleSpacing:kImageTitleSpacing
1678         backgroundColor:GetDialogBackgroundColor()]);
1679   [profileButton setTitle:base::SysUTF16ToNSString(item.name)];
1680   [profileButton setDefaultImage:CreateProfileImage(
1681       item.icon, kSmallImageSide).ToNSImage()];
1682   [profileButton setImagePosition:NSImageLeft];
1683   [profileButton setAlignment:NSLeftTextAlignment];
1684   [profileButton setBordered:NO];
1685   [profileButton setTag:itemIndex];
1686   [profileButton setTarget:self];
1687   [profileButton setAction:@selector(switchToProfile:)];
1688
1689   return profileButton.autorelease();
1690 }
1691
1692 - (NSView*)createOptionsViewWithRect:(NSRect)rect
1693                           enableLock:(BOOL)enableLock {
1694   NSRect viewRect = NSMakeRect(0, 0,
1695                                rect.size.width,
1696                                kBlueButtonHeight + kSmallVerticalSpacing);
1697   base::scoped_nsobject<NSView> container([[NSView alloc] initWithFrame:rect]);
1698
1699   if (enableLock) {
1700     NSButton* lockButton =
1701         [self hoverButtonWithRect:viewRect
1702                              text:l10n_util::GetNSString(
1703                                   IDS_PROFILES_PROFILE_SIGNOUT_BUTTON)
1704                   imageResourceId:IDR_ICON_PROFILES_MENU_LOCK
1705                            action:@selector(lockProfile:)];
1706     [container addSubview:lockButton];
1707     viewRect.origin.y = NSMaxY([lockButton frame]);
1708
1709     NSBox* separator = [self horizontalSeparatorWithFrame:viewRect];
1710     [container addSubview:separator];
1711     viewRect.origin.y = NSMaxY([separator frame]);
1712   }
1713
1714   if ([self shouldShowGoIncognito]) {
1715     NSButton* goIncognitoButton =
1716         [self hoverButtonWithRect:viewRect
1717                              text:l10n_util::GetNSString(
1718                                   IDS_PROFILES_GO_INCOGNITO_BUTTON)
1719                   imageResourceId:IDR_ICON_PROFILES_MENU_INCOGNITO
1720                            action:@selector(goIncognito:)];
1721     viewRect.origin.y = NSMaxY([goIncognitoButton frame]);
1722     [container addSubview:goIncognitoButton];
1723
1724     NSBox* separator = [self horizontalSeparatorWithFrame:viewRect];
1725     [container addSubview:separator];
1726     viewRect.origin.y = NSMaxY([separator frame]);
1727   }
1728
1729   NSString* text = isGuestSession_ ?
1730       l10n_util::GetNSString(IDS_PROFILES_EXIT_GUEST) :
1731       l10n_util::GetNSString(IDS_PROFILES_SWITCH_USERS_BUTTON);
1732   NSButton* switchUsersButton =
1733       [self hoverButtonWithRect:viewRect
1734                            text:text
1735                 imageResourceId:IDR_ICON_PROFILES_MENU_AVATAR
1736                          action:isGuestSession_? @selector(exitGuest:) :
1737                                                  @selector(showUserManager:)];
1738   viewRect.origin.y = NSMaxY([switchUsersButton frame]);
1739   [container addSubview:switchUsersButton];
1740
1741   [container setFrameSize:NSMakeSize(rect.size.width, viewRect.origin.y)];
1742   return container.autorelease();
1743 }
1744
1745 - (NSView*)createCurrentProfileAccountsView:(NSRect)rect {
1746   const CGFloat kAccountButtonHeight = 34;
1747
1748   const AvatarMenu::Item& item =
1749       avatarMenu_->GetItemAt(avatarMenu_->GetActiveProfileIndex());
1750   DCHECK(item.signed_in);
1751
1752   NSColor* backgroundColor = gfx::SkColorToCalibratedNSColor(
1753       profiles::kAvatarBubbleAccountsBackgroundColor);
1754   base::scoped_nsobject<NSView> container([[BackgroundColorView alloc]
1755       initWithFrame:rect
1756           withColor:backgroundColor]);
1757
1758   rect.origin.y = 0;
1759   if (!browser_->profile()->IsSupervised()) {
1760     // Manually elide the button text so the contents fit inside the bubble.
1761     // This is needed because the BlueLabelButton cell resets the style on
1762     // every call to -cellSize, which prevents setting a custom lineBreakMode.
1763     NSString* elidedButtonText = base::SysUTF16ToNSString(gfx::ElideText(
1764         l10n_util::GetStringFUTF16(
1765             IDS_PROFILES_PROFILE_ADD_ACCOUNT_BUTTON, item.name),
1766         gfx::FontList(), rect.size.width, gfx::ELIDE_TAIL));
1767
1768     NSButton* addAccountsButton =
1769         [self linkButtonWithTitle:elidedButtonText
1770                       frameOrigin:NSMakePoint(
1771             kHorizontalSpacing, kSmallVerticalSpacing)
1772                            action:@selector(addAccount:)];
1773     [container addSubview:addAccountsButton];
1774     rect.origin.y += kAccountButtonHeight;
1775   }
1776
1777   NSView* accountEmails = [self createAccountsListWithRect:NSMakeRect(
1778       0, rect.origin.y, rect.size.width, kAccountButtonHeight)];
1779   [container addSubview:accountEmails];
1780
1781   [container setFrameSize:NSMakeSize(rect.size.width,
1782                                      NSMaxY([accountEmails frame]))];
1783   return container.autorelease();
1784 }
1785
1786 - (NSView*)createAccountsListWithRect:(NSRect)rect {
1787   base::scoped_nsobject<NSView> container([[NSView alloc] initWithFrame:rect]);
1788   currentProfileAccounts_.clear();
1789
1790   Profile* profile = browser_->profile();
1791   std::string primaryAccount =
1792       SigninManagerFactory::GetForProfile(profile)->GetAuthenticatedUsername();
1793   DCHECK(!primaryAccount.empty());
1794   std::vector<std::string>accounts =
1795       profiles::GetSecondaryAccountsForProfile(profile, primaryAccount);
1796
1797   // If there is an account with an authentication error, it needs to be
1798   // badged with a warning icon.
1799   const SigninErrorController* errorController =
1800       profiles::GetSigninErrorController(profile);
1801   std::string errorAccountId =
1802       errorController ? errorController->error_account_id() : std::string();
1803
1804   rect.origin.y = 0;
1805   for (size_t i = 0; i < accounts.size(); ++i) {
1806     // Save the original email address, as the button text could be elided.
1807     currentProfileAccounts_[i] = accounts[i];
1808     NSButton* accountButton =
1809         [self accountButtonWithRect:rect
1810                               title:accounts[i]
1811                                 tag:i
1812                      reauthRequired:errorAccountId == accounts[i]];
1813     [container addSubview:accountButton];
1814     rect.origin.y = NSMaxY([accountButton frame]);
1815   }
1816
1817   // The primary account should always be listed first.
1818   NSButton* accountButton =
1819       [self accountButtonWithRect:rect
1820                             title:primaryAccount
1821                               tag:kPrimaryProfileTag
1822                    reauthRequired:errorAccountId == primaryAccount];
1823   [container addSubview:accountButton];
1824   [container setFrameSize:NSMakeSize(NSWidth([container frame]),
1825                                      NSMaxY([accountButton frame]))];
1826   return container.autorelease();
1827 }
1828
1829 - (NSView*)buildGaiaEmbeddedView {
1830   base::scoped_nsobject<NSView> container(
1831       [[NSView alloc] initWithFrame:NSZeroRect]);
1832   CGFloat yOffset = 0;
1833
1834   GURL url;
1835   int messageId = -1;
1836   SigninErrorController* errorController = NULL;
1837   switch (viewMode_) {
1838     case profiles::BUBBLE_VIEW_MODE_GAIA_SIGNIN:
1839       url = signin::GetPromoURL(signin::SOURCE_AVATAR_BUBBLE_SIGN_IN,
1840                                 false /* auto_close */,
1841                                 true /* is_constrained */);
1842       messageId = IDS_PROFILES_GAIA_SIGNIN_TITLE;
1843       break;
1844     case profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT:
1845       url = signin::GetPromoURL(signin::SOURCE_AVATAR_BUBBLE_ADD_ACCOUNT,
1846                                 false /* auto_close */,
1847                                 true /* is_constrained */);
1848       messageId = IDS_PROFILES_GAIA_ADD_ACCOUNT_TITLE;
1849       break;
1850     case profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH:
1851       DCHECK(HasAuthError(browser_->profile()));
1852       errorController = profiles::GetSigninErrorController(browser_->profile());
1853       url = signin::GetReauthURL(
1854           browser_->profile(),
1855           errorController ? errorController->error_username() : std::string());
1856       messageId = IDS_PROFILES_GAIA_REAUTH_TITLE;
1857       break;
1858     default:
1859       NOTREACHED() << "Called with invalid mode=" << viewMode_;
1860       break;
1861   }
1862
1863   webContents_.reset(content::WebContents::Create(
1864       content::WebContents::CreateParams(browser_->profile())));
1865   webContents_->GetController().LoadURL(url,
1866                                         content::Referrer(),
1867                                         content::PAGE_TRANSITION_AUTO_TOPLEVEL,
1868                                         std::string());
1869   NSView* webview = webContents_->GetNativeView();
1870   [webview setFrameSize:NSMakeSize(kFixedGaiaViewWidth, kFixedGaiaViewHeight)];
1871   [container addSubview:webview];
1872   yOffset = NSMaxY([webview frame]);
1873
1874   // Adds the title card.
1875   NSBox* separator = [self horizontalSeparatorWithFrame:
1876       NSMakeRect(0, yOffset, kFixedGaiaViewWidth, 0)];
1877   [container addSubview:separator];
1878   yOffset = NSMaxY([separator frame]) + kSmallVerticalSpacing;
1879
1880   NSView* titleView = BuildTitleCard(
1881       NSMakeRect(0, yOffset, kFixedGaiaViewWidth, 0),
1882       l10n_util::GetStringUTF16(messageId),
1883       self /* backButtonTarget*/,
1884       @selector(navigateBackFromSigninPage:) /* backButtonAction */);
1885   [container addSubview:titleView];
1886   yOffset = NSMaxY([titleView frame]);
1887
1888   [container setFrameSize:NSMakeSize(kFixedGaiaViewWidth, yOffset)];
1889   return container.autorelease();
1890 }
1891
1892 - (NSView*)buildAccountRemovalView {
1893   DCHECK(!accountIdToRemove_.empty());
1894
1895   base::scoped_nsobject<NSView> container(
1896       [[NSView alloc] initWithFrame:NSZeroRect]);
1897   CGFloat availableWidth =
1898       kFixedAccountRemovalViewWidth - 2 * kHorizontalSpacing;
1899   CGFloat yOffset = kVerticalSpacing;
1900
1901   const std::string& primaryAccount = SigninManagerFactory::GetForProfile(
1902       browser_->profile())->GetAuthenticatedUsername();
1903   bool isPrimaryAccount = primaryAccount == accountIdToRemove_;
1904
1905   // Adds "remove account" button at the bottom if needed.
1906   if (!isPrimaryAccount) {
1907     base::scoped_nsobject<NSButton> removeAccountButton(
1908         [[BlueLabelButton alloc] initWithFrame:NSZeroRect]);
1909     [removeAccountButton setTitle:l10n_util::GetNSString(
1910         IDS_PROFILES_ACCOUNT_REMOVAL_BUTTON)];
1911     [removeAccountButton setTarget:self];
1912     [removeAccountButton setAction:@selector(removeAccount:)];
1913     [removeAccountButton sizeToFit];
1914     [removeAccountButton setAlignment:NSCenterTextAlignment];
1915     CGFloat xOffset = (kFixedAccountRemovalViewWidth -
1916         NSWidth([removeAccountButton frame])) / 2;
1917     [removeAccountButton setFrameOrigin:NSMakePoint(xOffset, yOffset)];
1918     [container addSubview:removeAccountButton];
1919
1920     yOffset = NSMaxY([removeAccountButton frame]) + kVerticalSpacing;
1921   }
1922
1923   NSView* contentView;
1924   NSPoint contentFrameOrigin = NSMakePoint(kHorizontalSpacing, yOffset);
1925   if (isPrimaryAccount) {
1926     std::vector<size_t> offsets;
1927     NSString* contentStr = l10n_util::GetNSStringF(
1928         IDS_PROFILES_PRIMARY_ACCOUNT_REMOVAL_TEXT,
1929         base::UTF8ToUTF16(accountIdToRemove_), base::string16(), &offsets);
1930     NSString* linkStr = l10n_util::GetNSString(IDS_PROFILES_SETTINGS_LINK);
1931     contentView = BuildFixedWidthTextViewWithLink(self, contentStr, linkStr,
1932         offsets[1], contentFrameOrigin, availableWidth);
1933   } else {
1934     NSString* contentStr =
1935         l10n_util::GetNSString(IDS_PROFILES_ACCOUNT_REMOVAL_TEXT);
1936     NSTextField* contentLabel = BuildLabel(contentStr, contentFrameOrigin, nil);
1937     [contentLabel setFrameSize:NSMakeSize(availableWidth, 0)];
1938     [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:contentLabel];
1939     contentView = contentLabel;
1940   }
1941   [container addSubview:contentView];
1942   yOffset = NSMaxY([contentView frame]) + kVerticalSpacing;
1943
1944   // Adds the title card.
1945   NSBox* separator = [self horizontalSeparatorWithFrame:
1946       NSMakeRect(0, yOffset, kFixedAccountRemovalViewWidth, 0)];
1947   [container addSubview:separator];
1948   yOffset = NSMaxY([separator frame]) + kSmallVerticalSpacing;
1949
1950   NSView* titleView = BuildTitleCard(
1951       NSMakeRect(0, yOffset, kFixedAccountRemovalViewWidth,0),
1952       l10n_util::GetStringUTF16(IDS_PROFILES_ACCOUNT_REMOVAL_TITLE),
1953       self /* backButtonTarget*/,
1954       @selector(showAccountManagement:) /* backButtonAction */);
1955   [container addSubview:titleView];
1956   yOffset = NSMaxY([titleView frame]);
1957
1958   [container setFrameSize:NSMakeSize(kFixedAccountRemovalViewWidth, yOffset)];
1959   return container.autorelease();
1960 }
1961
1962 - (NSView*)buildSwitchUserView {
1963   ProfileMetrics::LogProfileNewAvatarMenuNotYou(
1964       ProfileMetrics::PROFILE_AVATAR_MENU_NOT_YOU_VIEW);
1965   base::scoped_nsobject<NSView> container(
1966       [[NSView alloc] initWithFrame:NSZeroRect]);
1967   CGFloat availableWidth =
1968       kFixedSwitchUserViewWidth - 2 * kHorizontalSpacing;
1969   CGFloat yOffset = 0;
1970   NSRect viewRect = NSMakeRect(0, yOffset,
1971                                kFixedSwitchUserViewWidth,
1972                                kBlueButtonHeight + kSmallVerticalSpacing);
1973
1974   const AvatarMenu::Item& avatarItem =
1975       avatarMenu_->GetItemAt(avatarMenu_->GetActiveProfileIndex());
1976
1977   // Adds "Disconnect your Google Account" button at the bottom.
1978   NSButton* disconnectButton =
1979       [self hoverButtonWithRect:viewRect
1980                            text:l10n_util::GetNSString(
1981                                     IDS_PROFILES_DISCONNECT_BUTTON)
1982                 imageResourceId:IDR_ICON_PROFILES_MENU_DISCONNECT
1983                          action:@selector(disconnectProfile:)];
1984   [container addSubview:disconnectButton];
1985   yOffset = NSMaxY([disconnectButton frame]);
1986
1987   NSBox* separator = [self horizontalSeparatorWithFrame:
1988       NSMakeRect(0, yOffset, kFixedMenuWidth, 0)];
1989   [container addSubview:separator];
1990   yOffset = NSMaxY([separator frame]);
1991
1992   // Adds "Add person" button.
1993   viewRect.origin.y = yOffset;
1994   NSButton* addPersonButton =
1995       [self hoverButtonWithRect:viewRect
1996                            text:l10n_util::GetNSString(
1997                                     IDS_PROFILES_ADD_PERSON_BUTTON)
1998                 imageResourceId:IDR_ICON_PROFILES_MENU_AVATAR
1999                          action:@selector(showUserManager:)];
2000   [container addSubview:addPersonButton];
2001   yOffset = NSMaxY([addPersonButton frame]);
2002
2003   separator = [self horizontalSeparatorWithFrame:
2004       NSMakeRect(0, yOffset, kFixedMenuWidth, 0)];
2005   [container addSubview:separator];
2006   yOffset = NSMaxY([separator frame]);
2007
2008   // Adds the content text.
2009   base::string16 elidedName(gfx::ElideText(
2010       avatarItem.name, gfx::FontList(), availableWidth, gfx::ELIDE_TAIL));
2011   NSTextField* contentLabel = BuildLabel(
2012       l10n_util::GetNSStringF(IDS_PROFILES_NOT_YOU_CONTENT_TEXT, elidedName),
2013       NSMakePoint(kHorizontalSpacing, yOffset + kVerticalSpacing),
2014       nil);
2015   [contentLabel setFrameSize:NSMakeSize(availableWidth, 0)];
2016   [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:contentLabel];
2017   [container addSubview:contentLabel];
2018   yOffset = NSMaxY([contentLabel frame]) + kVerticalSpacing;
2019
2020   // Adds the title card.
2021   separator = [self horizontalSeparatorWithFrame:
2022       NSMakeRect(0, yOffset, kFixedSwitchUserViewWidth, 0)];
2023   [container addSubview:separator];
2024   yOffset = NSMaxY([separator frame]) + kSmallVerticalSpacing;
2025
2026   NSView* titleView = BuildTitleCard(
2027       NSMakeRect(0, yOffset, kFixedSwitchUserViewWidth,0),
2028       l10n_util::GetStringFUTF16(IDS_PROFILES_NOT_YOU, avatarItem.name),
2029       self /* backButtonTarget*/,
2030       @selector(navigateBackFromSwitchUserView:) /* backButtonAction */);
2031   [container addSubview:titleView];
2032   yOffset = NSMaxY([titleView frame]);
2033
2034   [container setFrameSize:NSMakeSize(kFixedAccountRemovalViewWidth, yOffset)];
2035   return container.autorelease();
2036 }
2037
2038 // Called when clicked on the settings link.
2039 - (BOOL)textView:(NSTextView*)textView
2040    clickedOnLink:(id)link
2041          atIndex:(NSUInteger)charIndex {
2042   chrome::ShowSettings(browser_);
2043   return YES;
2044 }
2045
2046 - (NSButton*)hoverButtonWithRect:(NSRect)rect
2047                             text:(NSString*)text
2048                  imageResourceId:(int)imageResourceId
2049                           action:(SEL)action {
2050   base::scoped_nsobject<BackgroundColorHoverButton> button(
2051       [[BackgroundColorHoverButton alloc]
2052           initWithFrame:rect
2053       imageTitleSpacing:kImageTitleSpacing
2054         backgroundColor:GetDialogBackgroundColor()]);
2055
2056   [button setTitle:text];
2057   ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
2058   NSImage* image = rb->GetNativeImageNamed(imageResourceId).ToNSImage();
2059   [button setDefaultImage:image];
2060   [button setHoverImage:image];
2061   [button setPressedImage:image];
2062   [button setImagePosition:NSImageLeft];
2063   [button setAlignment:NSLeftTextAlignment];
2064   [button setBordered:NO];
2065   [button setTarget:self];
2066   [button setAction:action];
2067
2068   return button.autorelease();
2069 }
2070
2071 - (NSButton*)linkButtonWithTitle:(NSString*)title
2072                      frameOrigin:(NSPoint)frameOrigin
2073                           action:(SEL)action {
2074   base::scoped_nsobject<NSButton> link(
2075       [[HyperlinkButtonCell buttonWithString:title] retain]);
2076
2077   [[link cell] setShouldUnderline:NO];
2078   [[link cell] setTextColor:gfx::SkColorToCalibratedNSColor(
2079       chrome_style::GetLinkColor())];
2080   [link setTitle:title];
2081   [link setBordered:NO];
2082   [link setFont:[NSFont labelFontOfSize:kTextFontSize]];
2083   [link setTarget:self];
2084   [link setAction:action];
2085   [link setFrameOrigin:frameOrigin];
2086   [link sizeToFit];
2087
2088   return link.autorelease();
2089 }
2090
2091 - (NSButton*)accountButtonWithRect:(NSRect)rect
2092                              title:(const std::string&)title
2093                                tag:(int)tag
2094                     reauthRequired:(BOOL)reauthRequired {
2095   ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
2096   NSImage* deleteImage = rb->GetNativeImageNamed(IDR_CLOSE_1).ToNSImage();
2097   CGFloat deleteImageWidth = [deleteImage size].width;
2098   NSImage* warningImage = reauthRequired ? rb->GetNativeImageNamed(
2099       IDR_ICON_PROFILES_ACCOUNT_BUTTON_ERROR).ToNSImage() : nil;
2100   CGFloat warningImageWidth = [warningImage size].width;
2101
2102   CGFloat availableTextWidth = rect.size.width - kHorizontalSpacing -
2103       warningImageWidth - deleteImageWidth;
2104   if (warningImage)
2105     availableTextWidth -= kHorizontalSpacing;
2106
2107   NSColor* backgroundColor = gfx::SkColorToCalibratedNSColor(
2108       profiles::kAvatarBubbleAccountsBackgroundColor);
2109   base::scoped_nsobject<BackgroundColorHoverButton> button(
2110       [[BackgroundColorHoverButton alloc] initWithFrame:rect
2111                                       imageTitleSpacing:0
2112                                         backgroundColor:backgroundColor]);
2113   [button setTitle:ElideEmail(title, availableTextWidth)];
2114   [button setAlignment:NSLeftTextAlignment];
2115   [button setBordered:NO];
2116   if (reauthRequired) {
2117     [button setDefaultImage:warningImage];
2118     [button setImagePosition:NSImageLeft];
2119     [button setTarget:self];
2120     [button setAction:@selector(showAccountReauthenticationView:)];
2121     [button setTag:tag];
2122   }
2123
2124   // Delete button.
2125   if (!browser_->profile()->IsSupervised()) {
2126     NSRect buttonRect;
2127     NSDivideRect(rect, &buttonRect, &rect,
2128         deleteImageWidth + kHorizontalSpacing, NSMaxXEdge);
2129     buttonRect.origin.y = 0;
2130
2131     base::scoped_nsobject<HoverImageButton> deleteButton(
2132         [[HoverImageButton alloc] initWithFrame:buttonRect]);
2133     [deleteButton setBordered:NO];
2134     [deleteButton setDefaultImage:deleteImage];
2135     [deleteButton setHoverImage:rb->GetNativeImageNamed(
2136         IDR_CLOSE_1_H).ToNSImage()];
2137     [deleteButton setPressedImage:rb->GetNativeImageNamed(
2138         IDR_CLOSE_1_P).ToNSImage()];
2139     [deleteButton setTarget:self];
2140     [deleteButton setAction:@selector(showAccountRemovalView:)];
2141     [deleteButton setTag:tag];
2142
2143     [button addSubview:deleteButton];
2144   }
2145
2146   return button.autorelease();
2147 }
2148
2149 - (void)postActionPerformed:(ProfileMetrics::ProfileDesktopMenu)action {
2150   ProfileMetrics::LogProfileDesktopMenu(action, serviceType_);
2151   serviceType_ = signin::GAIA_SERVICE_TYPE_NONE;
2152 }
2153
2154 - (bool)shouldShowGoIncognito {
2155   bool incognitoAvailable =
2156       IncognitoModePrefs::GetAvailability(browser_->profile()->GetPrefs()) !=
2157           IncognitoModePrefs::DISABLED;
2158   return incognitoAvailable && !browser_->profile()->IsGuestSession();
2159 }
2160
2161 @end