OLD | NEW |
| (Empty) |
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_info_util.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" | |
25 | |
26 @interface ProfileMenuController (Private) | |
27 - (void)initializeMenu; | |
28 @end | |
29 | |
30 namespace ProfileMenuControllerInternal { | |
31 | |
32 class Observer : public chrome::BrowserListObserver, | |
33 public AvatarMenuObserver { | |
34 public: | |
35 Observer(ProfileMenuController* controller) : controller_(controller) { | |
36 BrowserList::AddObserver(this); | |
37 } | |
38 | |
39 virtual ~Observer() { | |
40 BrowserList::RemoveObserver(this); | |
41 } | |
42 | |
43 // chrome::BrowserListObserver: | |
44 virtual void OnBrowserAdded(Browser* browser) OVERRIDE {} | |
45 virtual void OnBrowserRemoved(Browser* browser) OVERRIDE { | |
46 [controller_ activeBrowserChangedTo:chrome::GetLastActiveBrowser()]; | |
47 } | |
48 virtual void OnBrowserSetLastActive(Browser* browser) OVERRIDE { | |
49 [controller_ activeBrowserChangedTo:browser]; | |
50 } | |
51 | |
52 // AvatarMenuObserver: | |
53 virtual void OnAvatarMenuChanged(AvatarMenu* menu) OVERRIDE { | |
54 [controller_ rebuildMenu]; | |
55 } | |
56 | |
57 private: | |
58 ProfileMenuController* controller_; // Weak; owns this. | |
59 }; | |
60 | |
61 } // namespace ProfileMenuControllerInternal | |
62 | |
63 //////////////////////////////////////////////////////////////////////////////// | |
64 | |
65 @implementation ProfileMenuController | |
66 | |
67 - (id)initWithMainMenuItem:(NSMenuItem*)item { | |
68 if ((self = [super init])) { | |
69 mainMenuItem_ = item; | |
70 | |
71 base::scoped_nsobject<NSMenu> menu([[NSMenu alloc] initWithTitle: | |
72 l10n_util::GetNSStringWithFixup(IDS_PROFILES_OPTIONS_GROUP_NAME)]); | |
73 [mainMenuItem_ setSubmenu:menu]; | |
74 | |
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) | |
79 withObject:nil | |
80 afterDelay:0]; | |
81 } | |
82 return self; | |
83 } | |
84 | |
85 - (IBAction)switchToProfileFromMenu:(id)sender { | |
86 menu_->SwitchToProfile([sender tag], false, | |
87 ProfileMetrics::SWITCH_PROFILE_MENU); | |
88 } | |
89 | |
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); | |
95 } | |
96 | |
97 - (IBAction)editProfile:(id)sender { | |
98 menu_->EditProfile(menu_->GetActiveProfileIndex()); | |
99 } | |
100 | |
101 - (IBAction)newProfile:(id)sender { | |
102 menu_->AddNewProfile(ProfileMetrics::ADD_NEW_USER_MENU); | |
103 } | |
104 | |
105 - (BOOL)insertItemsIntoMenu:(NSMenu*)menu | |
106 atOffset:(NSInteger)offset | |
107 fromDock:(BOOL)dock { | |
108 if (!menu_ || !menu_->ShouldShowAvatarMenu()) | |
109 return NO; | |
110 | |
111 if (dock) { | |
112 NSString* headerName = | |
113 l10n_util::GetNSStringWithFixup(IDS_PROFILES_OPTIONS_GROUP_NAME); | |
114 base::scoped_nsobject<NSMenuItem> header( | |
115 [[NSMenuItem alloc] initWithTitle:headerName | |
116 action:NULL | |
117 keyEquivalent:@""]); | |
118 [header setEnabled:NO]; | |
119 [menu insertItem:header atIndex:offset++]; | |
120 } | |
121 | |
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 | |
128 action:action]; | |
129 [item setTag:itemData.menu_index]; | |
130 if (dock) { | |
131 [item setIndentationLevel:1]; | |
132 } else { | |
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); | |
139 } | |
140 DCHECK(itemIcon.Width() <= profiles::kAvatarIconWidth); | |
141 DCHECK(itemIcon.Height() <= profiles::kAvatarIconHeight); | |
142 [item setImage:itemIcon.ToNSImage()]; | |
143 [item setState:itemData.active ? NSOnState : NSOffState]; | |
144 } | |
145 [menu insertItem:item atIndex:i + offset]; | |
146 } | |
147 | |
148 return YES; | |
149 } | |
150 | |
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:); | |
158 } | |
159 | |
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) | |
165 return YES; | |
166 | |
167 return [menuItem tag] == static_cast<NSInteger>(itemData.menu_index); | |
168 } | |
169 | |
170 if ([menuItem action] == @selector(newProfile:)) | |
171 return !itemData.managed; | |
172 | |
173 return YES; | |
174 } | |
175 | |
176 // Private ///////////////////////////////////////////////////////////////////// | |
177 | |
178 - (NSMenu*)menu { | |
179 return [mainMenuItem_ submenu]; | |
180 } | |
181 | |
182 - (void)initializeMenu { | |
183 observer_.reset(new ProfileMenuControllerInternal::Observer(self)); | |
184 menu_.reset(new AvatarMenu( | |
185 &g_browser_process->profile_manager()->GetProfileInfoCache(), | |
186 observer_.get(), | |
187 NULL)); | |
188 menu_->RebuildMenu(); | |
189 | |
190 [[self menu] addItem:[NSMenuItem separatorItem]]; | |
191 | |
192 NSMenuItem* item = [self createItemWithTitle: | |
193 l10n_util::GetNSStringWithFixup(IDS_PROFILES_CUSTOMIZE_PROFILE) | |
194 action:@selector(editProfile:)]; | |
195 [[self menu] addItem:item]; | |
196 | |
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]; | |
202 | |
203 [self rebuildMenu]; | |
204 } | |
205 | |
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); | |
211 | |
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. | |
216 // | |
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. | |
222 if (!browser) | |
223 return; | |
224 | |
225 // In guest mode, there is no active menu item. | |
226 size_t activeProfileIndex = browser->profile()->IsGuestSession() ? | |
227 std::string::npos : menu_->GetActiveProfileIndex(); | |
228 | |
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]; | |
234 } | |
235 } | |
236 | |
237 - (void)rebuildMenu { | |
238 NSMenu* menu = [self menu]; | |
239 | |
240 for (NSMenuItem* item = [menu itemAtIndex:0]; | |
241 ![item isSeparatorItem]; | |
242 item = [menu itemAtIndex:0]) { | |
243 [menu removeItemAtIndex:0]; | |
244 } | |
245 | |
246 BOOL hasContent = [self insertItemsIntoMenu:menu atOffset:0 fromDock:NO]; | |
247 | |
248 [mainMenuItem_ setHidden:!hasContent]; | |
249 } | |
250 | |
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]; | |
256 } | |
257 | |
258 @end | |
OLD | NEW |