• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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