Chromium Code Reviews| Index: chrome/browser/ui/cocoa/browser/new_avatar_button_controller.mm | 
| diff --git a/chrome/browser/ui/cocoa/browser/new_avatar_button_controller.mm b/chrome/browser/ui/cocoa/browser/new_avatar_button_controller.mm | 
| new file mode 100644 | 
| index 0000000000000000000000000000000000000000..be1667db75752533780a43938de20734108395ac | 
| --- /dev/null | 
| +++ b/chrome/browser/ui/cocoa/browser/new_avatar_button_controller.mm | 
| @@ -0,0 +1,194 @@ | 
| +// Copyright 2014 The Chromium Authors. All rights reserved. | 
| +// Use of this source code is governed by a BSD-style license that can be | 
| +// found in the LICENSE file. | 
| + | 
| +#import "chrome/browser/ui/cocoa/browser/new_avatar_button_controller.h" | 
| + | 
| +#include "base/strings/sys_string_conversions.h" | 
| +#include "chrome/app/chrome_command_ids.h" | 
| +#include "chrome/browser/chrome_notification_types.h" | 
| +#include "chrome/browser/profiles/profile_metrics.h" | 
| +#include "chrome/browser/profiles/profiles_state.h" | 
| +#include "chrome/browser/ui/browser.h" | 
| +#include "chrome/browser/ui/browser_commands.h" | 
| +#include "chrome/browser/ui/browser_window.h" | 
| +#import "chrome/browser/ui/cocoa/browser/profile_chooser_controller.h" | 
| +#import "chrome/browser/ui/cocoa/browser_window_controller.h" | 
| +#include "content/public/browser/notification_service.h" | 
| +#include "grit/generated_resources.h" | 
| +#include "grit/theme_resources.h" | 
| +#include "ui/base/l10n/l10n_util_mac.h" | 
| +#include "ui/base/resource/resource_bundle.h" | 
| +#include "ui/gfx/text_elider.h" | 
| + | 
| +namespace { | 
| + | 
| +NSString* GetElidedProfileName(const base::string16& name) { | 
| + // Maximum characters the button can be before the text will get elided. | 
| + const int kMaxCharactersToDisplay = 15; | 
| + | 
| + gfx::FontList font_list = ui::ResourceBundle::GetSharedInstance().GetFontList( | 
| + ui::ResourceBundle::BaseFont); | 
| + return base::SysUTF16ToNSString(gfx::ElideText( | 
| + name, | 
| + font_list, | 
| + font_list.GetExpectedTextWidth(kMaxCharactersToDisplay), | 
| + gfx::ELIDE_AT_END)); | 
| +} | 
| + | 
| +} // namespace | 
| + | 
| +@interface NewAvatarButtonController (Private) | 
| +// Shows the ProfileMenuController. | 
| +- (IBAction)buttonClicked:(id)sender; | 
| + | 
| +- (void)bubbleWillClose:(NSNotification*)notif; | 
| + | 
| +// Updates the profile name displayed by the avatar button. If |layoutParent| is | 
| +// yes, then the BrowserWindowController is notified to relayout the subviews, | 
| +// as the button needs to be repositioned. | 
| +- (void)updateAvatarButtonAndLayoutParent:(BOOL)layoutParent; | 
| +@end | 
| + | 
| +class ProfileInfoUpdateObserverBridge : public content::NotificationObserver { | 
| 
 
Nico
2014/01/07 23:20:33
In general, we try to not use notifications in new
 
noms (inactive)
2014/01/08 15:05:31
I can also make this an AvatarMenuObserver (like t
 
 | 
| + public: | 
| + ProfileInfoUpdateObserverBridge(NewAvatarButtonController* avatarButton) | 
| + : avatarButton_(avatarButton) { | 
| + registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED, | 
| + content::NotificationService::AllSources()); | 
| + } | 
| + | 
| + virtual ~ProfileInfoUpdateObserverBridge() { | 
| + registrar_.Remove(this, chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED, | 
| + content::NotificationService::AllSources()); | 
| 
 
Nico
2014/01/07 23:20:33
Do you need to do this? I thought the point of Not
 
 | 
| + } | 
| + | 
| + // content::NotificationObserver: | 
| + virtual void Observe(int type, | 
| + const content::NotificationSource& source, | 
| + const content::NotificationDetails& details) OVERRIDE { | 
| + switch (type) { | 
| + case chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED: | 
| + [avatarButton_ updateAvatarButtonAndLayoutParent:YES]; | 
| + break; | 
| + default: | 
| + NOTREACHED(); | 
| + break; | 
| + } | 
| + } | 
| + | 
| + private: | 
| + content::NotificationRegistrar registrar_; | 
| + | 
| + NewAvatarButtonController* avatarButton_; // Weak; owns this. | 
| + | 
| + DISALLOW_COPY_AND_ASSIGN(ProfileInfoUpdateObserverBridge); | 
| +}; | 
| + | 
| +@implementation NewAvatarButtonController | 
| + | 
| +- (id)initWithBrowser:(Browser*)browser { | 
| + if ((self = [super init])) { | 
| + browser_ = browser; | 
| + profileInfoObserver_.reset(new ProfileInfoUpdateObserverBridge(self)); | 
| + | 
| + base::scoped_nsobject<NSView> container([[NSView alloc] | 
| + initWithFrame:NSZeroRect]); | 
| + [self setView:container]; | 
| + | 
| + button_.reset([[NSButton alloc] initWithFrame:NSZeroRect]); | 
| + [button_ setBezelStyle:NSTexturedRoundedBezelStyle]; | 
| + [button_ setImage:ui::ResourceBundle::GetSharedInstance(). | 
| + GetNativeImageNamed(IDR_APP_DROPARROW).ToNSImage()]; | 
| + [button_ setImagePosition:NSImageRight]; | 
| + [button_ setAutoresizingMask:NSViewMinXMargin | NSViewMinYMargin]; | 
| + [button_ setTarget:self]; | 
| + [button_ setAction:@selector(buttonClicked:)]; | 
| + | 
| + [self updateAvatarButtonAndLayoutParent:NO]; | 
| + | 
| + [[self view] addSubview:button_]; | 
| 
 
Nico
2014/01/07 23:20:33
Since this is the only view, can button_ just be t
 
noms (inactive)
2014/01/08 15:05:31
Not really. The only reason I added the container
 
 | 
| + } | 
| + return self; | 
| +} | 
| + | 
| +- (void)dealloc { | 
| + [[NSNotificationCenter defaultCenter] | 
| + removeObserver:self | 
| + name:NSWindowWillCloseNotification | 
| + object:[menuController_ window]]; | 
| + [super dealloc]; | 
| +} | 
| + | 
| +- (NSButton*)buttonView { | 
| + return button_.get(); | 
| +} | 
| + | 
| +- (void)showAvatarBubble:(NSView*)anchor { | 
| + if (menuController_) | 
| + return; | 
| + | 
| + DCHECK(chrome::IsCommandEnabled(browser_, IDC_SHOW_AVATAR_MENU)); | 
| + | 
| + NSWindowController* wc = | 
| + [browser_->window()->GetNativeWindow() windowController]; | 
| + if ([wc isKindOfClass:[BrowserWindowController class]]) { | 
| + [static_cast<BrowserWindowController*>(wc) | 
| + lockBarVisibilityForOwner:self withAnimation:NO delay:NO]; | 
| + } | 
| + | 
| + NSPoint point = NSMakePoint(NSMidX([anchor bounds]), | 
| + NSMaxY([anchor bounds])); | 
| + point = [anchor convertPoint:point toView:nil]; | 
| + point = [[anchor window] convertBaseToScreen:point]; | 
| + | 
| + menuController_ = [[ProfileChooserController alloc] initWithBrowser:browser_ | 
| + anchoredAt:point]; | 
| + [[NSNotificationCenter defaultCenter] | 
| + addObserver:self | 
| + selector:@selector(bubbleWillClose:) | 
| + name:NSWindowWillCloseNotification | 
| + object:[menuController_ window]]; | 
| + [menuController_ showWindow:self]; | 
| + | 
| + ProfileMetrics::LogProfileOpenMethod(ProfileMetrics::ICON_AVATAR_BUBBLE); | 
| +} | 
| + | 
| +- (IBAction)buttonClicked:(id)sender { | 
| + DCHECK(sender == button_.get()); | 
| + [self showAvatarBubble:button_]; | 
| +} | 
| + | 
| +- (void)bubbleWillClose:(NSNotification*)notif { | 
| + NSWindowController* wc = | 
| + [browser_->window()->GetNativeWindow() windowController]; | 
| + if ([wc isKindOfClass:[BrowserWindowController class]]) { | 
| + [static_cast<BrowserWindowController*>(wc) | 
| + releaseBarVisibilityForOwner:self withAnimation:YES delay:NO]; | 
| + } | 
| + menuController_ = nil; | 
| +} | 
| + | 
| +- (void)updateAvatarButtonAndLayoutParent:(BOOL)layoutParent { | 
| + [button_ setTitle:GetElidedProfileName( | 
| + profiles::GetActiveProfileDisplayName(browser_))]; | 
| + [button_ sizeToFit]; | 
| + | 
| + // Resize the container. | 
| + [[self view] setFrameSize:[button_ frame].size]; | 
| + [button_ setFrameOrigin:NSMakePoint(0, 0)]; | 
| + | 
| + if (layoutParent) { | 
| + // Because the width of the button might have changed, the parent browser | 
| + // frame needs to recalculate the button bounds and redraw it. | 
| + [[BrowserWindowController | 
| + browserWindowControllerForWindow:browser_->window()->GetNativeWindow()] | 
| + layoutSubviews]; | 
| + } | 
| +} | 
| + | 
| +- (ProfileChooserController*)menuController { | 
| + return menuController_; | 
| +} | 
| + | 
| +@end |