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/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_avatar_icon_util.h"
11 #include "chrome/browser/profiles/profile_info_cache.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_window_controller.h"
16 #import "chrome/browser/ui/cocoa/profiles/avatar_label_button.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 [container setWantsLayer:YES];
63 [self setView:container];
65 button_.reset([[NSButton alloc] initWithFrame:NSMakeRect(
66 0, 0, profiles::kAvatarIconWidth, profiles::kAvatarIconHeight)]);
67 NSButtonCell* cell = [button_ cell];
68 [button_ setButtonType:NSMomentaryLightButton];
70 [button_ setImagePosition:NSImageOnly];
71 [cell setImageScaling:NSImageScaleProportionallyDown];
72 [cell setImagePosition:NSImageBelow];
74 // AppKit sets a title for some reason when using |-setImagePosition:|.
75 [button_ setTitle:nil];
77 [cell setImageDimsWhenDisabled:NO];
78 [cell setHighlightsBy:NSContentsCellMask];
79 [cell setShowsStateBy:NSContentsCellMask];
81 [button_ setBordered:NO];
82 [button_ setTarget:self];
83 [button_ setAction:@selector(buttonClicked:)];
85 [cell accessibilitySetOverrideValue:NSAccessibilityButtonRole
86 forAttribute:NSAccessibilityRoleAttribute];
87 [cell accessibilitySetOverrideValue:
88 NSAccessibilityRoleDescription(NSAccessibilityButtonRole, nil)
89 forAttribute:NSAccessibilityRoleDescriptionAttribute];
90 [cell accessibilitySetOverrideValue:
91 l10n_util::GetNSString(IDS_PROFILES_BUBBLE_ACCESSIBLE_NAME)
92 forAttribute:NSAccessibilityTitleAttribute];
93 [cell accessibilitySetOverrideValue:
94 l10n_util::GetNSString(IDS_PROFILES_BUBBLE_ACCESSIBLE_DESCRIPTION)
95 forAttribute:NSAccessibilityHelpAttribute];
96 [cell accessibilitySetOverrideValue:
97 l10n_util::GetNSString(IDS_PROFILES_BUBBLE_ACCESSIBLE_DESCRIPTION)
98 forAttribute:NSAccessibilityDescriptionAttribute];
100 Profile* profile = browser_->profile();
102 if (profile->IsOffTheRecord() || profile->IsGuestSession()) {
103 const int icon_id = profile->IsGuestSession() ?
104 profiles::GetPlaceholderAvatarIconResourceID() : IDR_OTR_ICON;
105 NSImage* icon = ResourceBundle::GetSharedInstance().GetNativeImageNamed(
106 icon_id).ToNSImage();
107 [self setImage:[self compositeImageWithShadow:icon]];
108 [self setButtonEnabled:profile->IsGuestSession()];
110 [self setButtonEnabled:YES];
111 [self updateAvatarButtonAndLayoutParent:NO];
113 // Supervised users cannot enter incognito mode, so we only need to check
114 // it in this code path.
115 if (profile->IsSupervised()) {
116 // Initialize the avatar label button.
118 profiles::kAvatarIconWidth + kAvatarLabelRightSpacing;
119 NSRect frame = NSMakeRect(
120 kAvatarSpacing, kAvatarLabelBottomSpacing, extraWidth, 0);
121 labelButton_.reset([[AvatarLabelButton alloc] initWithFrame:frame]);
122 [labelButton_ setTarget:self];
123 [labelButton_ setAction:@selector(buttonClicked:)];
124 [[self view] addSubview:labelButton_];
126 // Resize the container and reposition the avatar button.
127 NSSize textSize = [[labelButton_ cell] labelTextSize];
128 [container setFrameSize:
129 NSMakeSize([labelButton_ frame].size.width + kAvatarSpacing,
130 profiles::kAvatarIconHeight)];
132 setFrameOrigin:NSMakePoint(kAvatarSpacing + textSize.width, 0)];
135 [[self view] addSubview:button_];
140 - (NSButton*)labelButtonView {
141 return labelButton_.get();
144 - (void)setImage:(NSImage*)image {
145 [button_ setImage:image];
148 - (void)setButtonEnabled:(BOOL)flag {
149 [button_ setEnabled:flag];
152 // This will take in an original image and redraw it with a shadow.
153 - (NSImage*)compositeImageWithShadow:(NSImage*)image {
154 gfx::ScopedNSGraphicsContextSaveGState scopedGState;
156 base::scoped_nsobject<NSImage> destination(
157 [[NSImage alloc] initWithSize:[image size]]);
159 NSRect destRect = NSZeroRect;
160 destRect.size = [destination size];
162 [destination lockFocus];
164 base::scoped_nsobject<NSShadow> shadow([[NSShadow alloc] init]);
165 [shadow.get() setShadowColor:[NSColor colorWithCalibratedWhite:0.0
167 [shadow.get() setShadowOffset:NSZeroSize];
168 [shadow.get() setShadowBlurRadius:3.0];
171 [image drawInRect:destRect
173 operation:NSCompositeSourceOver
178 [destination unlockFocus];
180 return destination.autorelease();
183 // Updates the avatar information from the profile cache.
184 - (void)updateAvatarButtonAndLayoutParent:(BOOL)layoutParent {
185 // No updates are needed for an incognito or guest window, as the avatar
187 Profile* profile = browser_->profile();
188 if (profile->IsOffTheRecord() || profile->IsGuestSession())
191 ProfileInfoCache& cache =
192 g_browser_process->profile_manager()->GetProfileInfoCache();
194 cache.GetIndexOfProfileWithPath(browser_->profile()->GetPath());
195 if (index == std::string::npos)
198 BOOL is_gaia_picture =
199 cache.IsUsingGAIAPictureOfProfileAtIndex(index) &&
200 cache.GetGAIAPictureOfProfileAtIndex(index);
201 gfx::Image icon = profiles::GetAvatarIconForTitleBar(
202 cache.GetAvatarIconOfProfileAtIndex(index), is_gaia_picture,
203 profiles::kAvatarIconWidth, profiles::kAvatarIconHeight);
204 [self setImage:icon.ToNSImage()];
206 const base::string16& name = cache.GetNameOfProfileAtIndex(index);
207 NSString* nsName = base::SysUTF16ToNSString(name);
208 [button_ setToolTip:nsName];
210 accessibilitySetOverrideValue:nsName
211 forAttribute:NSAccessibilityValueAttribute];
213 [self addOrRemoveButtonIfNecessary];
216 // If the second-to-last profile was removed or a second profile was added,
217 // show or hide the avatar button from the window frame.
218 - (void)addOrRemoveButtonIfNecessary {
219 if (browser_->profile()->IsOffTheRecord())
222 NSWindowController* wc =
223 [browser_->window()->GetNativeWindow() windowController];
224 if (![wc isKindOfClass:[BrowserWindowController class]])
227 [self.view setHidden:
228 ![static_cast<BrowserWindowController*>(wc) shouldShowAvatar]];
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];