1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "chrome/browser/ui/cocoa/profiles/avatar_icon_controller.h"
7 #include "base/mac/foundation_util.h"
8 #include "base/strings/sys_string_conversions.h"
9 #include "chrome/browser/browser_process.h"
10 #include "chrome/browser/profiles/profile.h"
11 #include "chrome/browser/profiles/profile_avatar_icon_util.h"
12 #include "chrome/browser/profiles/profile_info_cache.h"
13 #include "chrome/browser/profiles/profile_manager.h"
14 #include "chrome/browser/ui/browser.h"
15 #include "chrome/browser/ui/browser_window.h"
16 #import "chrome/browser/ui/cocoa/browser_window_controller.h"
17 #import "chrome/browser/ui/cocoa/profiles/avatar_label_button.h"
18 #include "chrome/grit/generated_resources.h"
19 #include "grit/theme_resources.h"
20 #include "ui/base/l10n/l10n_util_mac.h"
21 #include "ui/base/resource/resource_bundle.h"
22 #include "ui/gfx/image/image.h"
23 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
27 // Space between the avatar label and the left edge of the container containing
28 // the label and the icon.
29 const CGFloat kAvatarSpacing = 4;
31 // Space between the bottom of the avatar icon and the bottom of the avatar
33 const CGFloat kAvatarLabelBottomSpacing = 3;
35 // Space between the right edge of the avatar label and the right edge of the
37 const CGFloat kAvatarLabelRightSpacing = 2;
41 @interface AvatarIconController (Private)
42 - (void)setButtonEnabled:(BOOL)flag;
43 - (NSImage*)compositeImageWithShadow:(NSImage*)image;
44 - (void)updateAvatarButtonAndLayoutParent:(BOOL)layoutParent;
45 - (void)addOrRemoveButtonIfNecessary;
48 // Declare a 10.7+ private API.
49 // NSThemeFrame < NSTitledFrame < NSFrameView < NSView.
50 @interface NSView (NSThemeFrame)
51 - (void)_tileTitlebarAndRedisplay:(BOOL)redisplay;
54 @implementation AvatarIconController
56 - (id)initWithBrowser:(Browser*)browser {
57 if ((self = [super initWithBrowser:browser])) {
60 base::scoped_nsobject<NSView> container(
61 [[NSView alloc] initWithFrame:NSMakeRect(
62 0, 0, profiles::kAvatarIconWidth, profiles::kAvatarIconHeight)]);
63 [container setWantsLayer:YES];
64 [self setView:container];
66 button_.reset([[NSButton alloc] initWithFrame:NSMakeRect(
67 0, 0, profiles::kAvatarIconWidth, profiles::kAvatarIconHeight)]);
68 NSButtonCell* cell = [button_ cell];
69 [button_ setButtonType:NSMomentaryLightButton];
71 [button_ setImagePosition:NSImageOnly];
72 [cell setImageScaling:NSImageScaleProportionallyDown];
73 [cell setImagePosition:NSImageBelow];
75 // AppKit sets a title for some reason when using |-setImagePosition:|.
76 [button_ setTitle:nil];
78 [cell setImageDimsWhenDisabled:NO];
79 [cell setHighlightsBy:NSContentsCellMask];
80 [cell setShowsStateBy:NSContentsCellMask];
82 [button_ setBordered:NO];
83 [button_ setTarget:self];
84 [button_ setAction:@selector(buttonClicked:)];
86 [cell accessibilitySetOverrideValue:NSAccessibilityButtonRole
87 forAttribute:NSAccessibilityRoleAttribute];
88 [cell accessibilitySetOverrideValue:
89 NSAccessibilityRoleDescription(NSAccessibilityButtonRole, nil)
90 forAttribute:NSAccessibilityRoleDescriptionAttribute];
91 [cell accessibilitySetOverrideValue:
92 l10n_util::GetNSString(IDS_PROFILES_BUBBLE_ACCESSIBLE_NAME)
93 forAttribute:NSAccessibilityTitleAttribute];
94 [cell accessibilitySetOverrideValue:
95 l10n_util::GetNSString(IDS_PROFILES_BUBBLE_ACCESSIBLE_DESCRIPTION)
96 forAttribute:NSAccessibilityHelpAttribute];
97 [cell accessibilitySetOverrideValue:
98 l10n_util::GetNSString(IDS_PROFILES_BUBBLE_ACCESSIBLE_DESCRIPTION)
99 forAttribute:NSAccessibilityDescriptionAttribute];
101 Profile* profile = browser_->profile();
103 if (profile->IsOffTheRecord() || profile->IsGuestSession()) {
104 const int icon_id = profile->IsGuestSession() ?
105 profiles::GetPlaceholderAvatarIconResourceID() : IDR_OTR_ICON;
106 NSImage* icon = ResourceBundle::GetSharedInstance().GetNativeImageNamed(
107 icon_id).ToNSImage();
108 [self setImage:[self compositeImageWithShadow:icon]];
109 [self setButtonEnabled:profile->IsGuestSession()];
111 [self setButtonEnabled:YES];
112 [self updateAvatarButtonAndLayoutParent:NO];
114 // Supervised users cannot enter incognito mode, so we only need to check
115 // it in this code path.
116 if (profile->IsSupervised()) {
117 // Initialize the avatar label button.
119 profiles::kAvatarIconWidth + kAvatarLabelRightSpacing;
120 NSRect frame = NSMakeRect(
121 kAvatarSpacing, kAvatarLabelBottomSpacing, extraWidth, 0);
122 labelButton_.reset([[AvatarLabelButton alloc] initWithFrame:frame]);
123 [labelButton_ setTarget:self];
124 [labelButton_ setAction:@selector(buttonClicked:)];
125 [[self view] addSubview:labelButton_];
127 // Resize the container and reposition the avatar button.
128 NSSize textSize = [[labelButton_ cell] labelTextSize];
129 [container setFrameSize:
130 NSMakeSize([labelButton_ frame].size.width + kAvatarSpacing,
131 profiles::kAvatarIconHeight)];
133 setFrameOrigin:NSMakePoint(kAvatarSpacing + textSize.width, 0)];
136 [[self view] addSubview:button_];
141 - (NSButton*)labelButtonView {
142 return labelButton_.get();
145 - (void)setImage:(NSImage*)image {
146 [button_ setImage:image];
149 - (void)setButtonEnabled:(BOOL)flag {
150 [button_ setEnabled:flag];
153 // This will take in an original image and redraw it with a shadow.
154 - (NSImage*)compositeImageWithShadow:(NSImage*)image {
155 gfx::ScopedNSGraphicsContextSaveGState scopedGState;
157 base::scoped_nsobject<NSImage> destination(
158 [[NSImage alloc] initWithSize:[image size]]);
160 NSRect destRect = NSZeroRect;
161 destRect.size = [destination size];
163 [destination lockFocus];
165 base::scoped_nsobject<NSShadow> shadow([[NSShadow alloc] init]);
166 [shadow.get() setShadowColor:[NSColor colorWithCalibratedWhite:0.0
168 [shadow.get() setShadowOffset:NSZeroSize];
169 [shadow.get() setShadowBlurRadius:3.0];
172 [image drawInRect:destRect
174 operation:NSCompositeSourceOver
179 [destination unlockFocus];
181 return destination.autorelease();
184 // Updates the avatar information from the profile cache.
185 - (void)updateAvatarButtonAndLayoutParent:(BOOL)layoutParent {
186 // No updates are needed for an incognito or guest window, as the avatar
188 Profile* profile = browser_->profile();
189 if (profile->IsOffTheRecord() || profile->IsGuestSession())
192 ProfileInfoCache& cache =
193 g_browser_process->profile_manager()->GetProfileInfoCache();
195 cache.GetIndexOfProfileWithPath(browser_->profile()->GetPath());
196 if (index == std::string::npos)
199 BOOL is_gaia_picture =
200 cache.IsUsingGAIAPictureOfProfileAtIndex(index) &&
201 cache.GetGAIAPictureOfProfileAtIndex(index);
202 gfx::Image icon = profiles::GetAvatarIconForTitleBar(
203 cache.GetAvatarIconOfProfileAtIndex(index), is_gaia_picture,
204 profiles::kAvatarIconWidth, profiles::kAvatarIconHeight);
205 [self setImage:icon.ToNSImage()];
207 const base::string16& name = cache.GetNameOfProfileAtIndex(index);
208 NSString* nsName = base::SysUTF16ToNSString(name);
209 [button_ setToolTip:nsName];
211 accessibilitySetOverrideValue:nsName
212 forAttribute:NSAccessibilityValueAttribute];
214 [self addOrRemoveButtonIfNecessary];
217 // If the second-to-last profile was removed or a second profile was added,
218 // show or hide the avatar button from the window frame.
219 - (void)addOrRemoveButtonIfNecessary {
220 if (browser_->profile()->IsOffTheRecord())
223 BrowserWindowController* wc = base::mac::ObjCCast<BrowserWindowController>(
224 [browser_->window()->GetNativeWindow() windowController]);
228 [self.view setHidden:![wc shouldShowAvatar]];
230 // If the avatar is being added or removed, then the Lion fullscreen button
231 // needs to be adjusted. Since the fullscreen button is positioned by
232 // FramedBrowserWindow using private APIs, the easiest way to update the
233 // position of the button is through this private API. Resizing the window
234 // also works, but invoking |-display| does not.
235 NSView* themeFrame = [[[wc window] contentView] superview];
236 if ([themeFrame respondsToSelector:@selector(_tileTitlebarAndRedisplay:)])
237 [themeFrame _tileTitlebarAndRedisplay:YES];
239 // This needs to be called after the fullscreen button is positioned, because
240 // TabStripController's rightIndentForControls depends on it.