1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "chrome/browser/ui/cocoa/browser/avatar_icon_controller.h"
7 #include "base/strings/sys_string_conversions.h"
8 #include "chrome/browser/browser_process.h"
9 #include "chrome/browser/profiles/profile.h"
10 #include "chrome/browser/profiles/profile_info_cache.h"
11 #include "chrome/browser/profiles/profile_info_util.h"
12 #include "chrome/browser/profiles/profile_manager.h"
13 #include "chrome/browser/ui/browser.h"
14 #include "chrome/browser/ui/browser_window.h"
15 #import "chrome/browser/ui/cocoa/browser/avatar_label_button.h"
16 #import "chrome/browser/ui/cocoa/browser_window_controller.h"
17 #include "grit/generated_resources.h"
18 #include "grit/theme_resources.h"
19 #include "ui/base/l10n/l10n_util_mac.h"
20 #include "ui/base/resource/resource_bundle.h"
21 #include "ui/gfx/image/image.h"
22 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
26 // Space between the avatar label and the left edge of the container containing
27 // the label and the icon.
28 const CGFloat kAvatarSpacing = 4;
30 // Space between the bottom of the avatar icon and the bottom of the avatar
32 const CGFloat kAvatarLabelBottomSpacing = 3;
34 // Space between the right edge of the avatar label and the right edge of the
36 const CGFloat kAvatarLabelRightSpacing = 2;
40 @interface AvatarIconController (Private)
41 - (void)setButtonEnabled:(BOOL)flag;
42 - (NSImage*)compositeImageWithShadow:(NSImage*)image;
43 - (void)updateAvatarButtonAndLayoutParent:(BOOL)layoutParent;
44 - (void)addOrRemoveButtonIfNecessary;
47 // Declare a 10.7+ private API.
48 // NSThemeFrame < NSTitledFrame < NSFrameView < NSView.
49 @interface NSView (NSThemeFrame)
50 - (void)_tileTitlebarAndRedisplay:(BOOL)redisplay;
53 @implementation AvatarIconController
55 - (id)initWithBrowser:(Browser*)browser {
56 if ((self = [super initWithBrowser:browser])) {
59 base::scoped_nsobject<NSView> container(
60 [[NSView alloc] initWithFrame:NSMakeRect(
61 0, 0, profiles::kAvatarIconWidth, profiles::kAvatarIconHeight)]);
62 [self setView:container];
64 button_.reset([[NSButton alloc] initWithFrame:NSMakeRect(
65 0, 0, profiles::kAvatarIconWidth, profiles::kAvatarIconHeight)]);
66 NSButtonCell* cell = [button_ cell];
67 [button_ setButtonType:NSMomentaryLightButton];
69 [button_ setImagePosition:NSImageOnly];
70 [cell setImageScaling:NSImageScaleProportionallyDown];
71 [cell setImagePosition:NSImageBelow];
73 // AppKit sets a title for some reason when using |-setImagePosition:|.
74 [button_ setTitle:nil];
76 [cell setImageDimsWhenDisabled:NO];
77 [cell setHighlightsBy:NSContentsCellMask];
78 [cell setShowsStateBy:NSContentsCellMask];
80 [button_ setBordered:NO];
81 [button_ setTarget:self];
82 [button_ setAction:@selector(buttonClicked:)];
84 [cell accessibilitySetOverrideValue:NSAccessibilityButtonRole
85 forAttribute:NSAccessibilityRoleAttribute];
86 [cell accessibilitySetOverrideValue:
87 NSAccessibilityRoleDescription(NSAccessibilityButtonRole, nil)
88 forAttribute:NSAccessibilityRoleDescriptionAttribute];
89 [cell accessibilitySetOverrideValue:
90 l10n_util::GetNSString(IDS_PROFILES_BUBBLE_ACCESSIBLE_NAME)
91 forAttribute:NSAccessibilityTitleAttribute];
92 [cell accessibilitySetOverrideValue:
93 l10n_util::GetNSString(IDS_PROFILES_BUBBLE_ACCESSIBLE_DESCRIPTION)
94 forAttribute:NSAccessibilityHelpAttribute];
95 [cell accessibilitySetOverrideValue:
96 l10n_util::GetNSString(IDS_PROFILES_BUBBLE_ACCESSIBLE_DESCRIPTION)
97 forAttribute:NSAccessibilityDescriptionAttribute];
99 Profile* profile = browser_->profile();
101 if (profile->IsOffTheRecord() || profile->IsGuestSession()) {
102 const int icon_id = profile->IsGuestSession() ? IDR_LOGIN_GUEST :
104 NSImage* icon = ResourceBundle::GetSharedInstance().GetNativeImageNamed(
105 icon_id).ToNSImage();
106 [self setImage:[self compositeImageWithShadow:icon]];
107 [self setButtonEnabled:profile->IsGuestSession()];
109 [self setButtonEnabled:YES];
110 [self updateAvatarButtonAndLayoutParent:NO];
112 // Managed users cannot enter incognito mode, so we only need to check
113 // it in this code path.
114 if (profile->IsManaged()) {
115 // Initialize the avatar label button.
117 profiles::kAvatarIconWidth + kAvatarLabelRightSpacing;
118 NSRect frame = NSMakeRect(
119 kAvatarSpacing, kAvatarLabelBottomSpacing, extraWidth, 0);
120 labelButton_.reset([[AvatarLabelButton alloc] initWithFrame:frame]);
121 [labelButton_ setTarget:self];
122 [labelButton_ setAction:@selector(buttonClicked:)];
123 [[self view] addSubview:labelButton_];
125 // Resize the container and reposition the avatar button.
126 NSSize textSize = [[labelButton_ cell] labelTextSize];
127 [container setFrameSize:
128 NSMakeSize([labelButton_ frame].size.width + kAvatarSpacing,
129 profiles::kAvatarIconHeight)];
131 setFrameOrigin:NSMakePoint(kAvatarSpacing + textSize.width, 0)];
134 [[self view] addSubview:button_];
139 - (NSButton*)labelButtonView {
140 return labelButton_.get();
143 - (void)setImage:(NSImage*)image {
144 [button_ setImage:image];
147 - (void)setButtonEnabled:(BOOL)flag {
148 [button_ setEnabled:flag];
151 // This will take in an original image and redraw it with a shadow.
152 - (NSImage*)compositeImageWithShadow:(NSImage*)image {
153 gfx::ScopedNSGraphicsContextSaveGState scopedGState;
155 base::scoped_nsobject<NSImage> destination(
156 [[NSImage alloc] initWithSize:[image size]]);
158 NSRect destRect = NSZeroRect;
159 destRect.size = [destination size];
161 [destination lockFocus];
163 base::scoped_nsobject<NSShadow> shadow([[NSShadow alloc] init]);
164 [shadow.get() setShadowColor:[NSColor colorWithCalibratedWhite:0.0
166 [shadow.get() setShadowOffset:NSZeroSize];
167 [shadow.get() setShadowBlurRadius:3.0];
170 [image drawInRect:destRect
172 operation:NSCompositeSourceOver
177 [destination unlockFocus];
179 return destination.autorelease();
182 // Updates the avatar information from the profile cache.
183 - (void)updateAvatarButtonAndLayoutParent:(BOOL)layoutParent {
184 // No updates are needed for an incognito or guest window, as the avatar
186 Profile* profile = browser_->profile();
187 if (profile->IsOffTheRecord() || profile->IsGuestSession())
190 ProfileInfoCache& cache =
191 g_browser_process->profile_manager()->GetProfileInfoCache();
193 cache.GetIndexOfProfileWithPath(browser_->profile()->GetPath());
194 if (index == std::string::npos)
197 BOOL is_gaia_picture =
198 cache.IsUsingGAIAPictureOfProfileAtIndex(index) &&
199 cache.GetGAIAPictureOfProfileAtIndex(index);
200 gfx::Image icon = profiles::GetAvatarIconForTitleBar(
201 cache.GetAvatarIconOfProfileAtIndex(index), is_gaia_picture,
202 profiles::kAvatarIconWidth, profiles::kAvatarIconHeight);
203 [self setImage:icon.ToNSImage()];
205 const base::string16& name = cache.GetNameOfProfileAtIndex(index);
206 NSString* nsName = base::SysUTF16ToNSString(name);
207 [button_ setToolTip:nsName];
209 accessibilitySetOverrideValue:nsName
210 forAttribute:NSAccessibilityValueAttribute];
212 [self addOrRemoveButtonIfNecessary];
215 // If the second-to-last profile was removed or a second profile was added,
216 // show or hide the avatar button from the window frame.
217 - (void)addOrRemoveButtonIfNecessary {
218 if (browser_->profile()->IsOffTheRecord())
221 NSWindowController* wc =
222 [browser_->window()->GetNativeWindow() windowController];
223 if (![wc isKindOfClass:[BrowserWindowController class]])
226 size_t count = g_browser_process->profile_manager()->GetNumberOfProfiles();
227 [self.view setHidden:count < 2];
229 [static_cast<BrowserWindowController*>(wc) layoutSubviews];
231 // If the avatar is being added or removed, then the Lion fullscreen button
232 // needs to be adjusted. Since the fullscreen button is positioned by
233 // FramedBrowserWindow using private APIs, the easiest way to update the
234 // position of the button is through this private API. Resizing the window
235 // also works, but invoking |-display| does not.
236 NSView* themeFrame = [[[wc window] contentView] superview];
237 if ([themeFrame respondsToSelector:@selector(_tileTitlebarAndRedisplay:)])
238 [themeFrame _tileTitlebarAndRedisplay:YES];