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.
5 #import "chrome/browser/ui/cocoa/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_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"
25 @interface ProfileMenuController (Private)
26 - (void)initializeMenu;
29 namespace ProfileMenuControllerInternal {
31 class Observer : public chrome::BrowserListObserver,
32 public AvatarMenuObserver {
34 Observer(ProfileMenuController* controller) : controller_(controller) {
35 BrowserList::AddObserver(this);
39 BrowserList::RemoveObserver(this);
42 // chrome::BrowserListObserver:
43 virtual void OnBrowserAdded(Browser* browser) OVERRIDE {}
44 virtual void OnBrowserRemoved(Browser* browser) OVERRIDE {
45 [controller_ activeBrowserChangedTo:chrome::GetLastActiveBrowser()];
47 virtual void OnBrowserSetLastActive(Browser* browser) OVERRIDE {
48 [controller_ activeBrowserChangedTo:browser];
51 // AvatarMenuObserver:
52 virtual void OnAvatarMenuChanged(AvatarMenu* menu) OVERRIDE {
53 [controller_ rebuildMenu];
57 ProfileMenuController* controller_; // Weak; owns this.
60 } // namespace ProfileMenuControllerInternal
62 ////////////////////////////////////////////////////////////////////////////////
64 @implementation ProfileMenuController
66 - (id)initWithMainMenuItem:(NSMenuItem*)item {
67 if ((self = [super init])) {
70 base::scoped_nsobject<NSMenu> menu([[NSMenu alloc] initWithTitle:
71 l10n_util::GetNSStringWithFixup(IDS_PROFILES_OPTIONS_GROUP_NAME)]);
72 [mainMenuItem_ setSubmenu:menu];
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)
84 - (IBAction)switchToProfileFromMenu:(id)sender {
85 menu_->SwitchToProfile([sender tag], false);
86 ProfileMetrics::LogProfileSwitchUser(ProfileMetrics::SWITCH_PROFILE_MENU);
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);
96 - (IBAction)editProfile:(id)sender {
97 menu_->EditProfile(menu_->GetActiveProfileIndex());
100 - (IBAction)newProfile:(id)sender {
101 menu_->AddNewProfile(ProfileMetrics::ADD_NEW_USER_MENU);
104 - (BOOL)insertItemsIntoMenu:(NSMenu*)menu
105 atOffset:(NSInteger)offset
106 fromDock:(BOOL)dock {
107 if (!menu_ || !menu_->ShouldShowAvatarMenu())
111 NSString* headerName =
112 l10n_util::GetNSStringWithFixup(IDS_PROFILES_OPTIONS_GROUP_NAME);
113 base::scoped_nsobject<NSMenuItem> header(
114 [[NSMenuItem alloc] initWithTitle:headerName
117 [header setEnabled:NO];
118 [menu insertItem:header atIndex:offset++];
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
128 [item setTag:itemData.menu_index];
130 [item setIndentationLevel:1];
132 [item setImage:itemData.icon.ToNSImage()];
133 [item setState:itemData.active ? NSOnState : NSOffState];
135 [menu insertItem:item atIndex:i + offset];
141 - (BOOL)validateMenuItem:(NSMenuItem*)menuItem {
142 const AvatarMenu::Item& itemData = menu_->GetItemAt(
143 menu_->GetActiveProfileIndex());
144 if ([menuItem action] == @selector(switchToProfileFromDock:) ||
145 [menuItem action] == @selector(switchToProfileFromMenu:)) {
146 if (!itemData.managed)
149 return [menuItem tag] == static_cast<NSInteger>(itemData.menu_index);
152 if ([menuItem action] == @selector(newProfile:))
153 return !itemData.managed;
158 // Private /////////////////////////////////////////////////////////////////////
161 return [mainMenuItem_ submenu];
164 - (void)initializeMenu {
165 observer_.reset(new ProfileMenuControllerInternal::Observer(self));
166 menu_.reset(new AvatarMenu(
167 &g_browser_process->profile_manager()->GetProfileInfoCache(),
170 menu_->RebuildMenu();
172 [[self menu] addItem:[NSMenuItem separatorItem]];
174 NSMenuItem* item = [self createItemWithTitle:
175 l10n_util::GetNSStringWithFixup(IDS_PROFILES_CUSTOMIZE_PROFILE)
176 action:@selector(editProfile:)];
177 [[self menu] addItem:item];
179 [[self menu] addItem:[NSMenuItem separatorItem]];
180 item = [self createItemWithTitle:l10n_util::GetNSStringWithFixup(
181 IDS_PROFILES_CREATE_NEW_PROFILE_OPTION)
182 action:@selector(newProfile:)];
183 [[self menu] addItem:item];
188 // Notifies the controller that the active browser has changed and that the
189 // menu item and menu need to be updated to reflect that.
190 - (void)activeBrowserChangedTo:(Browser*)browser {
191 // Tell the menu that the browser has changed.
192 menu_->ActiveBrowserChanged(browser);
194 // If |browser| is NULL, it may be because the current profile was deleted
195 // and there are no other loaded profiles. In this case, calling
196 // |menu_->GetActiveProfileIndex()| may result in a profile being loaded,
197 // which is inappropriate to do on the UI thread.
199 // An early return provides the desired behavior:
200 // a) If the profile was deleted, the menu would have been rebuilt and no
201 // profile will have a check mark.
202 // b) If the profile was not deleted, but there is no active browser, then
203 // the previous profile will remain checked.
207 size_t active_profile_index = menu_->GetActiveProfileIndex();
209 // Update the state for the menu items.
210 for (size_t i = 0; i < menu_->GetNumberOfItems(); ++i) {
211 size_t tag = menu_->GetItemAt(i).menu_index;
212 [[[self menu] itemWithTag:tag]
213 setState:active_profile_index == tag ? NSOnState
218 - (void)rebuildMenu {
219 NSMenu* menu = [self menu];
221 for (NSMenuItem* item = [menu itemAtIndex:0];
222 ![item isSeparatorItem];
223 item = [menu itemAtIndex:0]) {
224 [menu removeItemAtIndex:0];
227 BOOL hasContent = [self insertItemsIntoMenu:menu atOffset:0 fromDock:NO];
229 [mainMenuItem_ setHidden:!hasContent];
232 - (NSMenuItem*)createItemWithTitle:(NSString*)title action:(SEL)sel {
233 base::scoped_nsobject<NSMenuItem> item(
234 [[NSMenuItem alloc] initWithTitle:title action:sel keyEquivalent:@""]);
235 [item setTarget:self];
236 return [item.release() autorelease];