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/avatar_base_controller.h"
7 #include "base/mac/foundation_util.h"
8 #include "chrome/app/chrome_command_ids.h"
9 #include "chrome/browser/browser_process.h"
10 #include "chrome/browser/profiles/profile_info_cache_observer.h"
11 #include "chrome/browser/profiles/profile_avatar_icon_util.h"
12 #include "chrome/browser/profiles/profile_manager.h"
13 #include "chrome/browser/profiles/profile_metrics.h"
14 #include "chrome/browser/signin/signin_header_helper.h"
15 #include "chrome/browser/profiles/profiles_state.h"
16 #include "chrome/browser/profiles/profile_window.h"
17 #include "chrome/browser/ui/browser.h"
18 #include "chrome/browser/ui/browser_commands.h"
19 #include "chrome/browser/ui/browser_window.h"
20 #import "chrome/browser/ui/cocoa/base_bubble_controller.h"
21 #import "chrome/browser/ui/cocoa/browser_window_controller.h"
22 #import "chrome/browser/ui/cocoa/profiles/avatar_menu_bubble_controller.h"
23 #import "chrome/browser/ui/cocoa/profiles/profile_chooser_controller.h"
24 #include "components/signin/core/browser/signin_error_controller.h"
25 #include "components/signin/core/common/profile_management_switches.h"
27 // Space between the avatar icon and the avatar menu bubble.
28 const CGFloat kMenuYOffsetAdjust = 1.0;
29 // Offset needed to align the edge of the avatar bubble with the edge of the
31 const CGFloat kMenuXOffsetAdjust = 2.0;
33 @interface AvatarBaseController (Private)
34 // Shows the avatar bubble.
35 - (IBAction)buttonClicked:(id)sender;
36 - (IBAction)buttonRightClicked:(id)sender;
38 - (void)bubbleWillClose:(NSNotification*)notif;
40 // Updates the profile name displayed by the avatar button. If |layoutParent| is
41 // yes, then the BrowserWindowController is notified to relayout the subviews,
42 // as the button needs to be repositioned.
43 - (void)updateAvatarButtonAndLayoutParent:(BOOL)layoutParent;
45 // Displays an error icon if any accounts associated with this profile have an
47 - (void)updateErrorStatus:(BOOL)hasError;
50 class ProfileInfoUpdateObserver : public ProfileInfoCacheObserver,
51 public SigninErrorController::Observer {
53 ProfileInfoUpdateObserver(Profile* profile,
54 AvatarBaseController* avatarController)
56 avatarController_(avatarController) {
57 g_browser_process->profile_manager()->
58 GetProfileInfoCache().AddObserver(this);
60 // Subscribe to authentication error changes so that the avatar button
62 SigninErrorController* errorController =
63 profiles::GetSigninErrorController(profile_);
65 errorController->AddObserver(this);
68 ~ProfileInfoUpdateObserver() override {
69 g_browser_process->profile_manager()->
70 GetProfileInfoCache().RemoveObserver(this);
71 SigninErrorController* errorController =
72 profiles::GetSigninErrorController(profile_);
74 errorController->RemoveObserver(this);
77 // ProfileInfoCacheObserver:
78 void OnProfileAdded(const base::FilePath& profile_path) override {
79 [avatarController_ updateAvatarButtonAndLayoutParent:YES];
82 void OnProfileWasRemoved(const base::FilePath& profile_path,
83 const base::string16& profile_name) override {
84 // If deleting the active profile, don't bother updating the avatar
85 // button, as the browser window is being closed anyway.
86 if (profile_->GetPath() != profile_path)
87 [avatarController_ updateAvatarButtonAndLayoutParent:YES];
90 void OnProfileNameChanged(const base::FilePath& profile_path,
91 const base::string16& old_profile_name) override {
92 if (profile_->GetPath() == profile_path)
93 [avatarController_ updateAvatarButtonAndLayoutParent:YES];
96 void OnProfileAvatarChanged(const base::FilePath& profile_path) override {
97 if (!switches::IsNewAvatarMenu() && profile_->GetPath() == profile_path)
98 [avatarController_ updateAvatarButtonAndLayoutParent:YES];
101 void OnProfileSupervisedUserIdChanged(
102 const base::FilePath& profile_path) override {
103 if (profile_->GetPath() == profile_path)
104 [avatarController_ updateAvatarButtonAndLayoutParent:YES];
107 // SigninErrorController::Observer:
108 void OnErrorChanged() override {
109 SigninErrorController* errorController =
110 profiles::GetSigninErrorController(profile_);
112 [avatarController_ updateErrorStatus:errorController->HasError()];
117 AvatarBaseController* avatarController_; // Weak; owns this.
119 DISALLOW_COPY_AND_ASSIGN(ProfileInfoUpdateObserver);
122 @implementation AvatarBaseController
124 - (id)initWithBrowser:(Browser*)browser {
125 if ((self = [super init])) {
127 profileInfoObserver_.reset(
128 new ProfileInfoUpdateObserver(browser_->profile(), self));
134 [[NSNotificationCenter defaultCenter]
136 name:NSWindowWillCloseNotification
137 object:[menuController_ window]];
141 - (NSButton*)buttonView {
142 CHECK(button_.get()); // Subclasses must set this.
143 return button_.get();
146 - (void)showAvatarBubbleAnchoredAt:(NSView*)anchor
147 withMode:(BrowserWindow::AvatarBubbleMode)mode
148 withServiceType:(signin::GAIAServiceType)serviceType {
149 if (menuController_) {
150 if (switches::IsNewAvatarMenu()) {
151 profiles::BubbleViewMode viewMode;
152 profiles::TutorialMode tutorialMode;
153 profiles::BubbleViewModeFromAvatarBubbleMode(
154 mode, &viewMode, &tutorialMode);
155 if (tutorialMode != profiles::TUTORIAL_MODE_NONE) {
156 ProfileChooserController* profileChooserController =
157 base::mac::ObjCCastStrict<ProfileChooserController>(
159 [profileChooserController setTutorialMode:tutorialMode];
160 [profileChooserController initMenuContentsWithView:viewMode];
166 DCHECK(chrome::IsCommandEnabled(browser_, IDC_SHOW_AVATAR_MENU));
168 NSWindowController* wc =
169 [browser_->window()->GetNativeWindow() windowController];
170 if ([wc isKindOfClass:[BrowserWindowController class]]) {
171 [static_cast<BrowserWindowController*>(wc)
172 lockBarVisibilityForOwner:self withAnimation:NO delay:NO];
175 // The new avatar bubble does not have an arrow, and it should be anchored
176 // to the edge of the avatar button.
177 int anchorX = switches::IsNewAvatarMenu() ?
178 NSMaxX([anchor bounds]) - kMenuXOffsetAdjust :
179 NSMidX([anchor bounds]);
180 NSPoint point = NSMakePoint(anchorX,
181 NSMaxY([anchor bounds]) + kMenuYOffsetAdjust);
182 point = [anchor convertPoint:point toView:nil];
183 point = [[anchor window] convertBaseToScreen:point];
185 // |menuController_| will automatically release itself on close.
186 if (switches::IsNewAvatarMenu()) {
187 profiles::BubbleViewMode viewMode;
188 profiles::TutorialMode tutorialMode;
189 profiles::BubbleViewModeFromAvatarBubbleMode(
190 mode, &viewMode, &tutorialMode);
191 // Don't start creating the view if it would be an empty fast user switcher.
192 // It has to happen here to prevent the view system from creating an empty
194 if (viewMode == profiles::BUBBLE_VIEW_MODE_FAST_PROFILE_CHOOSER &&
195 profiles::HasProfileSwitchTargets(browser_->profile())) {
200 [[ProfileChooserController alloc] initWithBrowser:browser_
203 tutorialMode:tutorialMode
204 serviceType:serviceType];
207 [[AvatarMenuBubbleController alloc] initWithBrowser:browser_
211 [[NSNotificationCenter defaultCenter]
213 selector:@selector(bubbleWillClose:)
214 name:NSWindowWillCloseNotification
215 object:[menuController_ window]];
216 [menuController_ showWindow:self];
218 ProfileMetrics::LogProfileOpenMethod(ProfileMetrics::ICON_AVATAR_BUBBLE);
221 - (IBAction)buttonClicked:(id)sender {
222 BrowserWindow::AvatarBubbleMode mode =
223 BrowserWindow::AVATAR_BUBBLE_MODE_DEFAULT;
225 [self showAvatarBubbleAnchoredAt:button_
227 withServiceType:signin::GAIA_SERVICE_TYPE_NONE];
230 - (IBAction)buttonRightClicked:(id)sender {
231 BrowserWindow::AvatarBubbleMode mode =
232 BrowserWindow::AVATAR_BUBBLE_MODE_FAST_USER_SWITCH;
234 [self showAvatarBubbleAnchoredAt:button_
236 withServiceType:signin::GAIA_SERVICE_TYPE_NONE];
239 - (void)bubbleWillClose:(NSNotification*)notif {
240 NSWindowController* wc =
241 [browser_->window()->GetNativeWindow() windowController];
242 if ([wc isKindOfClass:[BrowserWindowController class]]) {
243 [static_cast<BrowserWindowController*>(wc)
244 releaseBarVisibilityForOwner:self withAnimation:YES delay:NO];
246 menuController_ = nil;
249 - (void)updateAvatarButtonAndLayoutParent:(BOOL)layoutParent {
253 - (void)updateErrorStatus:(BOOL)hasError {
256 - (BaseBubbleController*)menuController {
257 return menuController_;