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