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

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: now with less incorrect usage of initializers 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"
9 #include "chrome/browser/browser_process.h"
10 #include "chrome/browser/chrome_notification_types.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"
16 #include "chrome/browser/profiles/profile_metrics.h"
17 #include "chrome/browser/profiles/profiles_state.h" 8 #include "chrome/browser/profiles/profiles_state.h"
18 #include "chrome/browser/ui/browser.h" 9 #include "chrome/browser/ui/browser.h"
19 #include "chrome/browser/ui/browser_commands.h"
20 #include "chrome/browser/ui/browser_window.h" 10 #include "chrome/browser/ui/browser_window.h"
21 #import "chrome/browser/ui/cocoa/browser/avatar_label_button.h"
22 #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"
25 #import "chrome/browser/ui/cocoa/browser_window_controller.h" 11 #import "chrome/browser/ui/cocoa/browser_window_controller.h"
26 #include "chrome/common/profile_management_switches.h"
27 #include "content/public/browser/notification_service.h"
28 #include "grit/generated_resources.h" 12 #include "grit/generated_resources.h"
29 #include "grit/theme_resources.h" 13 #include "grit/theme_resources.h"
30 #include "ui/base/l10n/l10n_util_mac.h" 14 #include "ui/base/l10n/l10n_util_mac.h"
31 #include "ui/base/resource/resource_bundle.h" 15 #include "ui/base/resource/resource_bundle.h"
32 #include "ui/gfx/image/image.h" 16 #include "ui/gfx/text_elider.h"
33 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
34 17
35 namespace { 18 namespace {
36 19
37 // Space between the avatar icon and the avatar menu bubble. 20 NSString* GetElidedProfileName(const base::string16& name) {
38 const CGFloat kMenuYOffsetAdjust = 1.0; 21 // Maximum characters the button can be before the text will get elided.
22 const int kMaxCharactersToDisplay = 15;
39 23
40 // Space between the avatar label and the left edge of the container containing 24 gfx::FontList font_list = ui::ResourceBundle::GetSharedInstance().GetFontList(
41 // the label and the icon. 25 ui::ResourceBundle::BaseFont);
42 const CGFloat kAvatarSpacing = 4; 26 return base::SysUTF16ToNSString(gfx::ElideText(
43 27 name,
44 // Space between the bottom of the avatar icon and the bottom of the avatar 28 font_list,
45 // label. 29 font_list.GetExpectedTextWidth(kMaxCharactersToDisplay),
46 const CGFloat kAvatarLabelBottomSpacing = 3; 30 gfx::ELIDE_AT_END));
47 31 }
48 // Space between the right edge of the avatar label and the right edge of the
49 // avatar icon.
50 const CGFloat kAvatarLabelRightSpacing = 2;
51 32
52 } // namespace 33 } // namespace
53 34
54 @interface AvatarButtonController (Private) 35 @interface AvatarButtonController (Private)
55 - (void)setButtonEnabled:(BOOL)flag; 36 - (void)updateAvatarButtonAndLayoutParent:(BOOL)layoutParent;
56 - (IBAction)buttonClicked:(id)sender;
57 - (void)bubbleWillClose:(NSNotification*)notif;
58 - (NSImage*)compositeImageWithShadow:(NSImage*)image;
59 - (void)updateAvatar;
60 - (void)addOrRemoveButtonIfNecessary;
61 @end 37 @end
62 38
63 // Declare a 10.7+ private API.
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:
73 Observer(AvatarButtonController* button) : button_(button) {
74 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED,
75 content::NotificationService::AllSources());
76 }
77
78 // NotificationObserver:
79 virtual void Observe(int type,
80 const content::NotificationSource& source,
81 const content::NotificationDetails& details) OVERRIDE {
82 switch (type) {
83 case chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED:
84 [button_ updateAvatar];
85 [button_ addOrRemoveButtonIfNecessary];
86 break;
87 default:
88 NOTREACHED();
89 break;
90 }
91 }
92
93 private:
94 content::NotificationRegistrar registrar_;
95
96 AvatarButtonController* button_; // Weak; owns this.
97 };
98
99 } // namespace AvatarButtonControllerInternal
100
101 ////////////////////////////////////////////////////////////////////////////////
102
103 @implementation AvatarButtonController 39 @implementation AvatarButtonController
104 40
105 - (id)initWithBrowser:(Browser*)browser { 41 - (id)initWithBrowser:(Browser*)browser {
106 if ((self = [super init])) { 42 if ((self = [super initWithBrowser:browser])) {
107 browser_ = browser; 43 base::scoped_nsobject<NSView> container([[NSView alloc]
44 initWithFrame:NSZeroRect]);
45 [self setView:container];
Nico 2014/01/23 00:51:33 I'd [self setView:button_], as there's only one su
noms (inactive) 2014/01/23 18:24:27 Oops! Done. On 2014/01/23 00:51:33, Nico wrote:
108 46
109 base::scoped_nsobject<NSView> container( 47 button_.reset([[NSButton alloc] initWithFrame:NSZeroRect]);
110 [[NSView alloc] initWithFrame:NSMakeRect( 48 [button_ setBezelStyle:NSTexturedRoundedBezelStyle];
111 0, 0, profiles::kAvatarIconWidth, profiles::kAvatarIconHeight)]); 49 [button_ setImage:ui::ResourceBundle::GetSharedInstance().
112 [self setView:container]; 50 GetNativeImageNamed(IDR_APP_DROPARROW).ToNSImage()];
113 button_.reset([[NSButton alloc] initWithFrame:NSMakeRect( 51 [button_ setImagePosition:NSImageRight];
114 0, 0, profiles::kAvatarIconWidth, profiles::kAvatarIconHeight)]); 52 [button_ setAutoresizingMask:NSViewMinXMargin | NSViewMinYMargin];
115 NSButtonCell* cell = [button_ cell];
116 [button_ setButtonType:NSMomentaryLightButton];
117
118 [button_ setImagePosition:NSImageOnly];
119 [cell setImageScaling:NSImageScaleProportionallyDown];
120 [cell setImagePosition:NSImageBelow];
121
122 // AppKit sets a title for some reason when using |-setImagePosition:|.
123 [button_ setTitle:nil];
124
125 [cell setImageDimsWhenDisabled:NO];
126 [cell setHighlightsBy:NSContentsCellMask];
127 [cell setShowsStateBy:NSContentsCellMask];
128
129 [button_ setBordered:NO];
130 [button_ setTarget:self]; 53 [button_ setTarget:self];
131 [button_ setAction:@selector(buttonClicked:)]; 54 [button_ setAction:@selector(buttonClicked:)];
132 55
133 [cell accessibilitySetOverrideValue:NSAccessibilityButtonRole 56 [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 57
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_]; 58 [[self view] addSubview:button_];
185 } 59 }
186 return self; 60 return self;
187 } 61 }
188 62
189 - (void)dealloc { 63 - (void)updateAvatarButtonAndLayoutParent:(BOOL)layoutParent {
190 [[NSNotificationCenter defaultCenter] 64 [button_ setTitle:GetElidedProfileName(
191 removeObserver:self 65 profiles::GetActiveProfileDisplayName(browser_))];
192 name:NSWindowWillCloseNotification 66 [button_ sizeToFit];
193 object:[menuController_ window]];
194 [super dealloc];
195 }
196 67
197 - (NSButton*)buttonView { 68 // Resize the container.
198 return button_.get(); 69 [[self view] setFrameSize:[button_ frame].size];
199 } 70 [button_ setFrameOrigin:NSMakePoint(0, 0)];
200 71
201 - (NSButton*)labelButtonView { 72 if (layoutParent) {
202 return labelButton_.get(); 73 // Because the width of the button might have changed, the parent browser
203 } 74 // frame needs to recalculate the button bounds and redraw it.
204 75 [[BrowserWindowController
205 - (void)setImage:(NSImage*)image { 76 browserWindowControllerForWindow:browser_->window()->GetNativeWindow()]
206 [button_ setImage:image]; 77 layoutSubviews];
207 }
208
209 - (void)showAvatarBubble:(NSView*)anchor {
210 if (menuController_)
211 return;
212
213 DCHECK(chrome::IsCommandEnabled(browser_, IDC_SHOW_AVATAR_MENU));
214
215 NSWindowController* wc =
216 [browser_->window()->GetNativeWindow() windowController];
217 if ([wc isKindOfClass:[BrowserWindowController class]]) {
218 [static_cast<BrowserWindowController*>(wc)
219 lockBarVisibilityForOwner:self withAnimation:NO delay:NO];
220 } 78 }
221
222 NSPoint point = NSMakePoint(NSMidX([anchor bounds]),
223 NSMaxY([anchor bounds]) - kMenuYOffsetAdjust);
224 point = [anchor convertPoint:point toView:nil];
225 point = [[anchor window] convertBaseToScreen:point];
226
227 // |menuController_| will automatically release itself on close.
228 if (switches::IsNewProfileManagement()) {
229 menuController_ =
230 [[ProfileChooserController alloc] initWithBrowser:browser_
231 anchoredAt:point];
232 } else {
233 menuController_ =
234 [[AvatarMenuBubbleController alloc] initWithBrowser:browser_
235 anchoredAt:point];
236 }
237 [[NSNotificationCenter defaultCenter]
238 addObserver:self
239 selector:@selector(bubbleWillClose:)
240 name:NSWindowWillCloseNotification
241 object:[menuController_ window]];
242 [menuController_ showWindow:self];
243
244 ProfileMetrics::LogProfileOpenMethod(ProfileMetrics::ICON_AVATAR_BUBBLE);
245 }
246
247 // Private /////////////////////////////////////////////////////////////////////
248
249 - (void)setButtonEnabled:(BOOL)flag {
250 [button_ setEnabled:flag];
251 }
252
253 - (IBAction)buttonClicked:(id)sender {
254 DCHECK(sender == button_.get() || sender == labelButton_.get());
255 [self showAvatarBubble:button_];
256 }
257
258 - (void)bubbleWillClose:(NSNotification*)notif {
259 NSWindowController* wc =
260 [browser_->window()->GetNativeWindow() windowController];
261 if ([wc isKindOfClass:[BrowserWindowController class]]) {
262 [static_cast<BrowserWindowController*>(wc)
263 releaseBarVisibilityForOwner:self withAnimation:YES delay:NO];
264 }
265 menuController_ = nil;
266 }
267
268 // This will take in an original image and redraw it with a shadow.
269 - (NSImage*)compositeImageWithShadow:(NSImage*)image {
270 gfx::ScopedNSGraphicsContextSaveGState scopedGState;
271
272 base::scoped_nsobject<NSImage> destination(
273 [[NSImage alloc] initWithSize:[image size]]);
274
275 NSRect destRect = NSZeroRect;
276 destRect.size = [destination size];
277
278 [destination lockFocus];
279
280 base::scoped_nsobject<NSShadow> shadow([[NSShadow alloc] init]);
281 [shadow.get() setShadowColor:[NSColor colorWithCalibratedWhite:0.0
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 }
298
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 {
352 return menuController_;
353 } 79 }
354 80
355 @end 81 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698