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 "grit/generated_resources.h"
23 #include "ui/base/l10n/l10n_util_mac.h"
24 #include "ui/gfx/image/image.h"
26 @interface ProfileMenuController (Private)
27 - (void)initializeMenu;
30 namespace ProfileMenuControllerInternal {
32 class Observer : public chrome::BrowserListObserver,
33 public AvatarMenuObserver {
35 Observer(ProfileMenuController* controller) : controller_(controller) {
36 BrowserList::AddObserver(this);
40 BrowserList::RemoveObserver(this);
43 // chrome::BrowserListObserver:
44 virtual void OnBrowserAdded(Browser* browser) OVERRIDE {}
45 virtual void OnBrowserRemoved(Browser* browser) OVERRIDE {
46 [controller_ activeBrowserChangedTo:chrome::GetLastActiveBrowser()];
48 virtual void OnBrowserSetLastActive(Browser* browser) OVERRIDE {
49 [controller_ activeBrowserChangedTo:browser];
52 // AvatarMenuObserver:
53 virtual void OnAvatarMenuChanged(AvatarMenu* menu) OVERRIDE {
54 [controller_ rebuildMenu];
58 ProfileMenuController* controller_; // Weak; owns this.
61 } // namespace ProfileMenuControllerInternal
63 ////////////////////////////////////////////////////////////////////////////////
65 @implementation ProfileMenuController
67 - (id)initWithMainMenuItem:(NSMenuItem*)item {
68 if ((self = [super init])) {
71 base::scoped_nsobject<NSMenu> menu([[NSMenu alloc] initWithTitle:
72 l10n_util::GetNSStringWithFixup(IDS_PROFILES_OPTIONS_GROUP_NAME)]);
73 [mainMenuItem_ setSubmenu:menu];
75 // This object will be constructed as part of nib loading, which happens
76 // before the message loop starts and g_browser_process is available.
77 // Schedule this on the loop to do work when the browser is ready.
78 [self performSelector:@selector(initializeMenu)
85 - (IBAction)switchToProfileFromMenu:(id)sender {
86 menu_->SwitchToProfile([sender tag], false,
87 ProfileMetrics::SWITCH_PROFILE_MENU);
90 - (IBAction)switchToProfileFromDock:(id)sender {
91 // Explicitly bring to the foreground when taking action from the dock.
92 [NSApp activateIgnoringOtherApps:YES];
93 menu_->SwitchToProfile([sender tag], false,
94 ProfileMetrics::SWITCH_PROFILE_DOCK);
97 - (IBAction)editProfile:(id)sender {
98 menu_->EditProfile(menu_->GetActiveProfileIndex());
101 - (IBAction)newProfile:(id)sender {
102 menu_->AddNewProfile(ProfileMetrics::ADD_NEW_USER_MENU);
105 - (BOOL)insertItemsIntoMenu:(NSMenu*)menu
106 atOffset:(NSInteger)offset
107 fromDock:(BOOL)dock {
108 if (!menu_ || !menu_->ShouldShowAvatarMenu())
112 NSString* headerName =
113 l10n_util::GetNSStringWithFixup(IDS_PROFILES_OPTIONS_GROUP_NAME);
114 base::scoped_nsobject<NSMenuItem> header(
115 [[NSMenuItem alloc] initWithTitle:headerName
118 [header setEnabled:NO];
119 [menu insertItem:header atIndex:offset++];
122 for (size_t i = 0; i < menu_->GetNumberOfItems(); ++i) {
123 const AvatarMenu::Item& itemData = menu_->GetItemAt(i);
124 NSString* name = base::SysUTF16ToNSString(itemData.name);
125 SEL action = dock ? @selector(switchToProfileFromDock:)
126 : @selector(switchToProfileFromMenu:);
127 NSMenuItem* item = [self createItemWithTitle:name
129 [item setTag:itemData.menu_index];
131 [item setIndentationLevel:1];
133 gfx::Image itemIcon = itemData.icon;
134 // The image might be too large and need to be resized (i.e. if this is
135 // a signed-in user using the GAIA profile photo).
136 if (itemIcon.Width() > profiles::kAvatarIconWidth ||
137 itemIcon.Height() > profiles::kAvatarIconHeight) {
138 itemIcon = profiles::GetAvatarIconForWebUI(itemIcon, true);
140 DCHECK(itemIcon.Width() <= profiles::kAvatarIconWidth);
141 DCHECK(itemIcon.Height() <= profiles::kAvatarIconHeight);
142 [item setImage:itemIcon.ToNSImage()];
143 [item setState:itemData.active ? NSOnState : NSOffState];
145 [menu insertItem:item atIndex:i + offset];
151 - (BOOL)validateMenuItem:(NSMenuItem*)menuItem {
152 // In guest mode, chrome://settings isn't available, so disallow creating
153 // or editing a profile.
154 Profile* activeProfile = ProfileManager::GetLastUsedProfile();
155 if (activeProfile->IsGuestSession()) {
156 return [menuItem action] != @selector(newProfile:) &&
157 [menuItem action] != @selector(editProfile:);
160 const AvatarMenu::Item& itemData = menu_->GetItemAt(
161 menu_->GetActiveProfileIndex());
162 if ([menuItem action] == @selector(switchToProfileFromDock:) ||
163 [menuItem action] == @selector(switchToProfileFromMenu:)) {
164 if (!itemData.managed)
167 return [menuItem tag] == static_cast<NSInteger>(itemData.menu_index);
170 if ([menuItem action] == @selector(newProfile:))
171 return !itemData.managed;
176 // Private /////////////////////////////////////////////////////////////////////
179 return [mainMenuItem_ submenu];
182 - (void)initializeMenu {
183 observer_.reset(new ProfileMenuControllerInternal::Observer(self));
184 menu_.reset(new AvatarMenu(
185 &g_browser_process->profile_manager()->GetProfileInfoCache(),
188 menu_->RebuildMenu();
190 [[self menu] addItem:[NSMenuItem separatorItem]];
192 NSMenuItem* item = [self createItemWithTitle:
193 l10n_util::GetNSStringWithFixup(IDS_PROFILES_CUSTOMIZE_PROFILE)
194 action:@selector(editProfile:)];
195 [[self menu] addItem:item];
197 [[self menu] addItem:[NSMenuItem separatorItem]];
198 item = [self createItemWithTitle:l10n_util::GetNSStringWithFixup(
199 IDS_PROFILES_CREATE_NEW_PROFILE_OPTION)
200 action:@selector(newProfile:)];
201 [[self menu] addItem:item];
206 // Notifies the controller that the active browser has changed and that the
207 // menu item and menu need to be updated to reflect that.
208 - (void)activeBrowserChangedTo:(Browser*)browser {
209 // Tell the menu that the browser has changed.
210 menu_->ActiveBrowserChanged(browser);
212 // If |browser| is NULL, it may be because the current profile was deleted
213 // and there are no other loaded profiles. In this case, calling
214 // |menu_->GetActiveProfileIndex()| may result in a profile being loaded,
215 // which is inappropriate to do on the UI thread.
217 // An early return provides the desired behavior:
218 // a) If the profile was deleted, the menu would have been rebuilt and no
219 // profile will have a check mark.
220 // b) If the profile was not deleted, but there is no active browser, then
221 // the previous profile will remain checked.
225 // In guest mode, there is no active menu item.
226 size_t activeProfileIndex = browser->profile()->IsGuestSession() ?
227 std::string::npos : menu_->GetActiveProfileIndex();
229 // Update the state for the menu items.
230 for (size_t i = 0; i < menu_->GetNumberOfItems(); ++i) {
231 size_t tag = menu_->GetItemAt(i).menu_index;
232 [[[self menu] itemWithTag:tag]
233 setState:activeProfileIndex == tag ? NSOnState : NSOffState];
237 - (void)rebuildMenu {
238 NSMenu* menu = [self menu];
240 for (NSMenuItem* item = [menu itemAtIndex:0];
241 ![item isSeparatorItem];
242 item = [menu itemAtIndex:0]) {
243 [menu removeItemAtIndex:0];
246 BOOL hasContent = [self insertItemsIntoMenu:menu atOffset:0 fromDock:NO];
248 [mainMenuItem_ setHidden:!hasContent];
251 - (NSMenuItem*)createItemWithTitle:(NSString*)title action:(SEL)sel {
252 base::scoped_nsobject<NSMenuItem> item(
253 [[NSMenuItem alloc] initWithTitle:title action:sel keyEquivalent:@""]);
254 [item setTarget:self];
255 return [item.release() autorelease];