Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / cocoa / profiles / profile_menu_controller.mm
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.
4
5 #import "chrome/browser/ui/cocoa/profiles/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_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 "components/signin/core/common/profile_management_switches.h"
23 #include "grit/generated_resources.h"
24 #include "ui/base/l10n/l10n_util_mac.h"
25 #include "ui/gfx/image/image.h"
26
27 @interface ProfileMenuController (Private)
28 - (void)initializeMenu;
29 @end
30
31 namespace ProfileMenuControllerInternal {
32
33 class Observer : public chrome::BrowserListObserver,
34                  public AvatarMenuObserver {
35  public:
36   Observer(ProfileMenuController* controller) : controller_(controller) {
37     BrowserList::AddObserver(this);
38   }
39
40   virtual ~Observer() {
41     BrowserList::RemoveObserver(this);
42   }
43
44   // chrome::BrowserListObserver:
45   virtual void OnBrowserAdded(Browser* browser) OVERRIDE {}
46   virtual void OnBrowserRemoved(Browser* browser) OVERRIDE {
47     [controller_ activeBrowserChangedTo:chrome::GetLastActiveBrowser()];
48   }
49   virtual void OnBrowserSetLastActive(Browser* browser) OVERRIDE {
50     [controller_ activeBrowserChangedTo:browser];
51   }
52
53   // AvatarMenuObserver:
54   virtual void OnAvatarMenuChanged(AvatarMenu* menu) OVERRIDE {
55     [controller_ rebuildMenu];
56   }
57
58  private:
59   ProfileMenuController* controller_;  // Weak; owns this.
60 };
61
62 }  // namespace ProfileMenuControllerInternal
63
64 ////////////////////////////////////////////////////////////////////////////////
65
66 @implementation ProfileMenuController
67
68 - (id)initWithMainMenuItem:(NSMenuItem*)item {
69   if ((self = [super init])) {
70     mainMenuItem_ = item;
71
72     base::scoped_nsobject<NSMenu> menu([[NSMenu alloc] initWithTitle:
73         l10n_util::GetNSStringWithFixup(switches::IsNewAvatarMenu() ?
74         IDS_NEW_PROFILES_OPTIONS_GROUP_NAME :
75         IDS_PROFILES_OPTIONS_GROUP_NAME)]);
76     [mainMenuItem_ setSubmenu:menu];
77
78     // This object will be constructed as part of nib loading, which happens
79     // before the message loop starts and g_browser_process is available.
80     // Schedule this on the loop to do work when the browser is ready.
81     [self performSelector:@selector(initializeMenu)
82                withObject:nil
83                afterDelay:0];
84   }
85   return self;
86 }
87
88 - (IBAction)switchToProfileFromMenu:(id)sender {
89   menu_->SwitchToProfile([sender tag], false,
90                          ProfileMetrics::SWITCH_PROFILE_MENU);
91 }
92
93 - (IBAction)switchToProfileFromDock:(id)sender {
94   // Explicitly bring to the foreground when taking action from the dock.
95   [NSApp activateIgnoringOtherApps:YES];
96   menu_->SwitchToProfile([sender tag], false,
97                          ProfileMetrics::SWITCH_PROFILE_DOCK);
98 }
99
100 - (IBAction)editProfile:(id)sender {
101   menu_->EditProfile(menu_->GetActiveProfileIndex());
102 }
103
104 - (IBAction)newProfile:(id)sender {
105   menu_->AddNewProfile(ProfileMetrics::ADD_NEW_USER_MENU);
106 }
107
108 - (BOOL)insertItemsIntoMenu:(NSMenu*)menu
109                    atOffset:(NSInteger)offset
110                    fromDock:(BOOL)dock {
111   if (!menu_ || !menu_->ShouldShowAvatarMenu())
112     return NO;
113
114   if (dock) {
115     NSString* headerName = l10n_util::GetNSStringWithFixup(
116         switches::IsNewAvatarMenu() ? IDS_NEW_PROFILES_OPTIONS_GROUP_NAME :
117                                       IDS_PROFILES_OPTIONS_GROUP_NAME);
118     base::scoped_nsobject<NSMenuItem> header(
119         [[NSMenuItem alloc] initWithTitle:headerName
120                                    action:NULL
121                             keyEquivalent:@""]);
122     [header setEnabled:NO];
123     [menu insertItem:header atIndex:offset++];
124   }
125
126   for (size_t i = 0; i < menu_->GetNumberOfItems(); ++i) {
127     const AvatarMenu::Item& itemData = menu_->GetItemAt(i);
128     NSString* name = base::SysUTF16ToNSString(itemData.name);
129     SEL action = dock ? @selector(switchToProfileFromDock:)
130                       : @selector(switchToProfileFromMenu:);
131     NSMenuItem* item = [self createItemWithTitle:name
132                                           action:action];
133     [item setTag:itemData.menu_index];
134     if (dock) {
135       [item setIndentationLevel:1];
136     } else {
137       gfx::Image itemIcon = itemData.icon;
138       // The image might be too large and need to be resized (i.e. if this is
139       // a signed-in user using the GAIA profile photo).
140       if (itemIcon.Width() > profiles::kAvatarIconWidth ||
141           itemIcon.Height() > profiles::kAvatarIconHeight) {
142         itemIcon = profiles::GetAvatarIconForWebUI(itemIcon, true);
143       }
144       DCHECK(itemIcon.Width() <= profiles::kAvatarIconWidth);
145       DCHECK(itemIcon.Height() <= profiles::kAvatarIconHeight);
146       [item setImage:itemIcon.ToNSImage()];
147       [item setState:itemData.active ? NSOnState : NSOffState];
148     }
149     [menu insertItem:item atIndex:i + offset];
150   }
151
152   return YES;
153 }
154
155 - (BOOL)validateMenuItem:(NSMenuItem*)menuItem {
156   // In guest mode, chrome://settings isn't available, so disallow creating
157   // or editing a profile.
158   Profile* activeProfile = ProfileManager::GetLastUsedProfile();
159   if (activeProfile->IsGuestSession()) {
160     return [menuItem action] != @selector(newProfile:) &&
161            [menuItem action] != @selector(editProfile:);
162   }
163
164   const AvatarMenu::Item& itemData = menu_->GetItemAt(
165       menu_->GetActiveProfileIndex());
166   if ([menuItem action] == @selector(switchToProfileFromDock:) ||
167       [menuItem action] == @selector(switchToProfileFromMenu:)) {
168     if (!itemData.supervised)
169       return YES;
170
171     return [menuItem tag] == static_cast<NSInteger>(itemData.menu_index);
172   }
173
174   if ([menuItem action] == @selector(newProfile:))
175     return !itemData.supervised;
176
177   return YES;
178 }
179
180 // Private /////////////////////////////////////////////////////////////////////
181
182 - (NSMenu*)menu {
183   return [mainMenuItem_ submenu];
184 }
185
186 - (void)initializeMenu {
187   observer_.reset(new ProfileMenuControllerInternal::Observer(self));
188   menu_.reset(new AvatarMenu(
189       &g_browser_process->profile_manager()->GetProfileInfoCache(),
190       observer_.get(),
191       NULL));
192   menu_->RebuildMenu();
193
194   [[self menu] addItem:[NSMenuItem separatorItem]];
195
196   bool usingNewProfilesUI = switches::IsNewAvatarMenu();
197   NSString* editProfileTitle = l10n_util::GetNSStringWithFixup(
198       usingNewProfilesUI ? IDS_PROFILES_MANAGE_BUTTON_LABEL :
199                            IDS_PROFILES_CUSTOMIZE_PROFILE);
200   NSString* newProfileTitle = l10n_util::GetNSStringWithFixup(
201       usingNewProfilesUI ? IDS_NEW_PROFILES_CREATE_NEW_PROFILE_OPTION :
202                            IDS_PROFILES_CREATE_NEW_PROFILE_OPTION);
203
204   NSMenuItem* item = [self createItemWithTitle:editProfileTitle
205                                         action:@selector(editProfile:)];
206   [[self menu] addItem:item];
207
208   [[self menu] addItem:[NSMenuItem separatorItem]];
209   item = [self createItemWithTitle:newProfileTitle
210                             action:@selector(newProfile:)];
211   [[self menu] addItem:item];
212
213   [self rebuildMenu];
214 }
215
216 // Notifies the controller that the active browser has changed and that the
217 // menu item and menu need to be updated to reflect that.
218 - (void)activeBrowserChangedTo:(Browser*)browser {
219   // Tell the menu that the browser has changed.
220   menu_->ActiveBrowserChanged(browser);
221
222   // If |browser| is NULL, it may be because the current profile was deleted
223   // and there are no other loaded profiles. In this case, calling
224   // |menu_->GetActiveProfileIndex()| may result in a profile being loaded,
225   // which is inappropriate to do on the UI thread.
226   //
227   // An early return provides the desired behavior:
228   //   a) If the profile was deleted, the menu would have been rebuilt and no
229   //      profile will have a check mark.
230   //   b) If the profile was not deleted, but there is no active browser, then
231   //      the previous profile will remain checked.
232   if (!browser)
233     return;
234
235   // In guest mode, there is no active menu item.
236   size_t activeProfileIndex = browser->profile()->IsGuestSession() ?
237       std::string::npos : menu_->GetActiveProfileIndex();
238
239   // Update the state for the menu items.
240   for (size_t i = 0; i < menu_->GetNumberOfItems(); ++i) {
241     size_t tag = menu_->GetItemAt(i).menu_index;
242     [[[self menu] itemWithTag:tag]
243         setState:activeProfileIndex == tag ? NSOnState : NSOffState];
244   }
245 }
246
247 - (void)rebuildMenu {
248   NSMenu* menu = [self menu];
249
250   for (NSMenuItem* item = [menu itemAtIndex:0];
251        ![item isSeparatorItem];
252        item = [menu itemAtIndex:0]) {
253     [menu removeItemAtIndex:0];
254   }
255
256   BOOL hasContent = [self insertItemsIntoMenu:menu atOffset:0 fromDock:NO];
257
258   [mainMenuItem_ setHidden:!hasContent];
259 }
260
261 - (NSMenuItem*)createItemWithTitle:(NSString*)title action:(SEL)sel {
262   base::scoped_nsobject<NSMenuItem> item(
263       [[NSMenuItem alloc] initWithTitle:title action:sel keyEquivalent:@""]);
264   [item setTarget:self];
265   return [item.release() autorelease];
266 }
267
268 @end