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
3 // found in the LICENSE file.
5 #import "chrome/browser/ui/cocoa/profiles/profile_menu_controller.h"
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_avatar_icon_util.h"
14 #include "chrome/browser/profiles/profile_info_cache.h"
15 #include "chrome/browser/profiles/profile_info_interface.h"
16 #include "chrome/browser/profiles/profile_manager.h"
17 #include "chrome/browser/profiles/profile_metrics.h"
18 #include "chrome/browser/ui/browser.h"
19 #include "chrome/browser/ui/browser_list.h"
20 #include "chrome/browser/ui/browser_list_observer.h"
21 #include "chrome/browser/ui/cocoa/last_active_browser_cocoa.h"
22 #include "chrome/grit/generated_resources.h"
23 #include "components/signin/core/common/profile_management_switches.h"
24 #include "ui/base/l10n/l10n_util_mac.h"
25 #include "ui/gfx/image/image.h"
27 @interface ProfileMenuController (Private)
28 - (void)initializeMenu;
31 namespace ProfileMenuControllerInternal {
33 class Observer : public chrome::BrowserListObserver,
34 public AvatarMenuObserver {
36 Observer(ProfileMenuController* controller) : controller_(controller) {
37 BrowserList::AddObserver(this);
41 BrowserList::RemoveObserver(this);
44 // chrome::BrowserListObserver:
45 virtual void OnBrowserAdded(Browser* browser) OVERRIDE {}
46 virtual void OnBrowserRemoved(Browser* browser) OVERRIDE {
47 [controller_ activeBrowserChangedTo:chrome::GetLastActiveBrowser()];
49 virtual void OnBrowserSetLastActive(Browser* browser) OVERRIDE {
50 [controller_ activeBrowserChangedTo:browser];
53 // AvatarMenuObserver:
54 virtual void OnAvatarMenuChanged(AvatarMenu* menu) OVERRIDE {
55 [controller_ rebuildMenu];
59 ProfileMenuController* controller_; // Weak; owns this.
62 } // namespace ProfileMenuControllerInternal
64 ////////////////////////////////////////////////////////////////////////////////
66 @implementation ProfileMenuController
68 - (id)initWithMainMenuItem:(NSMenuItem*)item {
69 if ((self = [super init])) {
72 base::scoped_nsobject<NSMenu> menu([[NSMenu alloc] initWithTitle:
73 l10n_util::GetNSStringWithFixup(IDS_PROFILES_OPTIONS_GROUP_NAME)]);
74 [mainMenuItem_ setSubmenu:menu];
76 // This object will be constructed as part of nib loading, which happens
77 // before the message loop starts and g_browser_process is available.
78 // Schedule this on the loop to do work when the browser is ready.
79 [self performSelector:@selector(initializeMenu)
86 - (IBAction)switchToProfileFromMenu:(id)sender {
87 menu_->SwitchToProfile([sender tag], false,
88 ProfileMetrics::SWITCH_PROFILE_MENU);
91 - (IBAction)switchToProfileFromDock:(id)sender {
92 // Explicitly bring to the foreground when taking action from the dock.
93 [NSApp activateIgnoringOtherApps:YES];
94 menu_->SwitchToProfile([sender tag], false,
95 ProfileMetrics::SWITCH_PROFILE_DOCK);
98 - (IBAction)editProfile:(id)sender {
99 menu_->EditProfile(menu_->GetActiveProfileIndex());
102 - (IBAction)newProfile:(id)sender {
103 menu_->AddNewProfile(ProfileMetrics::ADD_NEW_USER_MENU);
106 - (BOOL)insertItemsIntoMenu:(NSMenu*)menu
107 atOffset:(NSInteger)offset
108 fromDock:(BOOL)dock {
109 if (!menu_ || !menu_->ShouldShowAvatarMenu())
113 NSString* headerName =
114 l10n_util::GetNSStringWithFixup(IDS_PROFILES_OPTIONS_GROUP_NAME);
115 base::scoped_nsobject<NSMenuItem> header(
116 [[NSMenuItem alloc] initWithTitle:headerName
119 [header setEnabled:NO];
120 [menu insertItem:header atIndex:offset++];
123 for (size_t i = 0; i < menu_->GetNumberOfItems(); ++i) {
124 const AvatarMenu::Item& itemData = menu_->GetItemAt(i);
125 NSString* name = base::SysUTF16ToNSString(itemData.name);
126 SEL action = dock ? @selector(switchToProfileFromDock:)
127 : @selector(switchToProfileFromMenu:);
128 NSMenuItem* item = [self createItemWithTitle:name
130 [item setTag:itemData.menu_index];
132 [item setIndentationLevel:1];
134 gfx::Image itemIcon = itemData.icon;
135 // The image might be too large and need to be resized (i.e. if this is
136 // a signed-in user using the GAIA profile photo).
137 if (itemIcon.Width() > profiles::kAvatarIconWidth ||
138 itemIcon.Height() > profiles::kAvatarIconHeight) {
139 itemIcon = profiles::GetAvatarIconForWebUI(itemIcon, true);
141 DCHECK(itemIcon.Width() <= profiles::kAvatarIconWidth);
142 DCHECK(itemIcon.Height() <= profiles::kAvatarIconHeight);
143 [item setImage:itemIcon.ToNSImage()];
144 [item setState:itemData.active ? NSOnState : NSOffState];
146 [menu insertItem:item atIndex:i + offset];
152 - (BOOL)validateMenuItem:(NSMenuItem*)menuItem {
153 // In guest mode, chrome://settings isn't available, so disallow creating
154 // or editing a profile.
155 Profile* activeProfile = ProfileManager::GetLastUsedProfile();
156 if (activeProfile->IsGuestSession()) {
157 return [menuItem action] != @selector(newProfile:) &&
158 [menuItem action] != @selector(editProfile:);
161 const AvatarMenu::Item& itemData = menu_->GetItemAt(
162 menu_->GetActiveProfileIndex());
163 if ([menuItem action] == @selector(switchToProfileFromDock:) ||
164 [menuItem action] == @selector(switchToProfileFromMenu:)) {
165 if (!itemData.supervised)
168 return [menuItem tag] == static_cast<NSInteger>(itemData.menu_index);
171 if ([menuItem action] == @selector(newProfile:))
172 return !itemData.supervised;
177 // Private /////////////////////////////////////////////////////////////////////
180 return [mainMenuItem_ submenu];
183 - (void)initializeMenu {
184 observer_.reset(new ProfileMenuControllerInternal::Observer(self));
185 menu_.reset(new AvatarMenu(
186 &g_browser_process->profile_manager()->GetProfileInfoCache(),
189 menu_->RebuildMenu();
191 [[self menu] addItem:[NSMenuItem separatorItem]];
193 NSMenuItem* item = [self createItemWithTitle:
194 l10n_util::GetNSStringWithFixup(IDS_PROFILES_MANAGE_BUTTON_LABEL)
195 action:@selector(editProfile:)];
196 [[self menu] addItem:item];
198 [[self menu] addItem:[NSMenuItem separatorItem]];
199 item = [self createItemWithTitle:l10n_util::GetNSStringWithFixup(
200 IDS_PROFILES_CREATE_NEW_PROFILE_OPTION)
201 action:@selector(newProfile:)];
202 [[self menu] addItem:item];
207 // Notifies the controller that the active browser has changed and that the
208 // menu item and menu need to be updated to reflect that.
209 - (void)activeBrowserChangedTo:(Browser*)browser {
210 // Tell the menu that the browser has changed.
211 menu_->ActiveBrowserChanged(browser);
213 // If |browser| is NULL, it may be because the current profile was deleted
214 // and there are no other loaded profiles. In this case, calling
215 // |menu_->GetActiveProfileIndex()| may result in a profile being loaded,
216 // which is inappropriate to do on the UI thread.
218 // An early return provides the desired behavior:
219 // a) If the profile was deleted, the menu would have been rebuilt and no
220 // profile will have a check mark.
221 // b) If the profile was not deleted, but there is no active browser, then
222 // the previous profile will remain checked.
226 // In guest mode, there is no active menu item.
227 size_t activeProfileIndex = browser->profile()->IsGuestSession() ?
228 std::string::npos : menu_->GetActiveProfileIndex();
230 // Update the state for the menu items.
231 for (size_t i = 0; i < menu_->GetNumberOfItems(); ++i) {
232 size_t tag = menu_->GetItemAt(i).menu_index;
233 [[[self menu] itemWithTag:tag]
234 setState:activeProfileIndex == tag ? NSOnState : NSOffState];
238 - (void)rebuildMenu {
239 NSMenu* menu = [self menu];
241 for (NSMenuItem* item = [menu itemAtIndex:0];
242 ![item isSeparatorItem];
243 item = [menu itemAtIndex:0]) {
244 [menu removeItemAtIndex:0];
247 BOOL hasContent = [self insertItemsIntoMenu:menu atOffset:0 fromDock:NO];
249 [mainMenuItem_ setHidden:!hasContent];
252 - (NSMenuItem*)createItemWithTitle:(NSString*)title action:(SEL)sel {
253 base::scoped_nsobject<NSMenuItem> item(
254 [[NSMenuItem alloc] initWithTitle:title action:sel keyEquivalent:@""]);
255 [item setTarget:self];
256 return [item.release() autorelease];