Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(10)

Side by Side Diff: chrome/browser/ui/cocoa/browser/avatar_button_controller.mm

Issue 117533002: [Mac] Redesign of the avatar menu button (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: . Created 6 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #import "chrome/browser/ui/cocoa/browser/avatar_button_controller.h" 5 #import "chrome/browser/ui/cocoa/browser/avatar_button_controller.h"
6 6
7 #include "base/strings/sys_string_conversions.h" 7 #include "base/strings/sys_string_conversions.h"
8 #include "chrome/app/chrome_command_ids.h" 8 #include "chrome/app/chrome_command_ids.h"
9 #include "chrome/browser/browser_process.h" 9 #include "chrome/browser/browser_process.h"
10 #include "chrome/browser/chrome_notification_types.h" 10 #include "chrome/browser/profiles/profile_info_cache_observer.h"
11 #include "chrome/browser/command_updater.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "chrome/browser/profiles/profile_info_cache.h"
14 #include "chrome/browser/profiles/profile_info_util.h"
15 #include "chrome/browser/profiles/profile_manager.h" 11 #include "chrome/browser/profiles/profile_manager.h"
16 #include "chrome/browser/profiles/profile_metrics.h" 12 #include "chrome/browser/profiles/profile_metrics.h"
17 #include "chrome/browser/profiles/profiles_state.h" 13 #include "chrome/browser/profiles/profiles_state.h"
18 #include "chrome/browser/ui/browser.h" 14 #include "chrome/browser/ui/browser.h"
19 #include "chrome/browser/ui/browser_commands.h" 15 #include "chrome/browser/ui/browser_commands.h"
20 #include "chrome/browser/ui/browser_window.h" 16 #include "chrome/browser/ui/browser_window.h"
21 #import "chrome/browser/ui/cocoa/browser/avatar_label_button.h" 17 #import "chrome/browser/ui/cocoa/base_bubble_controller.h"
22 #import "chrome/browser/ui/cocoa/browser/avatar_menu_bubble_controller.h" 18 #import "chrome/browser/ui/cocoa/browser/avatar_menu_bubble_controller.h"
23 #import "chrome/browser/ui/cocoa/base_bubble_controller.h"
24 #import "chrome/browser/ui/cocoa/browser/profile_chooser_controller.h" 19 #import "chrome/browser/ui/cocoa/browser/profile_chooser_controller.h"
25 #import "chrome/browser/ui/cocoa/browser_window_controller.h" 20 #import "chrome/browser/ui/cocoa/browser_window_controller.h"
26 #include "chrome/common/profile_management_switches.h" 21 #include "chrome/common/profile_management_switches.h"
27 #include "content/public/browser/notification_service.h"
28 #include "grit/generated_resources.h" 22 #include "grit/generated_resources.h"
29 #include "grit/theme_resources.h" 23 #include "grit/theme_resources.h"
30 #include "ui/base/l10n/l10n_util_mac.h" 24 #include "ui/base/l10n/l10n_util_mac.h"
31 #include "ui/base/resource/resource_bundle.h" 25 #include "ui/base/resource/resource_bundle.h"
32 #include "ui/gfx/image/image.h" 26 #include "ui/gfx/text_elider.h"
33 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
34 27
35 namespace { 28 namespace {
36
37 // Space between the avatar icon and the avatar menu bubble. 29 // Space between the avatar icon and the avatar menu bubble.
38 const CGFloat kMenuYOffsetAdjust = 1.0; 30 const CGFloat kMenuYOffsetAdjust = 1.0;
39 31
40 // Space between the avatar label and the left edge of the container containing 32 NSString* GetElidedProfileName(const base::string16& name) {
41 // the label and the icon. 33 // Maximum characters the button can be before the text will get elided.
42 const CGFloat kAvatarSpacing = 4; 34 const int kMaxCharactersToDisplay = 15;
43 35
44 // Space between the bottom of the avatar icon and the bottom of the avatar 36 gfx::FontList font_list = ui::ResourceBundle::GetSharedInstance().GetFontList(
45 // label. 37 ui::ResourceBundle::BaseFont);
46 const CGFloat kAvatarLabelBottomSpacing = 3; 38 return base::SysUTF16ToNSString(gfx::ElideText(
47 39 name,
48 // Space between the right edge of the avatar label and the right edge of the 40 font_list,
49 // avatar icon. 41 font_list.GetExpectedTextWidth(kMaxCharactersToDisplay),
50 const CGFloat kAvatarLabelRightSpacing = 2; 42 gfx::ELIDE_AT_END));
43 }
51 44
52 } // namespace 45 } // namespace
53 46
54 @interface AvatarButtonController (Private) 47 @interface AvatarButtonController (Private)
55 - (void)setButtonEnabled:(BOOL)flag; 48 // Shows the ProfileMenuController.
56 - (IBAction)buttonClicked:(id)sender; 49 - (IBAction)buttonClicked:(id)sender;
57 - (void)bubbleWillClose:(NSNotification*)notif; 50 - (void)bubbleWillClose:(NSNotification*)notif;
58 - (NSImage*)compositeImageWithShadow:(NSImage*)image; 51 // Updates the profile name displayed by the avatar button. If |layoutParent| is
59 - (void)updateAvatar; 52 // yes, then the BrowserWindowController is notified to relayout the subviews,
60 - (void)addOrRemoveButtonIfNecessary; 53 // as the button needs to be repositioned.
54 - (void)updateAvatarButtonAndLayoutParent:(BOOL)layoutParent;
61 @end 55 @end
62 56
63 // Declare a 10.7+ private API. 57 class ProfileInfoUpdateObserver : public ProfileInfoCacheObserver {
64 // NSThemeFrame < NSTitledFrame < NSFrameView < NSView.
65 @interface NSView (NSThemeFrame)
66 - (void)_tileTitlebarAndRedisplay:(BOOL)redisplay;
67 @end
68
69 namespace AvatarButtonControllerInternal {
70
71 class Observer : public content::NotificationObserver {
72 public: 58 public:
73 Observer(AvatarButtonController* button) : button_(button) { 59 ProfileInfoUpdateObserver(AvatarButtonController* avatarButton)
74 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED, 60 : avatarButton_(avatarButton) {
75 content::NotificationService::AllSources()); 61 g_browser_process->profile_manager()->
62 GetProfileInfoCache().AddObserver(this);
76 } 63 }
77 64
78 // NotificationObserver: 65 virtual ~ProfileInfoUpdateObserver() {
79 virtual void Observe(int type, 66 g_browser_process->profile_manager()->
80 const content::NotificationSource& source, 67 GetProfileInfoCache().RemoveObserver(this);
81 const content::NotificationDetails& details) OVERRIDE { 68 }
82 switch (type) { 69
83 case chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED: 70 // ProfileInfoCacheObserver:
84 [button_ updateAvatar]; 71 virtual void OnProfileAdded(const base::FilePath& profile_path) OVERRIDE {
85 [button_ addOrRemoveButtonIfNecessary]; 72 [avatarButton_ updateAvatarButtonAndLayoutParent:YES];
86 break; 73 }
87 default: 74
88 NOTREACHED(); 75 virtual void OnProfileWasRemoved(
89 break; 76 const base::FilePath& profile_path,
90 } 77 const base::string16& profile_name) OVERRIDE {
78 [avatarButton_ updateAvatarButtonAndLayoutParent:YES];
79 }
80 virtual void OnProfileNameChanged(
81 const base::FilePath& profile_path,
82 const base::string16& old_profile_name) OVERRIDE {
83 [avatarButton_ updateAvatarButtonAndLayoutParent:YES];
84 }
85 virtual void OnProfileAvatarChanged(
86 const base::FilePath& profile_path) OVERRIDE {
87 [avatarButton_ updateAvatarButtonAndLayoutParent:YES];
91 } 88 }
92 89
93 private: 90 private:
94 content::NotificationRegistrar registrar_; 91 AvatarButtonController* avatarButton_; // Weak; owns this.
95 92
96 AvatarButtonController* button_; // Weak; owns this. 93 DISALLOW_COPY_AND_ASSIGN(ProfileInfoUpdateObserver);
97 }; 94 };
98 95
99 } // namespace AvatarButtonControllerInternal 96 @implementation AvatarButtonController
100 97
101 //////////////////////////////////////////////////////////////////////////////// 98 - (id)init {
102 99 if ((self = [super init]))
103 @implementation AvatarButtonController 100 profileInfoObserver_.reset(new ProfileInfoUpdateObserver(self));
101 return self;
102 }
104 103
105 - (id)initWithBrowser:(Browser*)browser { 104 - (id)initWithBrowser:(Browser*)browser {
106 if ((self = [super init])) { 105 if ((self = [super init])) {
107 browser_ = browser; 106 browser_ = browser;
107 profileInfoObserver_.reset(new ProfileInfoUpdateObserver(self));
108 108
109 base::scoped_nsobject<NSView> container( 109 base::scoped_nsobject<NSView> container([[NSView alloc]
110 [[NSView alloc] initWithFrame:NSMakeRect( 110 initWithFrame:NSZeroRect]);
111 0, 0, profiles::kAvatarIconWidth, profiles::kAvatarIconHeight)]);
112 [self setView:container]; 111 [self setView:container];
113 button_.reset([[NSButton alloc] initWithFrame:NSMakeRect(
114 0, 0, profiles::kAvatarIconWidth, profiles::kAvatarIconHeight)]);
115 NSButtonCell* cell = [button_ cell];
116 [button_ setButtonType:NSMomentaryLightButton];
117 112
118 [button_ setImagePosition:NSImageOnly]; 113 button_.reset([[NSButton alloc] initWithFrame:NSZeroRect]);
119 [cell setImageScaling:NSImageScaleProportionallyDown]; 114 [button_ setBezelStyle:NSTexturedRoundedBezelStyle];
120 [cell setImagePosition:NSImageBelow]; 115 [button_ setImage:ui::ResourceBundle::GetSharedInstance().
121 116 GetNativeImageNamed(IDR_APP_DROPARROW).ToNSImage()];
122 // AppKit sets a title for some reason when using |-setImagePosition:|. 117 [button_ setImagePosition:NSImageRight];
123 [button_ setTitle:nil]; 118 [button_ setAutoresizingMask:NSViewMinXMargin | NSViewMinYMargin];
124
125 [cell setImageDimsWhenDisabled:NO];
126 [cell setHighlightsBy:NSContentsCellMask];
127 [cell setShowsStateBy:NSContentsCellMask];
128
129 [button_ setBordered:NO];
130 [button_ setTarget:self]; 119 [button_ setTarget:self];
131 [button_ setAction:@selector(buttonClicked:)]; 120 [button_ setAction:@selector(buttonClicked:)];
132 121
133 [cell accessibilitySetOverrideValue:NSAccessibilityButtonRole 122 [self updateAvatarButtonAndLayoutParent:NO];
134 forAttribute:NSAccessibilityRoleAttribute];
135 [cell accessibilitySetOverrideValue:
136 NSAccessibilityRoleDescription(NSAccessibilityButtonRole, nil)
137 forAttribute:NSAccessibilityRoleDescriptionAttribute];
138 [cell accessibilitySetOverrideValue:
139 l10n_util::GetNSString(IDS_PROFILES_BUBBLE_ACCESSIBLE_NAME)
140 forAttribute:NSAccessibilityTitleAttribute];
141 [cell accessibilitySetOverrideValue:
142 l10n_util::GetNSString(IDS_PROFILES_BUBBLE_ACCESSIBLE_DESCRIPTION)
143 forAttribute:NSAccessibilityHelpAttribute];
144 [cell accessibilitySetOverrideValue:
145 l10n_util::GetNSString(IDS_PROFILES_BUBBLE_ACCESSIBLE_DESCRIPTION)
146 forAttribute:NSAccessibilityDescriptionAttribute];
147 123
148 Profile* profile = browser_->profile();
149
150 if (profile->IsOffTheRecord() || profile->IsGuestSession()) {
151 const int icon_id = profile->IsGuestSession() ? IDR_LOGIN_GUEST :
152 IDR_OTR_ICON;
153 NSImage* icon = ResourceBundle::GetSharedInstance().GetNativeImageNamed(
154 icon_id).ToNSImage();
155 [self setImage:[self compositeImageWithShadow:icon]];
156 [self setButtonEnabled:profile->IsGuestSession()];
157 } else {
158 [self setButtonEnabled:YES];
159 observer_.reset(new AvatarButtonControllerInternal::Observer(self));
160 [self updateAvatar];
161
162 // Managed users cannot enter incognito mode, so we only need to check
163 // it in this code path.
164 if (profile->IsManaged()) {
165 // Initialize the avatar label button.
166 CGFloat extraWidth =
167 profiles::kAvatarIconWidth + kAvatarLabelRightSpacing;
168 NSRect frame = NSMakeRect(
169 kAvatarSpacing, kAvatarLabelBottomSpacing, extraWidth, 0);
170 labelButton_.reset([[AvatarLabelButton alloc] initWithFrame:frame]);
171 [labelButton_ setTarget:self];
172 [labelButton_ setAction:@selector(buttonClicked:)];
173 [[self view] addSubview:labelButton_];
174
175 // Resize the container and reposition the avatar button.
176 NSSize textSize = [[labelButton_ cell] labelTextSize];
177 [container setFrameSize:
178 NSMakeSize([labelButton_ frame].size.width + kAvatarSpacing,
179 profiles::kAvatarIconHeight)];
180 [button_
181 setFrameOrigin:NSMakePoint(kAvatarSpacing + textSize.width, 0)];
182 }
183 }
184 [[self view] addSubview:button_]; 124 [[self view] addSubview:button_];
185 } 125 }
186 return self; 126 return self;
187 } 127 }
188 128
189 - (void)dealloc { 129 - (void)dealloc {
190 [[NSNotificationCenter defaultCenter] 130 [[NSNotificationCenter defaultCenter]
191 removeObserver:self 131 removeObserver:self
192 name:NSWindowWillCloseNotification 132 name:NSWindowWillCloseNotification
193 object:[menuController_ window]]; 133 object:[menuController_ window]];
194 [super dealloc]; 134 [super dealloc];
195 } 135 }
196 136
197 - (NSButton*)buttonView { 137 - (NSButton*)buttonView {
198 return button_.get(); 138 return button_.get();
199 } 139 }
200 140
201 - (NSButton*)labelButtonView {
202 return labelButton_.get();
203 }
204
205 - (void)setImage:(NSImage*)image {
206 [button_ setImage:image];
207 }
208
209 - (void)showAvatarBubble:(NSView*)anchor { 141 - (void)showAvatarBubble:(NSView*)anchor {
210 if (menuController_) 142 if (menuController_)
211 return; 143 return;
212 144
213 DCHECK(chrome::IsCommandEnabled(browser_, IDC_SHOW_AVATAR_MENU)); 145 DCHECK(chrome::IsCommandEnabled(browser_, IDC_SHOW_AVATAR_MENU));
214 146
215 NSWindowController* wc = 147 NSWindowController* wc =
216 [browser_->window()->GetNativeWindow() windowController]; 148 [browser_->window()->GetNativeWindow() windowController];
217 if ([wc isKindOfClass:[BrowserWindowController class]]) { 149 if ([wc isKindOfClass:[BrowserWindowController class]]) {
218 [static_cast<BrowserWindowController*>(wc) 150 [static_cast<BrowserWindowController*>(wc)
219 lockBarVisibilityForOwner:self withAnimation:NO delay:NO]; 151 lockBarVisibilityForOwner:self withAnimation:NO delay:NO];
220 } 152 }
221 153
222 NSPoint point = NSMakePoint(NSMidX([anchor bounds]), 154 NSPoint point = NSMakePoint(NSMidX([anchor bounds]),
223 NSMaxY([anchor bounds]) - kMenuYOffsetAdjust); 155 NSMaxY([anchor bounds]) - kMenuYOffsetAdjust);
224 point = [anchor convertPoint:point toView:nil]; 156 point = [anchor convertPoint:point toView:nil];
225 point = [[anchor window] convertBaseToScreen:point]; 157 point = [[anchor window] convertBaseToScreen:point];
226 158
227 // |menuController_| will automatically release itself on close. 159 // |menuController_| will automatically release itself on close.
228 if (switches::IsNewProfileManagement()) { 160 if (switches::IsNewProfileManagement()) {
229 menuController_ = 161 menuController_ =
230 [[ProfileChooserController alloc] initWithBrowser:browser_ 162 [[ProfileChooserController alloc] initWithBrowser:browser_
231 anchoredAt:point]; 163 anchoredAt:point];
232 } else { 164 } else {
233 menuController_ = 165 menuController_ =
234 [[AvatarMenuBubbleController alloc] initWithBrowser:browser_ 166 [[AvatarMenuBubbleController alloc] initWithBrowser:browser_
235 anchoredAt:point]; 167 anchoredAt:point];
236 } 168 }
169
237 [[NSNotificationCenter defaultCenter] 170 [[NSNotificationCenter defaultCenter]
238 addObserver:self 171 addObserver:self
239 selector:@selector(bubbleWillClose:) 172 selector:@selector(bubbleWillClose:)
240 name:NSWindowWillCloseNotification 173 name:NSWindowWillCloseNotification
241 object:[menuController_ window]]; 174 object:[menuController_ window]];
242 [menuController_ showWindow:self]; 175 [menuController_ showWindow:self];
243 176
244 ProfileMetrics::LogProfileOpenMethod(ProfileMetrics::ICON_AVATAR_BUBBLE); 177 ProfileMetrics::LogProfileOpenMethod(ProfileMetrics::ICON_AVATAR_BUBBLE);
245 } 178 }
246 179
247 // Private /////////////////////////////////////////////////////////////////////
248
249 - (void)setButtonEnabled:(BOOL)flag {
250 [button_ setEnabled:flag];
251 }
252
253 - (IBAction)buttonClicked:(id)sender { 180 - (IBAction)buttonClicked:(id)sender {
254 DCHECK(sender == button_.get() || sender == labelButton_.get()); 181 DCHECK(sender == button_.get());
255 [self showAvatarBubble:button_]; 182 [self showAvatarBubble:button_];
256 } 183 }
257 184
258 - (void)bubbleWillClose:(NSNotification*)notif { 185 - (void)bubbleWillClose:(NSNotification*)notif {
259 NSWindowController* wc = 186 NSWindowController* wc =
260 [browser_->window()->GetNativeWindow() windowController]; 187 [browser_->window()->GetNativeWindow() windowController];
261 if ([wc isKindOfClass:[BrowserWindowController class]]) { 188 if ([wc isKindOfClass:[BrowserWindowController class]]) {
262 [static_cast<BrowserWindowController*>(wc) 189 [static_cast<BrowserWindowController*>(wc)
263 releaseBarVisibilityForOwner:self withAnimation:YES delay:NO]; 190 releaseBarVisibilityForOwner:self withAnimation:YES delay:NO];
264 } 191 }
265 menuController_ = nil; 192 menuController_ = nil;
266 } 193 }
267 194
268 // This will take in an original image and redraw it with a shadow. 195 - (void)updateAvatarButtonAndLayoutParent:(BOOL)layoutParent {
269 - (NSImage*)compositeImageWithShadow:(NSImage*)image { 196 [button_ setTitle:GetElidedProfileName(
270 gfx::ScopedNSGraphicsContextSaveGState scopedGState; 197 profiles::GetActiveProfileDisplayName(browser_))];
198 [button_ sizeToFit];
271 199
272 base::scoped_nsobject<NSImage> destination( 200 // Resize the container.
273 [[NSImage alloc] initWithSize:[image size]]); 201 [[self view] setFrameSize:[button_ frame].size];
202 [button_ setFrameOrigin:NSMakePoint(0, 0)];
274 203
275 NSRect destRect = NSZeroRect; 204 if (layoutParent) {
276 destRect.size = [destination size]; 205 // Because the width of the button might have changed, the parent browser
277 206 // frame needs to recalculate the button bounds and redraw it.
278 [destination lockFocus]; 207 [[BrowserWindowController
279 208 browserWindowControllerForWindow:browser_->window()->GetNativeWindow()]
280 base::scoped_nsobject<NSShadow> shadow([[NSShadow alloc] init]); 209 layoutSubviews];
281 [shadow.get() setShadowColor:[NSColor colorWithCalibratedWhite:0.0 210 }
282 alpha:0.75]];
283 [shadow.get() setShadowOffset:NSZeroSize];
284 [shadow.get() setShadowBlurRadius:3.0];
285 [shadow.get() set];
286
287 [image drawInRect:destRect
288 fromRect:NSZeroRect
289 operation:NSCompositeSourceOver
290 fraction:1.0
291 respectFlipped:YES
292 hints:nil];
293
294 [destination unlockFocus];
295
296 return destination.autorelease();
297 } 211 }
298 212
299 // Updates the avatar information from the profile cache.
300 - (void)updateAvatar {
301 ProfileInfoCache& cache =
302 g_browser_process->profile_manager()->GetProfileInfoCache();
303 size_t index =
304 cache.GetIndexOfProfileWithPath(browser_->profile()->GetPath());
305 if (index == std::string::npos)
306 return;
307 BOOL is_gaia_picture =
308 cache.IsUsingGAIAPictureOfProfileAtIndex(index) &&
309 cache.GetGAIAPictureOfProfileAtIndex(index);
310 gfx::Image icon = profiles::GetAvatarIconForTitleBar(
311 cache.GetAvatarIconOfProfileAtIndex(index), is_gaia_picture,
312 profiles::kAvatarIconWidth, profiles::kAvatarIconHeight);
313 [self setImage:icon.ToNSImage()];
314
315 const base::string16& name = cache.GetNameOfProfileAtIndex(index);
316 NSString* nsName = base::SysUTF16ToNSString(name);
317 [button_ setToolTip:nsName];
318 [[button_ cell]
319 accessibilitySetOverrideValue:nsName
320 forAttribute:NSAccessibilityValueAttribute];
321 }
322
323 // If the second-to-last profile was removed or a second profile was added,
324 // show or hide the avatar button from the window frame.
325 - (void)addOrRemoveButtonIfNecessary {
326 if (browser_->profile()->IsOffTheRecord())
327 return;
328
329 NSWindowController* wc =
330 [browser_->window()->GetNativeWindow() windowController];
331 if (![wc isKindOfClass:[BrowserWindowController class]])
332 return;
333
334 size_t count = g_browser_process->profile_manager()->GetNumberOfProfiles();
335 [self.view setHidden:count < 2];
336
337 [static_cast<BrowserWindowController*>(wc) layoutSubviews];
338
339 // If the avatar is being added or removed, then the Lion fullscreen button
340 // needs to be adjusted. Since the fullscreen button is positioned by
341 // FramedBrowserWindow using private APIs, the easiest way to update the
342 // position of the button is through this private API. Resizing the window
343 // also works, but invoking |-display| does not.
344 NSView* themeFrame = [[[wc window] contentView] superview];
345 if ([themeFrame respondsToSelector:@selector(_tileTitlebarAndRedisplay:)])
346 [themeFrame _tileTitlebarAndRedisplay:YES];
347 }
348
349 // Testing /////////////////////////////////////////////////////////////////////
350
351 - (BaseBubbleController*)menuController { 213 - (BaseBubbleController*)menuController {
352 return menuController_; 214 return menuController_;
353 } 215 }
354 216
355 @end 217 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698