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 #include "ash/system/user/user_view.h"
6
7 #include <algorithm>
8
9 #include "ash/multi_profile_uma.h"
10 #include "ash/popup_message.h"
11 #include "ash/session/session_state_delegate.h"
12 #include "ash/shell.h"
13 #include "ash/shell_delegate.h"
14 #include "ash/system/tray/system_tray.h"
15 #include "ash/system/tray/system_tray_delegate.h"
16 #include "ash/system/tray/tray_popup_label_button.h"
17 #include "ash/system/tray/tray_popup_label_button_border.h"
18 #include "ash/system/user/button_from_view.h"
19 #include "ash/system/user/config.h"
20 #include "ash/system/user/rounded_image_view.h"
21 #include "ash/system/user/user_card_view.h"
22 #include "components/user_manager/user_info.h"
23 #include "grit/ash_resources.h"
24 #include "grit/ash_strings.h"
25 #include "ui/base/l10n/l10n_util.h"
26 #include "ui/base/resource/resource_bundle.h"
27 #include "ui/views/layout/fill_layout.h"
28 #include "ui/views/painter.h"
29 #include "ui/wm/core/shadow_types.h"
30
31 namespace ash {
32 namespace tray {
33
34 namespace {
35
36 const int kPublicAccountLogoutButtonBorderImagesNormal[] = {
37 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
38 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
39 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
40 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
41 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
42 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
43 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
44 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
45 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
46 };
47
48 const int kPublicAccountLogoutButtonBorderImagesHovered[] = {
49 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
50 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
51 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
52 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
53 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_HOVER_BACKGROUND,
54 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
55 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
56 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
57 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
58 };
59
60 // When a hover border is used, it is starting this many pixels before the icon
61 // position.
62 const int kTrayUserTileHoverBorderInset = 10;
63
64 // Offsetting the popup message relative to the tray menu.
65 const int kPopupMessageOffset = 25;
66
67 // Switch to a user with the given |user_index|.
SwitchUser(ash::MultiProfileIndex user_index)68 void SwitchUser(ash::MultiProfileIndex user_index) {
69 // Do not switch users when the log screen is presented.
70 if (ash::Shell::GetInstance()
71 ->session_state_delegate()
72 ->IsUserSessionBlocked())
73 return;
74
75 DCHECK(user_index > 0);
76 ash::SessionStateDelegate* delegate =
77 ash::Shell::GetInstance()->session_state_delegate();
78 ash::MultiProfileUMA::RecordSwitchActiveUser(
79 ash::MultiProfileUMA::SWITCH_ACTIVE_USER_BY_TRAY);
80 delegate->SwitchActiveUser(delegate->GetUserInfo(user_index)->GetUserID());
81 }
82
83 class LogoutButton : public TrayPopupLabelButton {
84 public:
85 // If |placeholder| is true, button is used as placeholder. That means that
86 // button is inactive and is not painted, but consume the same ammount of
87 // space, as if it was painted.
LogoutButton(views::ButtonListener * listener,const base::string16 & text,bool placeholder)88 LogoutButton(views::ButtonListener* listener,
89 const base::string16& text,
90 bool placeholder)
91 : TrayPopupLabelButton(listener, text), placeholder_(placeholder) {
92 SetEnabled(!placeholder_);
93 }
94
~LogoutButton()95 virtual ~LogoutButton() {}
96
97 private:
Paint(gfx::Canvas * canvas,const views::CullSet & cull_set)98 virtual void Paint(gfx::Canvas* canvas,
99 const views::CullSet& cull_set) OVERRIDE {
100 // Just skip paint if this button used as a placeholder.
101 if (!placeholder_)
102 TrayPopupLabelButton::Paint(canvas, cull_set);
103 }
104
105 bool placeholder_;
106 DISALLOW_COPY_AND_ASSIGN(LogoutButton);
107 };
108
109 class UserViewMouseWatcherHost : public views::MouseWatcherHost {
110 public:
UserViewMouseWatcherHost(const gfx::Rect & screen_area)111 explicit UserViewMouseWatcherHost(const gfx::Rect& screen_area)
112 : screen_area_(screen_area) {}
~UserViewMouseWatcherHost()113 virtual ~UserViewMouseWatcherHost() {}
114
115 // Implementation of MouseWatcherHost.
Contains(const gfx::Point & screen_point,views::MouseWatcherHost::MouseEventType type)116 virtual bool Contains(const gfx::Point& screen_point,
117 views::MouseWatcherHost::MouseEventType type) OVERRIDE {
118 return screen_area_.Contains(screen_point);
119 }
120
121 private:
122 gfx::Rect screen_area_;
123
124 DISALLOW_COPY_AND_ASSIGN(UserViewMouseWatcherHost);
125 };
126
127 // The menu item view which gets shown when the user clicks in multi profile
128 // mode onto the user item.
129 class AddUserView : public views::View {
130 public:
131 // The |owner| is the view for which this view gets created.
132 AddUserView(ButtonFromView* owner);
133 virtual ~AddUserView();
134
135 // Get the anchor view for a message.
anchor()136 views::View* anchor() { return anchor_; }
137
138 private:
139 // Overridden from views::View.
140 virtual gfx::Size GetPreferredSize() const OVERRIDE;
141
142 // Create the additional client content for this item.
143 void AddContent();
144
145 // This is the content we create and show.
146 views::View* add_user_;
147
148 // This is the owner view of this item.
149 ButtonFromView* owner_;
150
151 // The anchor view for targetted bubble messages.
152 views::View* anchor_;
153
154 DISALLOW_COPY_AND_ASSIGN(AddUserView);
155 };
156
AddUserView(ButtonFromView * owner)157 AddUserView::AddUserView(ButtonFromView* owner)
158 : add_user_(NULL), owner_(owner), anchor_(NULL) {
159 AddContent();
160 owner_->ForceBorderVisible(true);
161 }
162
~AddUserView()163 AddUserView::~AddUserView() {
164 owner_->ForceBorderVisible(false);
165 }
166
GetPreferredSize() const167 gfx::Size AddUserView::GetPreferredSize() const {
168 return owner_->bounds().size();
169 }
170
AddContent()171 void AddUserView::AddContent() {
172 SetLayoutManager(new views::FillLayout());
173 set_background(views::Background::CreateSolidBackground(kBackgroundColor));
174
175 add_user_ = new views::View;
176 add_user_->SetBorder(views::Border::CreateEmptyBorder(
177 0, kTrayUserTileHoverBorderInset, 0, 0));
178
179 add_user_->SetLayoutManager(new views::BoxLayout(
180 views::BoxLayout::kHorizontal, 0, 0, kTrayPopupPaddingBetweenItems));
181 AddChildViewAt(add_user_, 0);
182
183 // Add the [+] icon which is also the anchor for messages.
184 RoundedImageView* icon = new RoundedImageView(kTrayAvatarCornerRadius, true);
185 anchor_ = icon;
186 icon->SetImage(*ui::ResourceBundle::GetSharedInstance()
187 .GetImageNamed(IDR_AURA_UBER_TRAY_ADD_MULTIPROFILE_USER)
188 .ToImageSkia(),
189 gfx::Size(kTrayAvatarSize, kTrayAvatarSize));
190 add_user_->AddChildView(icon);
191
192 // Add the command text.
193 views::Label* command_label = new views::Label(
194 l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_SIGN_IN_ANOTHER_ACCOUNT));
195 command_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
196 add_user_->AddChildView(command_label);
197 }
198
199 } // namespace
200
UserView(SystemTrayItem * owner,user::LoginStatus login,MultiProfileIndex index,bool for_detailed_view)201 UserView::UserView(SystemTrayItem* owner,
202 user::LoginStatus login,
203 MultiProfileIndex index,
204 bool for_detailed_view)
205 : multiprofile_index_(index),
206 user_card_view_(NULL),
207 owner_(owner),
208 is_user_card_button_(false),
209 logout_button_(NULL),
210 add_user_enabled_(true),
211 for_detailed_view_(for_detailed_view),
212 focus_manager_(NULL) {
213 CHECK_NE(user::LOGGED_IN_NONE, login);
214 if (!index) {
215 // Only the logged in user will have a background. All other users will have
216 // to allow the TrayPopupContainer highlighting the menu line.
217 set_background(views::Background::CreateSolidBackground(
218 login == user::LOGGED_IN_PUBLIC ? kPublicAccountBackgroundColor
219 : kBackgroundColor));
220 }
221 SetLayoutManager(new views::BoxLayout(
222 views::BoxLayout::kHorizontal, 0, 0, kTrayPopupPaddingBetweenItems));
223 // The logout button must be added before the user card so that the user card
224 // can correctly calculate the remaining available width.
225 // Note that only the current multiprofile user gets a button.
226 if (!multiprofile_index_)
227 AddLogoutButton(login);
228 AddUserCard(login);
229 }
230
~UserView()231 UserView::~UserView() {
232 RemoveAddUserMenuOption();
233 }
234
MouseMovedOutOfHost()235 void UserView::MouseMovedOutOfHost() {
236 RemoveAddUserMenuOption();
237 }
238
GetStateForTest() const239 TrayUser::TestState UserView::GetStateForTest() const {
240 if (add_menu_option_.get()) {
241 return add_user_enabled_ ? TrayUser::ACTIVE : TrayUser::ACTIVE_BUT_DISABLED;
242 }
243
244 if (!is_user_card_button_)
245 return TrayUser::SHOWN;
246
247 return static_cast<ButtonFromView*>(user_card_view_)->is_hovered_for_test()
248 ? TrayUser::HOVERED
249 : TrayUser::SHOWN;
250 }
251
GetBoundsInScreenOfUserButtonForTest()252 gfx::Rect UserView::GetBoundsInScreenOfUserButtonForTest() {
253 DCHECK(user_card_view_);
254 return user_card_view_->GetBoundsInScreen();
255 }
256
GetPreferredSize() const257 gfx::Size UserView::GetPreferredSize() const {
258 gfx::Size size = views::View::GetPreferredSize();
259 // Only the active user panel will be forced to a certain height.
260 if (!multiprofile_index_) {
261 size.set_height(
262 std::max(size.height(), kTrayPopupItemHeight + GetInsets().height()));
263 }
264 return size;
265 }
266
GetHeightForWidth(int width) const267 int UserView::GetHeightForWidth(int width) const {
268 return GetPreferredSize().height();
269 }
270
Layout()271 void UserView::Layout() {
272 gfx::Rect contents_area(GetContentsBounds());
273 if (user_card_view_ && logout_button_) {
274 // Give the logout button the space it requests.
275 gfx::Rect logout_area = contents_area;
276 logout_area.ClampToCenteredSize(logout_button_->GetPreferredSize());
277 logout_area.set_x(contents_area.right() - logout_area.width());
278
279 // Give the remaining space to the user card.
280 gfx::Rect user_card_area = contents_area;
281 int remaining_width = contents_area.width() - logout_area.width();
282 if (IsMultiProfileSupportedAndUserActive() ||
283 IsMultiAccountSupportedAndUserActive()) {
284 // In multiprofile/multiaccount case |user_card_view_| and
285 // |logout_button_| have to have the same height.
286 int y = std::min(user_card_area.y(), logout_area.y());
287 int height = std::max(user_card_area.height(), logout_area.height());
288 logout_area.set_y(y);
289 logout_area.set_height(height);
290 user_card_area.set_y(y);
291 user_card_area.set_height(height);
292
293 // In multiprofile mode we have also to increase the size of the card by
294 // the size of the border to make it overlap with the logout button.
295 user_card_area.set_width(std::max(0, remaining_width + 1));
296
297 // To make the logout button symmetrical with the user card we also make
298 // the button longer by the same size the hover area in front of the icon
299 // got inset.
300 logout_area.set_width(logout_area.width() +
301 kTrayUserTileHoverBorderInset);
302 } else {
303 // In all other modes we have to make sure that there is enough spacing
304 // between the two.
305 remaining_width -= kTrayPopupPaddingBetweenItems;
306 }
307 user_card_area.set_width(remaining_width);
308 user_card_view_->SetBoundsRect(user_card_area);
309 logout_button_->SetBoundsRect(logout_area);
310 } else if (user_card_view_) {
311 user_card_view_->SetBoundsRect(contents_area);
312 } else if (logout_button_) {
313 logout_button_->SetBoundsRect(contents_area);
314 }
315 }
316
ButtonPressed(views::Button * sender,const ui::Event & event)317 void UserView::ButtonPressed(views::Button* sender, const ui::Event& event) {
318 if (sender == logout_button_) {
319 Shell::GetInstance()->metrics()->RecordUserMetricsAction(
320 ash::UMA_STATUS_AREA_SIGN_OUT);
321 RemoveAddUserMenuOption();
322 Shell::GetInstance()->system_tray_delegate()->SignOut();
323 } else if (sender == user_card_view_ && !multiprofile_index_ &&
324 IsMultiAccountSupportedAndUserActive()) {
325 owner_->TransitionDetailedView();
326 } else if (sender == user_card_view_ &&
327 IsMultiProfileSupportedAndUserActive()) {
328 if (!multiprofile_index_) {
329 ToggleAddUserMenuOption();
330 } else {
331 RemoveAddUserMenuOption();
332 SwitchUser(multiprofile_index_);
333 // Since the user list is about to change the system menu should get
334 // closed.
335 owner_->system_tray()->CloseSystemBubble();
336 }
337 } else if (add_menu_option_.get() &&
338 sender == add_menu_option_->GetContentsView()) {
339 RemoveAddUserMenuOption();
340 // Let the user add another account to the session.
341 MultiProfileUMA::RecordSigninUser(MultiProfileUMA::SIGNIN_USER_BY_TRAY);
342 Shell::GetInstance()->system_tray_delegate()->ShowUserLogin();
343 owner_->system_tray()->CloseSystemBubble();
344 } else {
345 NOTREACHED();
346 }
347 }
348
OnWillChangeFocus(View * focused_before,View * focused_now)349 void UserView::OnWillChangeFocus(View* focused_before, View* focused_now) {
350 if (focused_now)
351 RemoveAddUserMenuOption();
352 }
353
OnDidChangeFocus(View * focused_before,View * focused_now)354 void UserView::OnDidChangeFocus(View* focused_before, View* focused_now) {
355 // Nothing to do here.
356 }
357
AddLogoutButton(user::LoginStatus login)358 void UserView::AddLogoutButton(user::LoginStatus login) {
359 const base::string16 title =
360 user::GetLocalizedSignOutStringForStatus(login, true);
361 TrayPopupLabelButton* logout_button =
362 new LogoutButton(this, title, for_detailed_view_);
363 logout_button->SetAccessibleName(title);
364 logout_button_ = logout_button;
365 // In public account mode, the logout button border has a custom color.
366 if (login == user::LOGGED_IN_PUBLIC) {
367 scoped_ptr<TrayPopupLabelButtonBorder> border(
368 new TrayPopupLabelButtonBorder());
369 border->SetPainter(false,
370 views::Button::STATE_NORMAL,
371 views::Painter::CreateImageGridPainter(
372 kPublicAccountLogoutButtonBorderImagesNormal));
373 border->SetPainter(false,
374 views::Button::STATE_HOVERED,
375 views::Painter::CreateImageGridPainter(
376 kPublicAccountLogoutButtonBorderImagesHovered));
377 border->SetPainter(false,
378 views::Button::STATE_PRESSED,
379 views::Painter::CreateImageGridPainter(
380 kPublicAccountLogoutButtonBorderImagesHovered));
381 logout_button_->SetBorder(border.PassAs<views::Border>());
382 }
383 AddChildView(logout_button_);
384 }
385
AddUserCard(user::LoginStatus login)386 void UserView::AddUserCard(user::LoginStatus login) {
387 // Add padding around the panel.
388 SetBorder(views::Border::CreateEmptyBorder(kTrayPopupUserCardVerticalPadding,
389 kTrayPopupPaddingHorizontal,
390 kTrayPopupUserCardVerticalPadding,
391 kTrayPopupPaddingHorizontal));
392
393 views::TrayBubbleView* bubble_view =
394 owner_->system_tray()->GetSystemBubble()->bubble_view();
395 int max_card_width =
396 bubble_view->GetMaximumSize().width() -
397 (2 * kTrayPopupPaddingHorizontal + kTrayPopupPaddingBetweenItems);
398 if (logout_button_)
399 max_card_width -= logout_button_->GetPreferredSize().width();
400 user_card_view_ =
401 new UserCardView(login, max_card_width, multiprofile_index_);
402 // The entry is clickable when no system modal dialog is open and one of the
403 // multi user options is active.
404 bool clickable = !Shell::GetInstance()->IsSystemModalWindowOpen() &&
405 (IsMultiProfileSupportedAndUserActive() ||
406 IsMultiAccountSupportedAndUserActive());
407 if (clickable) {
408 // To allow the border to start before the icon, reduce the size before and
409 // add an inset to the icon to get the spacing.
410 if (!multiprofile_index_) {
411 SetBorder(views::Border::CreateEmptyBorder(
412 kTrayPopupUserCardVerticalPadding,
413 kTrayPopupPaddingHorizontal - kTrayUserTileHoverBorderInset,
414 kTrayPopupUserCardVerticalPadding,
415 kTrayPopupPaddingHorizontal));
416 user_card_view_->SetBorder(views::Border::CreateEmptyBorder(
417 0, kTrayUserTileHoverBorderInset, 0, 0));
418 }
419 gfx::Insets insets = gfx::Insets(1, 1, 1, 1);
420 views::View* contents_view = user_card_view_;
421 ButtonFromView* button = NULL;
422 if (!for_detailed_view_) {
423 if (multiprofile_index_) {
424 // Since the activation border needs to be drawn around the tile, we
425 // have to put the tile into another view which fills the menu panel,
426 // but keeping the offsets of the content.
427 contents_view = new views::View();
428 contents_view->SetBorder(views::Border::CreateEmptyBorder(
429 kTrayPopupUserCardVerticalPadding,
430 kTrayPopupPaddingHorizontal,
431 kTrayPopupUserCardVerticalPadding,
432 kTrayPopupPaddingHorizontal));
433 contents_view->SetLayoutManager(new views::FillLayout());
434 SetBorder(views::Border::CreateEmptyBorder(0, 0, 0, 0));
435 contents_view->AddChildView(user_card_view_);
436 insets = gfx::Insets(1, 1, 1, 3);
437 }
438 button = new ButtonFromView(contents_view,
439 this,
440 !multiprofile_index_,
441 insets);
442 // TODO(skuhne): For accessibility we need to call |SetAccessibleName|
443 // with a useful name (string freeze for M37 has passed).
444 } else {
445 // We want user card for detailed view to have exactly the same look
446 // as user card for default view. That's why we wrap it in a button
447 // without click listener and special hover behavior.
448 button = new ButtonFromView(contents_view, NULL, false, insets);
449 }
450 // A click on the button should not trigger a focus change.
451 button->set_request_focus_on_press(false);
452 user_card_view_ = button;
453 is_user_card_button_ = true;
454 }
455 AddChildViewAt(user_card_view_, 0);
456 // Card for supervised user can consume more space than currently
457 // available. In that case we should increase system bubble's width.
458 if (login == user::LOGGED_IN_PUBLIC)
459 bubble_view->SetWidth(GetPreferredSize().width());
460 }
461
ToggleAddUserMenuOption()462 void UserView::ToggleAddUserMenuOption() {
463 if (add_menu_option_.get()) {
464 RemoveAddUserMenuOption();
465 return;
466 }
467
468 // Note: We do not need to install a global event handler to delete this
469 // item since it will destroyed automatically before the menu / user menu item
470 // gets destroyed..
471 add_menu_option_.reset(new views::Widget);
472 views::Widget::InitParams params;
473 params.type = views::Widget::InitParams::TYPE_TOOLTIP;
474 params.keep_on_top = true;
475 params.context = this->GetWidget()->GetNativeWindow();
476 params.accept_events = true;
477 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
478 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
479 add_menu_option_->Init(params);
480 add_menu_option_->SetOpacity(0xFF);
481 add_menu_option_->GetNativeWindow()->set_owned_by_parent(false);
482 SetShadowType(add_menu_option_->GetNativeView(), wm::SHADOW_TYPE_NONE);
483
484 // Position it below our user card.
485 gfx::Rect bounds = user_card_view_->GetBoundsInScreen();
486 bounds.set_y(bounds.y() + bounds.height());
487 add_menu_option_->SetBounds(bounds);
488
489 // Show the content.
490 add_menu_option_->SetAlwaysOnTop(true);
491 add_menu_option_->Show();
492
493 AddUserView* add_user_view =
494 new AddUserView(static_cast<ButtonFromView*>(user_card_view_));
495
496 const SessionStateDelegate* delegate =
497 Shell::GetInstance()->session_state_delegate();
498
499 SessionStateDelegate::AddUserError add_user_error;
500 add_user_enabled_ = delegate->CanAddUserToMultiProfile(&add_user_error);
501
502 ButtonFromView* button = new ButtonFromView(add_user_view,
503 add_user_enabled_ ? this : NULL,
504 add_user_enabled_,
505 gfx::Insets(1, 1, 1, 1));
506 button->set_request_focus_on_press(false);
507 button->SetAccessibleName(
508 l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_SIGN_IN_ANOTHER_ACCOUNT));
509 button->ForceBorderVisible(true);
510 add_menu_option_->SetContentsView(button);
511
512 if (add_user_enabled_) {
513 // We activate the entry automatically if invoked with focus.
514 if (user_card_view_->HasFocus()) {
515 button->GetFocusManager()->SetFocusedView(button);
516 user_card_view_->GetFocusManager()->SetFocusedView(button);
517 }
518 } else {
519 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
520 int message_id = 0;
521 switch (add_user_error) {
522 case SessionStateDelegate::ADD_USER_ERROR_NOT_ALLOWED_PRIMARY_USER:
523 message_id = IDS_ASH_STATUS_TRAY_MESSAGE_NOT_ALLOWED_PRIMARY_USER;
524 break;
525 case SessionStateDelegate::ADD_USER_ERROR_MAXIMUM_USERS_REACHED:
526 message_id = IDS_ASH_STATUS_TRAY_MESSAGE_CANNOT_ADD_USER;
527 break;
528 case SessionStateDelegate::ADD_USER_ERROR_OUT_OF_USERS:
529 message_id = IDS_ASH_STATUS_TRAY_MESSAGE_OUT_OF_USERS;
530 break;
531 default:
532 NOTREACHED() << "Unknown adding user error " << add_user_error;
533 }
534
535 popup_message_.reset(new PopupMessage(
536 bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_CAPTION_CANNOT_ADD_USER),
537 bundle.GetLocalizedString(message_id),
538 PopupMessage::ICON_WARNING,
539 add_user_view->anchor(),
540 views::BubbleBorder::TOP_LEFT,
541 gfx::Size(parent()->bounds().width() - kPopupMessageOffset, 0),
542 2 * kPopupMessageOffset));
543 }
544 // Find the screen area which encloses both elements and sets then a mouse
545 // watcher which will close the "menu".
546 gfx::Rect area = user_card_view_->GetBoundsInScreen();
547 area.set_height(2 * area.height());
548 mouse_watcher_.reset(
549 new views::MouseWatcher(new UserViewMouseWatcherHost(area), this));
550 mouse_watcher_->Start();
551 // Install a listener to focus changes so that we can remove the card when
552 // the focus gets changed. When called through the destruction of the bubble,
553 // the FocusManager cannot be determined anymore and we remember it here.
554 focus_manager_ = user_card_view_->GetFocusManager();
555 focus_manager_->AddFocusChangeListener(this);
556 }
557
RemoveAddUserMenuOption()558 void UserView::RemoveAddUserMenuOption() {
559 if (!add_menu_option_.get())
560 return;
561 focus_manager_->RemoveFocusChangeListener(this);
562 focus_manager_ = NULL;
563 if (user_card_view_->GetFocusManager())
564 user_card_view_->GetFocusManager()->ClearFocus();
565 popup_message_.reset();
566 mouse_watcher_.reset();
567 add_menu_option_.reset();
568 }
569
570 } // namespace tray
571 } // namespace ash
572