Home | History | Annotate | Download | only in cocoa
      1 // Copyright (c) 2012 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.
      4 
      5 #import "chrome/browser/ui/cocoa/profile_menu_controller.h"
      6 
      7 #include "base/mac/scoped_nsobject.h"
      8 #include "base/strings/sys_string_conversions.h"
      9 #include "chrome/browser/browser_process.h"
     10 #include "chrome/browser/profiles/avatar_menu.h"
     11 #include "chrome/browser/profiles/avatar_menu_observer.h"
     12 #include "chrome/browser/profiles/profile.h"
     13 #include "chrome/browser/profiles/profile_info_cache.h"
     14 #include "chrome/browser/profiles/profile_info_interface.h"
     15 #include "chrome/browser/profiles/profile_manager.h"
     16 #include "chrome/browser/profiles/profile_metrics.h"
     17 #include "chrome/browser/ui/browser.h"
     18 #include "chrome/browser/ui/browser_list.h"
     19 #include "chrome/browser/ui/browser_list_observer.h"
     20 #include "chrome/browser/ui/cocoa/last_active_browser_cocoa.h"
     21 #include "grit/generated_resources.h"
     22 #include "ui/base/l10n/l10n_util_mac.h"
     23 #include "ui/gfx/image/image.h"
     24 
     25 @interface ProfileMenuController (Private)
     26 - (void)initializeMenu;
     27 @end
     28 
     29 namespace ProfileMenuControllerInternal {
     30 
     31 class Observer : public chrome::BrowserListObserver,
     32                  public AvatarMenuObserver {
     33  public:
     34   Observer(ProfileMenuController* controller) : controller_(controller) {
     35     BrowserList::AddObserver(this);
     36   }
     37 
     38   virtual ~Observer() {
     39     BrowserList::RemoveObserver(this);
     40   }
     41 
     42   // chrome::BrowserListObserver:
     43   virtual void OnBrowserAdded(Browser* browser) OVERRIDE {}
     44   virtual void OnBrowserRemoved(Browser* browser) OVERRIDE {
     45     [controller_ activeBrowserChangedTo:chrome::GetLastActiveBrowser()];
     46   }
     47   virtual void OnBrowserSetLastActive(Browser* browser) OVERRIDE {
     48     [controller_ activeBrowserChangedTo:browser];
     49   }
     50 
     51   // AvatarMenuObserver:
     52   virtual void OnAvatarMenuChanged(AvatarMenu* menu) OVERRIDE {
     53     [controller_ rebuildMenu];
     54   }
     55 
     56  private:
     57   ProfileMenuController* controller_;  // Weak; owns this.
     58 };
     59 
     60 }  // namespace ProfileMenuControllerInternal
     61 
     62 ////////////////////////////////////////////////////////////////////////////////
     63 
     64 @implementation ProfileMenuController
     65 
     66 - (id)initWithMainMenuItem:(NSMenuItem*)item {
     67   if ((self = [super init])) {
     68     mainMenuItem_ = item;
     69 
     70     base::scoped_nsobject<NSMenu> menu([[NSMenu alloc] initWithTitle:
     71             l10n_util::GetNSStringWithFixup(IDS_PROFILES_OPTIONS_GROUP_NAME)]);
     72     [mainMenuItem_ setSubmenu:menu];
     73 
     74     // This object will be constructed as part of nib loading, which happens
     75     // before the message loop starts and g_browser_process is available.
     76     // Schedule this on the loop to do work when the browser is ready.
     77     [self performSelector:@selector(initializeMenu)
     78                withObject:nil
     79                afterDelay:0];
     80   }
     81   return self;
     82 }
     83 
     84 - (IBAction)switchToProfileFromMenu:(id)sender {
     85   menu_->SwitchToProfile([sender tag], false);
     86   ProfileMetrics::LogProfileSwitchUser(ProfileMetrics::SWITCH_PROFILE_MENU);
     87 }
     88 
     89 - (IBAction)switchToProfileFromDock:(id)sender {
     90   // Explicitly bring to the foreground when taking action from the dock.
     91   [NSApp activateIgnoringOtherApps:YES];
     92   menu_->SwitchToProfile([sender tag], false);
     93   ProfileMetrics::LogProfileSwitchUser(ProfileMetrics::SWITCH_PROFILE_DOCK);
     94 }
     95 
     96 - (IBAction)editProfile:(id)sender {
     97   menu_->EditProfile(menu_->GetActiveProfileIndex());
     98 }
     99 
    100 - (IBAction)newProfile:(id)sender {
    101   menu_->AddNewProfile(ProfileMetrics::ADD_NEW_USER_MENU);
    102 }
    103 
    104 - (BOOL)insertItemsIntoMenu:(NSMenu*)menu
    105                    atOffset:(NSInteger)offset
    106                    fromDock:(BOOL)dock {
    107   if (!menu_ || !menu_->ShouldShowAvatarMenu())
    108     return NO;
    109 
    110   if (dock) {
    111     NSString* headerName =
    112         l10n_util::GetNSStringWithFixup(IDS_PROFILES_OPTIONS_GROUP_NAME);
    113     base::scoped_nsobject<NSMenuItem> header(
    114         [[NSMenuItem alloc] initWithTitle:headerName
    115                                    action:NULL
    116                             keyEquivalent:@""]);
    117     [header setEnabled:NO];
    118     [menu insertItem:header atIndex:offset++];
    119   }
    120 
    121   for (size_t i = 0; i < menu_->GetNumberOfItems(); ++i) {
    122     const AvatarMenu::Item& itemData = menu_->GetItemAt(i);
    123     NSString* name = base::SysUTF16ToNSString(itemData.name);
    124     SEL action = dock ? @selector(switchToProfileFromDock:)
    125                       : @selector(switchToProfileFromMenu:);
    126     NSMenuItem* item = [self createItemWithTitle:name
    127                                           action:action];
    128     [item setTag:itemData.menu_index];
    129     if (dock) {
    130       [item setIndentationLevel:1];
    131     } else {
    132       [item setImage:itemData.icon.ToNSImage()];
    133       [item setState:itemData.active ? NSOnState : NSOffState];
    134     }
    135     [menu insertItem:item atIndex:i + offset];
    136   }
    137 
    138   return YES;
    139 }
    140 
    141 - (BOOL)validateMenuItem:(NSMenuItem*)menuItem {
    142   // In guest mode, chrome://settings isn't available, so disallow creating
    143   // or editing a profile.
    144   Profile* activeProfile = ProfileManager::GetLastUsedProfile();
    145   if (activeProfile->IsGuestSession()) {
    146     return [menuItem action] != @selector(newProfile:) &&
    147            [menuItem action] != @selector(editProfile:);
    148   }
    149 
    150   const AvatarMenu::Item& itemData = menu_->GetItemAt(
    151       menu_->GetActiveProfileIndex());
    152   if ([menuItem action] == @selector(switchToProfileFromDock:) ||
    153       [menuItem action] == @selector(switchToProfileFromMenu:)) {
    154     if (!itemData.managed)
    155       return YES;
    156 
    157     return [menuItem tag] == static_cast<NSInteger>(itemData.menu_index);
    158   }
    159 
    160   if ([menuItem action] == @selector(newProfile:))
    161     return !itemData.managed;
    162 
    163   return YES;
    164 }
    165 
    166 // Private /////////////////////////////////////////////////////////////////////
    167 
    168 - (NSMenu*)menu {
    169   return [mainMenuItem_ submenu];
    170 }
    171 
    172 - (void)initializeMenu {
    173   observer_.reset(new ProfileMenuControllerInternal::Observer(self));
    174   menu_.reset(new AvatarMenu(
    175       &g_browser_process->profile_manager()->GetProfileInfoCache(),
    176       observer_.get(),
    177       NULL));
    178   menu_->RebuildMenu();
    179 
    180   [[self menu] addItem:[NSMenuItem separatorItem]];
    181 
    182   NSMenuItem* item = [self createItemWithTitle:
    183           l10n_util::GetNSStringWithFixup(IDS_PROFILES_CUSTOMIZE_PROFILE)
    184                                         action:@selector(editProfile:)];
    185   [[self menu] addItem:item];
    186 
    187   [[self menu] addItem:[NSMenuItem separatorItem]];
    188   item = [self createItemWithTitle:l10n_util::GetNSStringWithFixup(
    189                                        IDS_PROFILES_CREATE_NEW_PROFILE_OPTION)
    190                             action:@selector(newProfile:)];
    191   [[self menu] addItem:item];
    192 
    193   [self rebuildMenu];
    194 }
    195 
    196 // Notifies the controller that the active browser has changed and that the
    197 // menu item and menu need to be updated to reflect that.
    198 - (void)activeBrowserChangedTo:(Browser*)browser {
    199   // Tell the menu that the browser has changed.
    200   menu_->ActiveBrowserChanged(browser);
    201 
    202   // If |browser| is NULL, it may be because the current profile was deleted
    203   // and there are no other loaded profiles. In this case, calling
    204   // |menu_->GetActiveProfileIndex()| may result in a profile being loaded,
    205   // which is inappropriate to do on the UI thread.
    206   //
    207   // An early return provides the desired behavior:
    208   //   a) If the profile was deleted, the menu would have been rebuilt and no
    209   //      profile will have a check mark.
    210   //   b) If the profile was not deleted, but there is no active browser, then
    211   //      the previous profile will remain checked.
    212   if (!browser)
    213     return;
    214 
    215   // In guest mode, there is no active menu item.
    216   size_t activeProfileIndex = browser->profile()->IsGuestSession() ?
    217       std::string::npos : menu_->GetActiveProfileIndex();
    218 
    219   // Update the state for the menu items.
    220   for (size_t i = 0; i < menu_->GetNumberOfItems(); ++i) {
    221     size_t tag = menu_->GetItemAt(i).menu_index;
    222     [[[self menu] itemWithTag:tag]
    223         setState:activeProfileIndex == tag ? NSOnState : NSOffState];
    224   }
    225 }
    226 
    227 - (void)rebuildMenu {
    228   NSMenu* menu = [self menu];
    229 
    230   for (NSMenuItem* item = [menu itemAtIndex:0];
    231        ![item isSeparatorItem];
    232        item = [menu itemAtIndex:0]) {
    233     [menu removeItemAtIndex:0];
    234   }
    235 
    236   BOOL hasContent = [self insertItemsIntoMenu:menu atOffset:0 fromDock:NO];
    237 
    238   [mainMenuItem_ setHidden:!hasContent];
    239 }
    240 
    241 - (NSMenuItem*)createItemWithTitle:(NSString*)title action:(SEL)sel {
    242   base::scoped_nsobject<NSMenuItem> item(
    243       [[NSMenuItem alloc] initWithTitle:title action:sel keyEquivalent:@""]);
    244   [item setTarget:self];
    245   return [item.release() autorelease];
    246 }
    247 
    248 @end
    249