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