| // Copyright (c) 2012 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/avatar_button_controller.h" |
| |
| #include "base/strings/sys_string_conversions.h" |
| #include "chrome/app/chrome_command_ids.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/chrome_notification_types.h" |
| #include "chrome/browser/command_updater.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_info_cache.h" |
| #include "chrome/browser/profiles/profile_info_util.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/profiles/profile_metrics.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/avatar_label_button.h" |
| #import "chrome/browser/ui/cocoa/browser/avatar_menu_bubble_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/image/image.h" |
| #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h" |
| |
| namespace { |
| |
| // Space between the avatar icon and the avatar menu bubble. |
| const CGFloat kMenuYOffsetAdjust = 1.0; |
| |
| // Space between the avatar label and the left edge of the container containing |
| // the label and the icon. |
| const CGFloat kAvatarSpacing = 4; |
| |
| // Space between the bottom of the avatar icon and the bottom of the avatar |
| // label. |
| const CGFloat kAvatarLabelBottomSpacing = 3; |
| |
| // Space between the right edge of the avatar label and the right edge of the |
| // avatar icon. |
| const CGFloat kAvatarLabelRightSpacing = 2; |
| |
| } // namespace |
| |
| @interface AvatarButtonController (Private) |
| - (void)setButtonEnabled:(BOOL)flag; |
| - (IBAction)buttonClicked:(id)sender; |
| - (void)bubbleWillClose:(NSNotification*)notif; |
| - (NSImage*)compositeImageWithShadow:(NSImage*)image; |
| - (void)updateAvatar; |
| - (void)addOrRemoveButtonIfNecessary; |
| @end |
| |
| // Declare a 10.7+ private API. |
| // NSThemeFrame < NSTitledFrame < NSFrameView < NSView. |
| @interface NSView (NSThemeFrame) |
| - (void)_tileTitlebarAndRedisplay:(BOOL)redisplay; |
| @end |
| |
| namespace AvatarButtonControllerInternal { |
| |
| class Observer : public content::NotificationObserver { |
| public: |
| Observer(AvatarButtonController* button) : button_(button) { |
| registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED, |
| content::NotificationService::AllSources()); |
| } |
| |
| // NotificationObserver: |
| virtual void Observe(int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) OVERRIDE { |
| switch (type) { |
| case chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED: |
| [button_ updateAvatar]; |
| [button_ addOrRemoveButtonIfNecessary]; |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| private: |
| content::NotificationRegistrar registrar_; |
| |
| AvatarButtonController* button_; // Weak; owns this. |
| }; |
| |
| } // namespace AvatarButtonControllerInternal |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| @implementation AvatarButtonController |
| |
| - (id)initWithBrowser:(Browser*)browser { |
| if ((self = [super init])) { |
| browser_ = browser; |
| |
| base::scoped_nsobject<NSView> container( |
| [[NSView alloc] initWithFrame:NSMakeRect( |
| 0, 0, profiles::kAvatarIconWidth, profiles::kAvatarIconHeight)]); |
| [self setView:container]; |
| button_.reset([[NSButton alloc] initWithFrame:NSMakeRect( |
| 0, 0, profiles::kAvatarIconWidth, profiles::kAvatarIconHeight)]); |
| NSButtonCell* cell = [button_ cell]; |
| [button_ setButtonType:NSMomentaryLightButton]; |
| |
| [button_ setImagePosition:NSImageOnly]; |
| [cell setImageScaling:NSImageScaleProportionallyDown]; |
| [cell setImagePosition:NSImageBelow]; |
| |
| // AppKit sets a title for some reason when using |-setImagePosition:|. |
| [button_ setTitle:nil]; |
| |
| [cell setImageDimsWhenDisabled:NO]; |
| [cell setHighlightsBy:NSContentsCellMask]; |
| [cell setShowsStateBy:NSContentsCellMask]; |
| |
| [button_ setBordered:NO]; |
| [button_ setTarget:self]; |
| [button_ setAction:@selector(buttonClicked:)]; |
| |
| [cell accessibilitySetOverrideValue:NSAccessibilityButtonRole |
| forAttribute:NSAccessibilityRoleAttribute]; |
| [cell accessibilitySetOverrideValue: |
| NSAccessibilityRoleDescription(NSAccessibilityButtonRole, nil) |
| forAttribute:NSAccessibilityRoleDescriptionAttribute]; |
| [cell accessibilitySetOverrideValue: |
| l10n_util::GetNSString(IDS_PROFILES_BUBBLE_ACCESSIBLE_NAME) |
| forAttribute:NSAccessibilityTitleAttribute]; |
| [cell accessibilitySetOverrideValue: |
| l10n_util::GetNSString(IDS_PROFILES_BUBBLE_ACCESSIBLE_DESCRIPTION) |
| forAttribute:NSAccessibilityHelpAttribute]; |
| [cell accessibilitySetOverrideValue: |
| l10n_util::GetNSString(IDS_PROFILES_BUBBLE_ACCESSIBLE_DESCRIPTION) |
| forAttribute:NSAccessibilityDescriptionAttribute]; |
| |
| Profile* profile = browser_->profile(); |
| if (profile->IsOffTheRecord()) { |
| ResourceBundle& bundle = ResourceBundle::GetSharedInstance(); |
| NSImage* otrIcon = bundle.GetNativeImageNamed(IDR_OTR_ICON).ToNSImage(); |
| [self setImage:[self compositeImageWithShadow:otrIcon]]; |
| [self setButtonEnabled:NO]; |
| } else { |
| [self setButtonEnabled:YES]; |
| observer_.reset(new AvatarButtonControllerInternal::Observer(self)); |
| [self updateAvatar]; |
| |
| // Managed users cannot enter incognito mode, so we only need to check |
| // it in this code path. |
| if (profile->IsManaged()) { |
| // Initialize the avatar label button. |
| CGFloat extraWidth = |
| profiles::kAvatarIconWidth + kAvatarLabelRightSpacing; |
| NSRect frame = NSMakeRect( |
| kAvatarSpacing, kAvatarLabelBottomSpacing, extraWidth, 0); |
| labelButton_.reset([[AvatarLabelButton alloc] initWithFrame:frame]); |
| [labelButton_ setTarget:self]; |
| [labelButton_ setAction:@selector(buttonClicked:)]; |
| [[self view] addSubview:labelButton_]; |
| |
| // Resize the container and reposition the avatar button. |
| NSSize textSize = [[labelButton_ cell] labelTextSize]; |
| [container setFrameSize: |
| NSMakeSize([labelButton_ frame].size.width + kAvatarSpacing, |
| profiles::kAvatarIconHeight)]; |
| [button_ |
| setFrameOrigin:NSMakePoint(kAvatarSpacing + textSize.width, 0)]; |
| } |
| } |
| [[self view] addSubview:button_]; |
| } |
| return self; |
| } |
| |
| - (void)dealloc { |
| [[NSNotificationCenter defaultCenter] |
| removeObserver:self |
| name:NSWindowWillCloseNotification |
| object:[menuController_ window]]; |
| [super dealloc]; |
| } |
| |
| - (NSButton*)buttonView { |
| return button_.get(); |
| } |
| |
| - (NSButton*)labelButtonView { |
| return labelButton_.get(); |
| } |
| |
| - (void)setImage:(NSImage*)image { |
| [button_ setImage:image]; |
| } |
| |
| - (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]) - kMenuYOffsetAdjust); |
| point = [anchor convertPoint:point toView:nil]; |
| point = [[anchor window] convertBaseToScreen:point]; |
| |
| // |menu| will automatically release itself on close. |
| menuController_ = [[AvatarMenuBubbleController 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); |
| } |
| |
| // Private ///////////////////////////////////////////////////////////////////// |
| |
| - (void)setButtonEnabled:(BOOL)flag { |
| [button_ setEnabled:flag]; |
| } |
| |
| - (IBAction)buttonClicked:(id)sender { |
| DCHECK(sender == button_.get() || sender == labelButton_.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; |
| } |
| |
| // This will take in an original image and redraw it with a shadow. |
| - (NSImage*)compositeImageWithShadow:(NSImage*)image { |
| gfx::ScopedNSGraphicsContextSaveGState scopedGState; |
| |
| base::scoped_nsobject<NSImage> destination( |
| [[NSImage alloc] initWithSize:[image size]]); |
| |
| NSRect destRect = NSZeroRect; |
| destRect.size = [destination size]; |
| |
| [destination lockFocus]; |
| |
| base::scoped_nsobject<NSShadow> shadow([[NSShadow alloc] init]); |
| [shadow.get() setShadowColor:[NSColor colorWithCalibratedWhite:0.0 |
| alpha:0.75]]; |
| [shadow.get() setShadowOffset:NSZeroSize]; |
| [shadow.get() setShadowBlurRadius:3.0]; |
| [shadow.get() set]; |
| |
| [image drawInRect:destRect |
| fromRect:NSZeroRect |
| operation:NSCompositeSourceOver |
| fraction:1.0 |
| respectFlipped:YES |
| hints:nil]; |
| |
| [destination unlockFocus]; |
| |
| return destination.autorelease(); |
| } |
| |
| // Updates the avatar information from the profile cache. |
| - (void)updateAvatar { |
| ProfileInfoCache& cache = |
| g_browser_process->profile_manager()->GetProfileInfoCache(); |
| size_t index = |
| cache.GetIndexOfProfileWithPath(browser_->profile()->GetPath()); |
| if (index == std::string::npos) |
| return; |
| BOOL is_gaia_picture = |
| cache.IsUsingGAIAPictureOfProfileAtIndex(index) && |
| cache.GetGAIAPictureOfProfileAtIndex(index); |
| gfx::Image icon = profiles::GetAvatarIconForTitleBar( |
| cache.GetAvatarIconOfProfileAtIndex(index), is_gaia_picture, |
| profiles::kAvatarIconWidth, profiles::kAvatarIconHeight); |
| [self setImage:icon.ToNSImage()]; |
| |
| const string16& name = cache.GetNameOfProfileAtIndex(index); |
| NSString* nsName = base::SysUTF16ToNSString(name); |
| [button_ setToolTip:nsName]; |
| [[button_ cell] |
| accessibilitySetOverrideValue:nsName |
| forAttribute:NSAccessibilityValueAttribute]; |
| } |
| |
| // If the second-to-last profile was removed or a second profile was added, |
| // show or hide the avatar button from the window frame. |
| - (void)addOrRemoveButtonIfNecessary { |
| if (browser_->profile()->IsOffTheRecord()) |
| return; |
| |
| NSWindowController* wc = |
| [browser_->window()->GetNativeWindow() windowController]; |
| if (![wc isKindOfClass:[BrowserWindowController class]]) |
| return; |
| |
| size_t count = g_browser_process->profile_manager()->GetNumberOfProfiles(); |
| [self.view setHidden:count < 2]; |
| |
| [static_cast<BrowserWindowController*>(wc) layoutSubviews]; |
| |
| // If the avatar is being added or removed, then the Lion fullscreen button |
| // needs to be adjusted. Since the fullscreen button is positioned by |
| // FramedBrowserWindow using private APIs, the easiest way to update the |
| // position of the button is through this private API. Resizing the window |
| // also works, but invoking |-display| does not. |
| NSView* themeFrame = [[[wc window] contentView] superview]; |
| if ([themeFrame respondsToSelector:@selector(_tileTitlebarAndRedisplay:)]) |
| [themeFrame _tileTitlebarAndRedisplay:YES]; |
| } |
| |
| // Testing ///////////////////////////////////////////////////////////////////// |
| |
| - (AvatarMenuBubbleController*)menuController { |
| return menuController_; |
| } |
| |
| @end |