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