tray_user.cc revision 8bcbed890bc3ce4d7a057a8f32cab53fa534672e
1// Copyright (c) 2012 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/tray_user.h"
6
7#include <algorithm>
8#include <climits>
9#include <vector>
10
11#include "ash/ash_switches.h"
12#include "ash/multi_profile_uma.h"
13#include "ash/popup_message.h"
14#include "ash/root_window_controller.h"
15#include "ash/session_state_delegate.h"
16#include "ash/shelf/shelf_layout_manager.h"
17#include "ash/shell.h"
18#include "ash/shell_delegate.h"
19#include "ash/system/tray/system_tray.h"
20#include "ash/system/tray/system_tray_delegate.h"
21#include "ash/system/tray/system_tray_notifier.h"
22#include "ash/system/tray/tray_constants.h"
23#include "ash/system/tray/tray_item_view.h"
24#include "ash/system/tray/tray_popup_label_button.h"
25#include "ash/system/tray/tray_popup_label_button_border.h"
26#include "ash/system/tray/tray_utils.h"
27#include "base/i18n/rtl.h"
28#include "base/logging.h"
29#include "base/memory/scoped_vector.h"
30#include "base/strings/string16.h"
31#include "base/strings/string_util.h"
32#include "base/strings/utf_string_conversions.h"
33#include "grit/ash_resources.h"
34#include "grit/ash_strings.h"
35#include "skia/ext/image_operations.h"
36#include "third_party/skia/include/core/SkCanvas.h"
37#include "third_party/skia/include/core/SkPaint.h"
38#include "third_party/skia/include/core/SkPath.h"
39#include "ui/aura/window.h"
40#include "ui/base/l10n/l10n_util.h"
41#include "ui/base/resource/resource_bundle.h"
42#include "ui/gfx/canvas.h"
43#include "ui/gfx/font.h"
44#include "ui/gfx/image/image.h"
45#include "ui/gfx/image/image_skia_operations.h"
46#include "ui/gfx/insets.h"
47#include "ui/gfx/range/range.h"
48#include "ui/gfx/rect.h"
49#include "ui/gfx/render_text.h"
50#include "ui/gfx/size.h"
51#include "ui/gfx/skia_util.h"
52#include "ui/gfx/text_elider.h"
53#include "ui/views/border.h"
54#include "ui/views/bubble/tray_bubble_view.h"
55#include "ui/views/controls/button/button.h"
56#include "ui/views/controls/button/custom_button.h"
57#include "ui/views/controls/image_view.h"
58#include "ui/views/controls/label.h"
59#include "ui/views/controls/link.h"
60#include "ui/views/controls/link_listener.h"
61#include "ui/views/corewm/shadow_types.h"
62#include "ui/views/layout/box_layout.h"
63#include "ui/views/layout/fill_layout.h"
64#include "ui/views/mouse_watcher.h"
65#include "ui/views/painter.h"
66#include "ui/views/view.h"
67#include "ui/views/widget/widget.h"
68
69namespace {
70
71const int kUserDetailsVerticalPadding = 5;
72const int kUserCardVerticalPadding = 10;
73const int kProfileRoundedCornerRadius = 2;
74const int kUserIconSize = 27;
75const int kUserIconLargeSize = 32;
76const int kUserIconLargeCornerRadius = 2;
77const int kUserLabelToIconPadding = 5;
78// When using multi login, this spacing is added between user icons.
79const int kTrayLabelSpacing = 1;
80
81// When a hover border is used, it is starting this many pixels before the icon
82// position.
83const int kTrayUserTileHoverBorderInset = 10;
84
85// The border color of the user button.
86const SkColor kBorderColor = 0xffdcdcdc;
87
88// The invisible word joiner character, used as a marker to indicate the start
89// and end of the user's display name in the public account user card's text.
90const char16 kDisplayNameMark[] = { 0x2060, 0 };
91
92const int kPublicAccountLogoutButtonBorderImagesNormal[] = {
93    IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
94    IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
95    IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
96    IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
97    IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
98    IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
99    IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
100    IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
101    IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
102};
103
104const int kPublicAccountLogoutButtonBorderImagesHovered[] = {
105    IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
106    IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
107    IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
108    IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
109    IDR_AURA_TRAY_POPUP_LABEL_BUTTON_HOVER_BACKGROUND,
110    IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
111    IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
112    IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
113    IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
114};
115
116// Offsetting the popup message relative to the tray menu.
117const int kPopupMessageOffset = 25;
118
119// Switch to a user with the given |user_index|.
120void SwitchUser(ash::MultiProfileIndex user_index) {
121  DCHECK(user_index > 0);
122  ash::SessionStateDelegate* delegate =
123      ash::Shell::GetInstance()->session_state_delegate();
124  ash::MultiProfileUMA::RecordSwitchActiveUser(
125      ash::MultiProfileUMA::SWITCH_ACTIVE_USER_BY_TRAY);
126  delegate->SwitchActiveUser(delegate->GetUserID(user_index));
127}
128
129}  // namespace
130
131namespace ash {
132namespace internal {
133
134namespace tray {
135
136// A custom image view with rounded edges.
137class RoundedImageView : public views::View {
138 public:
139  // Constructs a new rounded image view with rounded corners of radius
140  // |corner_radius|. If |active_user| is set, the icon will be drawn in
141  // full colors - otherwise it will fade into the background.
142  RoundedImageView(int corner_radius, bool active_user);
143  virtual ~RoundedImageView();
144
145  // Set the image that should be displayed. The image contents is copied to the
146  // receiver's image.
147  void SetImage(const gfx::ImageSkia& img, const gfx::Size& size);
148
149  // Set the radii of the corners independently.
150  void SetCornerRadii(int top_left,
151                      int top_right,
152                      int bottom_right,
153                      int bottom_left);
154
155 private:
156  // Overridden from views::View.
157  virtual gfx::Size GetPreferredSize() OVERRIDE;
158  virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
159
160  gfx::ImageSkia image_;
161  gfx::ImageSkia resized_;
162  gfx::Size image_size_;
163  int corner_radius_[4];
164
165  // True if the given user is the active user and the icon should get
166  // painted as active.
167  bool active_user_;
168
169  DISALLOW_COPY_AND_ASSIGN(RoundedImageView);
170};
171
172// An inactive user view which can be clicked to make active. Note that this
173// "button" does not show as a button any click or hover changes.
174class UserSwitcherView : public RoundedImageView {
175 public:
176  UserSwitcherView(int corner_radius, MultiProfileIndex user_index);
177  virtual ~UserSwitcherView() {}
178
179  virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
180  virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE;
181
182 private:
183  // The user index to activate when the item was clicked. Note that this
184  // index refers to the LRU list of logged in users.
185  MultiProfileIndex user_index_;
186
187  DISALLOW_COPY_AND_ASSIGN(UserSwitcherView);
188};
189
190// The user details shown in public account mode. This is essentially a label
191// but with custom painting code as the text is styled with multiple colors and
192// contains a link.
193class PublicAccountUserDetails : public views::View,
194                                 public views::LinkListener {
195 public:
196  PublicAccountUserDetails(SystemTrayItem* owner, int used_width);
197  virtual ~PublicAccountUserDetails();
198
199 private:
200  // Overridden from views::View.
201  virtual void Layout() OVERRIDE;
202  virtual gfx::Size GetPreferredSize() OVERRIDE;
203  virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
204
205  // Overridden from views::LinkListener.
206  virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE;
207
208  // Calculate a preferred size that ensures the label text and the following
209  // link do not wrap over more than three lines in total for aesthetic reasons
210  // if possible.
211  void CalculatePreferredSize(SystemTrayItem* owner, int used_width);
212
213  base::string16 text_;
214  views::Link* learn_more_;
215  gfx::Size preferred_size_;
216  ScopedVector<gfx::RenderText> lines_;
217
218  DISALLOW_COPY_AND_ASSIGN(PublicAccountUserDetails);
219};
220
221// The button which holds the user card in case of multi profile.
222class UserCard : public views::CustomButton {
223 public:
224  UserCard(views::ButtonListener* listener, bool active_user);
225  virtual ~UserCard();
226
227  // Called when the border should remain even in the non highlighted state.
228  void ForceBorderVisible(bool show);
229
230  // Overridden from views::View
231  virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE;
232  virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE;
233
234  // Check if the item is hovered.
235  bool is_hovered_for_test() {return button_hovered_; }
236
237 private:
238  // Change the hover/active state of the "button" when the status changes.
239  void ShowActive();
240
241  // True if this is the active user.
242  bool is_active_user_;
243
244  // True if button is hovered.
245  bool button_hovered_;
246
247  // True if the border should be visible.
248  bool show_border_;
249
250  DISALLOW_COPY_AND_ASSIGN(UserCard);
251};
252
253class UserViewMouseWatcherHost : public views::MouseWatcherHost {
254public:
255 explicit UserViewMouseWatcherHost(const gfx::Rect& screen_area)
256     : screen_area_(screen_area) {}
257 virtual ~UserViewMouseWatcherHost() {}
258
259 // Implementation of MouseWatcherHost.
260 virtual bool Contains(const gfx::Point& screen_point,
261                       views::MouseWatcherHost::MouseEventType type) OVERRIDE {
262   return screen_area_.Contains(screen_point);
263 }
264
265private:
266 gfx::Rect screen_area_;
267
268 DISALLOW_COPY_AND_ASSIGN(UserViewMouseWatcherHost);
269};
270
271// The view of a user item.
272class UserView : public views::View,
273                 public views::ButtonListener,
274                 public views::MouseWatcherListener {
275 public:
276  UserView(SystemTrayItem* owner,
277           ash::user::LoginStatus login,
278           MultiProfileIndex index);
279  virtual ~UserView();
280
281  // Overridden from MouseWatcherListener:
282  virtual void MouseMovedOutOfHost() OVERRIDE;
283
284  TrayUser::TestState GetStateForTest() const;
285  gfx::Rect GetBoundsInScreenOfUserButtonForTest();
286
287 private:
288  // Overridden from views::View.
289  virtual gfx::Size GetPreferredSize() OVERRIDE;
290  virtual int GetHeightForWidth(int width) OVERRIDE;
291  virtual void Layout() OVERRIDE;
292
293  // Overridden from views::ButtonListener.
294  virtual void ButtonPressed(views::Button* sender,
295                             const ui::Event& event) OVERRIDE;
296
297  void AddLogoutButton(user::LoginStatus login);
298  void AddUserCard(SystemTrayItem* owner, user::LoginStatus login);
299
300  // Create a user icon representation for the user card.
301  views::View* CreateIconForUserCard(user::LoginStatus login);
302
303  // Create the additional user card content for the retail logged in mode.
304  void AddLoggedInRetailModeUserCardContent();
305
306  // Create the additional user card content for the public mode.
307  void AddLoggedInPublicModeUserCardContent(SystemTrayItem* owner);
308
309  // Create the menu option to add another user. If |disabled| is set the user
310  // cannot actively click on the item.
311  void ToggleAddUserMenuOption();
312
313  // Returns true when multi profile is supported.
314  bool SupportsMultiProfile();
315
316  MultiProfileIndex multiprofile_index_;
317  // The view of the user card.
318  views::View* user_card_view_;
319
320  // This is the owner system tray item of this view.
321  SystemTrayItem* owner_;
322
323  // True if |user_card_view_| is a |UserView| - otherwise it is only a
324  // |views::View|.
325  bool is_user_card_;
326  views::View* logout_button_;
327  scoped_ptr<PopupMessage> popup_message_;
328  scoped_ptr<views::Widget> add_menu_option_;
329
330  // True when the add user panel is visible but not activatable.
331  bool add_user_visible_but_disabled_;
332
333  // The mouse watcher which takes care of out of window hover events.
334  scoped_ptr<views::MouseWatcher> mouse_watcher_;
335
336  DISALLOW_COPY_AND_ASSIGN(UserView);
337};
338
339// The menu item view which gets shown when the user clicks in multi profile
340// mode onto the user item.
341class AddUserView : public views::CustomButton,
342                    public views::ButtonListener {
343 public:
344  // The |owner| is the view for which this view gets created. The |listener|
345  // will get notified when this item gets clicked.
346  AddUserView(UserCard* owner, views::ButtonListener* listener);
347  virtual ~AddUserView();
348
349  // Get the anchor view for a message.
350  views::View* anchor() { return anchor_; }
351
352  // Overridden from views::ButtonListener.
353  virtual void ButtonPressed(views::Button* sender,
354                             const ui::Event& event) OVERRIDE;
355
356 private:
357  // Overridden from views::View.
358  virtual gfx::Size GetPreferredSize() OVERRIDE;
359  virtual int GetHeightForWidth(int width) OVERRIDE;
360  virtual void Layout() OVERRIDE;
361
362  // Create the additional client content for this item.
363  void AddContent();
364
365  // This is the content we create and show.
366  views::View* add_user_;
367
368  // This listener will get informed when someone clicks on this button.
369  views::ButtonListener* listener_;
370
371  // This is the owner view of this item.
372  UserCard* owner_;
373
374  // The anchor view for targetted bubble messages.
375  views::View* anchor_;
376
377  DISALLOW_COPY_AND_ASSIGN(AddUserView);
378};
379
380RoundedImageView::RoundedImageView(int corner_radius, bool active_user)
381    : active_user_(active_user) {
382  for (int i = 0; i < 4; ++i)
383    corner_radius_[i] = corner_radius;
384}
385
386RoundedImageView::~RoundedImageView() {}
387
388void RoundedImageView::SetImage(const gfx::ImageSkia& img,
389                                const gfx::Size& size) {
390  image_ = img;
391  image_size_ = size;
392
393  // Try to get the best image quality for the avatar.
394  resized_ = gfx::ImageSkiaOperations::CreateResizedImage(image_,
395      skia::ImageOperations::RESIZE_BEST, size);
396  if (GetWidget() && visible()) {
397    PreferredSizeChanged();
398    SchedulePaint();
399  }
400}
401
402void RoundedImageView::SetCornerRadii(int top_left,
403                                      int top_right,
404                                      int bottom_right,
405                                      int bottom_left) {
406  corner_radius_[0] = top_left;
407  corner_radius_[1] = top_right;
408  corner_radius_[2] = bottom_right;
409  corner_radius_[3] = bottom_left;
410}
411
412gfx::Size RoundedImageView::GetPreferredSize() {
413  return gfx::Size(image_size_.width() + GetInsets().width(),
414                   image_size_.height() + GetInsets().height());
415}
416
417void RoundedImageView::OnPaint(gfx::Canvas* canvas) {
418  View::OnPaint(canvas);
419  gfx::Rect image_bounds(size());
420  image_bounds.ClampToCenteredSize(GetPreferredSize());
421  image_bounds.Inset(GetInsets());
422  const SkScalar kRadius[8] = {
423    SkIntToScalar(corner_radius_[0]),
424    SkIntToScalar(corner_radius_[0]),
425    SkIntToScalar(corner_radius_[1]),
426    SkIntToScalar(corner_radius_[1]),
427    SkIntToScalar(corner_radius_[2]),
428    SkIntToScalar(corner_radius_[2]),
429    SkIntToScalar(corner_radius_[3]),
430    SkIntToScalar(corner_radius_[3])
431  };
432  SkPath path;
433  path.addRoundRect(gfx::RectToSkRect(image_bounds), kRadius);
434  SkPaint paint;
435  paint.setAntiAlias(true);
436  paint.setXfermodeMode(active_user_ ? SkXfermode::kSrcOver_Mode :
437                                       SkXfermode::kLuminosity_Mode);
438  canvas->DrawImageInPath(resized_, image_bounds.x(), image_bounds.y(),
439                          path, paint);
440}
441
442UserSwitcherView::UserSwitcherView(int corner_radius,
443                                   MultiProfileIndex user_index)
444    : RoundedImageView(corner_radius, false),
445      user_index_(user_index) {
446  SetEnabled(true);
447}
448
449void UserSwitcherView::OnMouseEvent(ui::MouseEvent* event) {
450  if (event->type() == ui::ET_MOUSE_PRESSED) {
451    SwitchUser(user_index_);
452    event->SetHandled();
453  }
454}
455
456void UserSwitcherView::OnTouchEvent(ui::TouchEvent* event) {
457  if (event->type() == ui::ET_TOUCH_PRESSED) {
458    SwitchUser(user_index_);
459    event->SetHandled();
460  }
461}
462
463PublicAccountUserDetails::PublicAccountUserDetails(SystemTrayItem* owner,
464                                                   int used_width)
465    : learn_more_(NULL) {
466  const int inner_padding =
467      kTrayPopupPaddingHorizontal - kTrayPopupPaddingBetweenItems;
468  const bool rtl = base::i18n::IsRTL();
469  set_border(views::Border::CreateEmptyBorder(
470      kUserDetailsVerticalPadding, rtl ? 0 : inner_padding,
471      kUserDetailsVerticalPadding, rtl ? inner_padding : 0));
472
473  // Retrieve the user's display name and wrap it with markers.
474  // Note that since this is a public account it always has to be the primary
475  // user.
476  base::string16 display_name =
477      Shell::GetInstance()->session_state_delegate()->GetUserDisplayName(0);
478  RemoveChars(display_name, kDisplayNameMark, &display_name);
479  display_name = kDisplayNameMark[0] + display_name + kDisplayNameMark[0];
480  // Retrieve the domain managing the device and wrap it with markers.
481  base::string16 domain = UTF8ToUTF16(
482      Shell::GetInstance()->system_tray_delegate()->GetEnterpriseDomain());
483  RemoveChars(domain, kDisplayNameMark, &domain);
484  base::i18n::WrapStringWithLTRFormatting(&domain);
485  // Retrieve the label text, inserting the display name and domain.
486  text_ = l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_PUBLIC_LABEL,
487                                     display_name, domain);
488
489  learn_more_ = new views::Link(l10n_util::GetStringUTF16(IDS_ASH_LEARN_MORE));
490  learn_more_->SetUnderline(false);
491  learn_more_->set_listener(this);
492  AddChildView(learn_more_);
493
494  CalculatePreferredSize(owner, used_width);
495}
496
497PublicAccountUserDetails::~PublicAccountUserDetails() {}
498
499void PublicAccountUserDetails::Layout() {
500  lines_.clear();
501  const gfx::Rect contents_area = GetContentsBounds();
502  if (contents_area.IsEmpty())
503    return;
504
505  // Word-wrap the label text.
506  const gfx::Font font;
507  std::vector<base::string16> lines;
508  gfx::ElideRectangleText(text_, font, contents_area.width(),
509                          contents_area.height(), gfx::ELIDE_LONG_WORDS,
510                          &lines);
511  // Loop through the lines, creating a renderer for each.
512  gfx::Point position = contents_area.origin();
513  gfx::Range display_name(gfx::Range::InvalidRange());
514  for (std::vector<base::string16>::const_iterator it = lines.begin();
515       it != lines.end(); ++it) {
516    gfx::RenderText* line = gfx::RenderText::CreateInstance();
517    line->SetDirectionalityMode(gfx::DIRECTIONALITY_FROM_UI);
518    line->SetText(*it);
519    const gfx::Size size(contents_area.width(), line->GetStringSize().height());
520    line->SetDisplayRect(gfx::Rect(position, size));
521    position.set_y(position.y() + size.height());
522
523    // Set the default text color for the line.
524    line->SetColor(kPublicAccountUserCardTextColor);
525
526    // If a range of the line contains the user's display name, apply a custom
527    // text color to it.
528    if (display_name.is_empty())
529      display_name.set_start(it->find(kDisplayNameMark));
530    if (!display_name.is_empty()) {
531      display_name.set_end(
532          it->find(kDisplayNameMark, display_name.start() + 1));
533      gfx::Range line_range(0, it->size());
534      line->ApplyColor(kPublicAccountUserCardNameColor,
535                       display_name.Intersect(line_range));
536      // Update the range for the next line.
537      if (display_name.end() >= line_range.end())
538        display_name.set_start(0);
539      else
540        display_name = gfx::Range::InvalidRange();
541    }
542
543    lines_.push_back(line);
544  }
545
546  // Position the link after the label text, separated by a space. If it does
547  // not fit onto the last line of the text, wrap the link onto its own line.
548  const gfx::Size last_line_size = lines_.back()->GetStringSize();
549  const int space_width = font.GetStringWidth(ASCIIToUTF16(" "));
550  const gfx::Size link_size = learn_more_->GetPreferredSize();
551  if (contents_area.width() - last_line_size.width() >=
552      space_width + link_size.width()) {
553    position.set_x(position.x() + last_line_size.width() + space_width);
554    position.set_y(position.y() - last_line_size.height());
555  }
556  position.set_y(position.y() - learn_more_->GetInsets().top());
557  gfx::Rect learn_more_bounds(position, link_size);
558  learn_more_bounds.Intersect(contents_area);
559  if (base::i18n::IsRTL()) {
560    const gfx::Insets insets = GetInsets();
561    learn_more_bounds.Offset(insets.right() - insets.left(), 0);
562  }
563  learn_more_->SetBoundsRect(learn_more_bounds);
564}
565
566gfx::Size PublicAccountUserDetails::GetPreferredSize() {
567  return preferred_size_;
568}
569
570void PublicAccountUserDetails::OnPaint(gfx::Canvas* canvas) {
571  for (ScopedVector<gfx::RenderText>::const_iterator it = lines_.begin();
572       it != lines_.end(); ++it) {
573    (*it)->Draw(canvas);
574  }
575  views::View::OnPaint(canvas);
576}
577
578void PublicAccountUserDetails::LinkClicked(views::Link* source,
579                                           int event_flags) {
580  DCHECK_EQ(source, learn_more_);
581  Shell::GetInstance()->system_tray_delegate()->ShowPublicAccountInfo();
582}
583
584void PublicAccountUserDetails::CalculatePreferredSize(SystemTrayItem* owner,
585                                                      int used_width) {
586  const gfx::Font font;
587  const gfx::Size link_size = learn_more_->GetPreferredSize();
588  const int space_width = font.GetStringWidth(ASCIIToUTF16(" "));
589  const gfx::Insets insets = GetInsets();
590  views::TrayBubbleView* bubble_view =
591      owner->system_tray()->GetSystemBubble()->bubble_view();
592  int min_width = std::max(
593      link_size.width(),
594      bubble_view->GetPreferredSize().width() - (used_width + insets.width()));
595  int max_width = std::min(
596      font.GetStringWidth(text_) + space_width + link_size.width(),
597      bubble_view->GetMaximumSize().width() - (used_width + insets.width()));
598  // Do a binary search for the minimum width that ensures no more than three
599  // lines are needed. The lower bound is the minimum of the current bubble
600  // width and the width of the link (as no wrapping is permitted inside the
601  // link). The upper bound is the maximum of the largest allowed bubble width
602  // and the sum of the label text and link widths when put on a single line.
603  std::vector<base::string16> lines;
604  while (min_width < max_width) {
605    lines.clear();
606    const int width = (min_width + max_width) / 2;
607    const bool too_narrow = gfx::ElideRectangleText(
608        text_, font, width, INT_MAX, gfx::TRUNCATE_LONG_WORDS, &lines) != 0;
609    int line_count = lines.size();
610    if (!too_narrow && line_count == 3 &&
611            width - font.GetStringWidth(lines.back()) <=
612            space_width + link_size.width()) {
613      ++line_count;
614    }
615    if (too_narrow || line_count > 3)
616      min_width = width + 1;
617    else
618      max_width = width;
619  }
620
621  // Calculate the corresponding height and set the preferred size.
622  lines.clear();
623  gfx::ElideRectangleText(
624      text_, font, min_width, INT_MAX, gfx::TRUNCATE_LONG_WORDS, &lines);
625  int line_count = lines.size();
626  if (min_width - font.GetStringWidth(lines.back()) <=
627      space_width + link_size.width()) {
628    ++line_count;
629  }
630  const int line_height = font.GetHeight();
631  const int link_extra_height = std::max(
632      link_size.height() - learn_more_->GetInsets().top() - line_height, 0);
633  preferred_size_ = gfx::Size(
634      min_width + insets.width(),
635      line_count * line_height + link_extra_height + insets.height());
636
637  bubble_view->SetWidth(preferred_size_.width() + used_width);
638}
639
640UserCard::UserCard(views::ButtonListener* listener, bool active_user)
641    : CustomButton(listener),
642      is_active_user_(active_user),
643      button_hovered_(false),
644      show_border_(false) {
645  if (is_active_user_) {
646    set_background(
647        views::Background::CreateSolidBackground(kBackgroundColor));
648    ShowActive();
649  }
650}
651
652UserCard::~UserCard() {}
653
654void UserCard::ForceBorderVisible(bool show) {
655  show_border_ = show;
656  ShowActive();
657}
658
659void UserCard::OnMouseEntered(const ui::MouseEvent& event) {
660  if (is_active_user_) {
661    button_hovered_ = true;
662    background()->SetNativeControlColor(kHoverBackgroundColor);
663    ShowActive();
664  }
665}
666
667void UserCard::OnMouseExited(const ui::MouseEvent& event) {
668  if (is_active_user_) {
669    button_hovered_ = false;
670    background()->SetNativeControlColor(kBackgroundColor);
671    ShowActive();
672  }
673}
674
675void UserCard::ShowActive() {
676  int width = button_hovered_ || show_border_ ? 1 : 0;
677  set_border(views::Border::CreateSolidSidedBorder(width, width, width, 1,
678                                                   kBorderColor));
679  SchedulePaint();
680}
681
682UserView::UserView(SystemTrayItem* owner,
683                   user::LoginStatus login,
684                   MultiProfileIndex index)
685    : multiprofile_index_(index),
686      user_card_view_(NULL),
687      owner_(owner),
688      is_user_card_(false),
689      logout_button_(NULL),
690      add_user_visible_but_disabled_(false) {
691  CHECK_NE(user::LOGGED_IN_NONE, login);
692  if (!index) {
693    // Only the logged in user will have a background. All other users will have
694    // to allow the TrayPopupContainer highlighting the menu line.
695    set_background(views::Background::CreateSolidBackground(
696        login == user::LOGGED_IN_PUBLIC ? kPublicAccountBackgroundColor :
697                                          kBackgroundColor));
698  }
699  SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0,
700                                        kTrayPopupPaddingBetweenItems));
701  // The logout button must be added before the user card so that the user card
702  // can correctly calculate the remaining available width.
703  // Note that only the current multiprofile user gets a button.
704  if (!multiprofile_index_)
705    AddLogoutButton(login);
706  AddUserCard(owner, login);
707}
708
709UserView::~UserView() {}
710
711void UserView::MouseMovedOutOfHost() {
712  popup_message_.reset();
713  mouse_watcher_.reset();
714  add_menu_option_.reset();
715}
716
717TrayUser::TestState UserView::GetStateForTest() const {
718  if (add_menu_option_.get()) {
719    return add_user_visible_but_disabled_ ? TrayUser::ACTIVE_BUT_DISABLED :
720                                            TrayUser::ACTIVE;
721  }
722
723  if (!is_user_card_)
724    return TrayUser::SHOWN;
725
726  return static_cast<UserCard*>(user_card_view_)->is_hovered_for_test() ?
727      TrayUser::HOVERED : TrayUser::SHOWN;
728}
729
730gfx::Rect UserView::GetBoundsInScreenOfUserButtonForTest() {
731  DCHECK(user_card_view_);
732  return user_card_view_->GetBoundsInScreen();
733}
734
735gfx::Size UserView::GetPreferredSize() {
736  gfx::Size size = views::View::GetPreferredSize();
737  // Only the active user panel will be forced to a certain height.
738  if (!multiprofile_index_) {
739    size.set_height(std::max(size.height(),
740                             kTrayPopupItemHeight + GetInsets().height()));
741  }
742  return size;
743}
744
745int UserView::GetHeightForWidth(int width) {
746  return GetPreferredSize().height();
747}
748
749void UserView::Layout() {
750  gfx::Rect contents_area(GetContentsBounds());
751  if (user_card_view_ && logout_button_) {
752    // Give the logout button the space it requests.
753    gfx::Rect logout_area = contents_area;
754    logout_area.ClampToCenteredSize(logout_button_->GetPreferredSize());
755    logout_area.set_x(contents_area.right() - logout_area.width());
756
757    // Give the remaining space to the user card.
758    gfx::Rect user_card_area = contents_area;
759    int remaining_width = contents_area.width() - logout_area.width();
760    if (SupportsMultiProfile()) {
761      // In multiprofile case |user_card_view_| and |logout_button_| have to
762      // have the same height.
763      int y = std::min(user_card_area.y(), logout_area.y());
764      int height = std::max(user_card_area.height(), logout_area.height());
765      logout_area.set_y(y);
766      logout_area.set_height(height);
767      user_card_area.set_y(y);
768      user_card_area.set_height(height);
769
770      // In multiprofile mode we have also to increase the size of the card by
771      // the size of the border to make it overlap with the logout button.
772      user_card_area.set_width(std::max(0, remaining_width + 1));
773
774      // To make the logout button symmetrical with the user card we also make
775      // the button longer by the same size the hover area in front of the icon
776      // got inset.
777      logout_area.set_width(logout_area.width() +
778                            kTrayUserTileHoverBorderInset);
779    } else {
780      // In all other modes we have to make sure that there is enough spacing
781      // between the two.
782      remaining_width -= kTrayPopupPaddingBetweenItems;
783    }
784    user_card_area.set_width(remaining_width);
785    user_card_view_->SetBoundsRect(user_card_area);
786    logout_button_->SetBoundsRect(logout_area);
787  } else if (user_card_view_) {
788    user_card_view_->SetBoundsRect(contents_area);
789  } else if (logout_button_) {
790    logout_button_->SetBoundsRect(contents_area);
791  }
792}
793
794void UserView::ButtonPressed(views::Button* sender, const ui::Event& event) {
795  if (sender == logout_button_) {
796    Shell::GetInstance()->system_tray_delegate()->SignOut();
797  } else if (sender == user_card_view_ && SupportsMultiProfile()) {
798    if (!multiprofile_index_) {
799      ToggleAddUserMenuOption();
800    } else {
801      SwitchUser(multiprofile_index_);
802      // Since the user list is about to change the system menu should get
803      // closed.
804      owner_->system_tray()->CloseSystemBubble();
805    }
806  } else if (add_menu_option_.get() &&
807             sender == add_menu_option_->GetContentsView()) {
808    // Let the user add another account to the session.
809    MultiProfileUMA::RecordSigninUser(MultiProfileUMA::SIGNIN_USER_BY_TRAY);
810    Shell::GetInstance()->system_tray_delegate()->ShowUserLogin();
811  } else {
812    NOTREACHED();
813  }
814}
815
816void UserView::AddLogoutButton(user::LoginStatus login) {
817  const base::string16 title = user::GetLocalizedSignOutStringForStatus(login,
818                                                                        true);
819  TrayPopupLabelButton* logout_button = new TrayPopupLabelButton(this, title);
820  logout_button->SetAccessibleName(title);
821  logout_button_ = logout_button;
822  // In public account mode, the logout button border has a custom color.
823  if (login == user::LOGGED_IN_PUBLIC) {
824    TrayPopupLabelButtonBorder* border =
825        static_cast<TrayPopupLabelButtonBorder*>(logout_button_->border());
826    border->SetPainter(false, views::Button::STATE_NORMAL,
827                       views::Painter::CreateImageGridPainter(
828                           kPublicAccountLogoutButtonBorderImagesNormal));
829    border->SetPainter(false, views::Button::STATE_HOVERED,
830                       views::Painter::CreateImageGridPainter(
831                           kPublicAccountLogoutButtonBorderImagesHovered));
832    border->SetPainter(false, views::Button::STATE_PRESSED,
833                       views::Painter::CreateImageGridPainter(
834                           kPublicAccountLogoutButtonBorderImagesHovered));
835  }
836  AddChildView(logout_button_);
837}
838
839void UserView::AddUserCard(SystemTrayItem* owner, user::LoginStatus login) {
840  // Add padding around the panel.
841  set_border(views::Border::CreateEmptyBorder(
842      kUserCardVerticalPadding, kTrayPopupPaddingHorizontal,
843      kUserCardVerticalPadding, kTrayPopupPaddingHorizontal));
844
845  if (SupportsMultiProfile() && login != user::LOGGED_IN_RETAIL_MODE) {
846    user_card_view_ = new UserCard(this, multiprofile_index_ == 0);
847    is_user_card_ = true;
848  } else {
849    user_card_view_ = new views::View();
850    is_user_card_ = false;
851  }
852
853  user_card_view_->SetLayoutManager(new views::BoxLayout(
854      views::BoxLayout::kHorizontal, 0, 0 , kTrayPopupPaddingBetweenItems));
855  AddChildViewAt(user_card_view_, 0);
856
857  if (login == user::LOGGED_IN_RETAIL_MODE) {
858    AddLoggedInRetailModeUserCardContent();
859    return;
860  }
861
862  // The entire user card should trigger hover (the inner items get disabled).
863  user_card_view_->SetEnabled(true);
864  user_card_view_->set_notify_enter_exit_on_child(true);
865
866  if (login == user::LOGGED_IN_PUBLIC) {
867    AddLoggedInPublicModeUserCardContent(owner);
868    return;
869  }
870
871  views::View* icon = CreateIconForUserCard(login);
872  user_card_view_->AddChildView(icon);
873
874  // To allow the border to start before the icon, reduce the size before and
875  // add an inset to the icon to get the spacing.
876  if (multiprofile_index_ == 0 && SupportsMultiProfile()) {
877    icon->set_border(views::Border::CreateEmptyBorder(
878        0, kTrayUserTileHoverBorderInset, 0, 0));
879    set_border(views::Border::CreateEmptyBorder(
880        kUserCardVerticalPadding,
881        kTrayPopupPaddingHorizontal - kTrayUserTileHoverBorderInset,
882        kUserCardVerticalPadding,
883        kTrayPopupPaddingHorizontal));
884  }
885  SessionStateDelegate* delegate =
886      Shell::GetInstance()->session_state_delegate();
887  views::Label* username = NULL;
888  ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
889  if (!multiprofile_index_) {
890    base::string16 user_name_string =
891        login == user::LOGGED_IN_GUEST ?
892            bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_GUEST_LABEL) :
893            delegate->GetUserDisplayName(multiprofile_index_);
894    if (!user_name_string.empty()) {
895      username = new views::Label(user_name_string);
896      username->SetHorizontalAlignment(gfx::ALIGN_LEFT);
897    }
898  }
899
900  views::Label* additional = NULL;
901  if (login != user::LOGGED_IN_GUEST) {
902    base::string16 user_email_string =
903        login == user::LOGGED_IN_LOCALLY_MANAGED ?
904            bundle.GetLocalizedString(
905                IDS_ASH_STATUS_TRAY_LOCALLY_MANAGED_LABEL) :
906            UTF8ToUTF16(delegate->GetUserEmail(multiprofile_index_));
907    if (!user_email_string.empty()) {
908      additional = new views::Label(user_email_string);
909      additional->SetFont(bundle.GetFont(ui::ResourceBundle::SmallFont));
910      additional->SetHorizontalAlignment(gfx::ALIGN_LEFT);
911    }
912  }
913
914  // Adjust text properties dependent on if it is an active or inactive user.
915  if (multiprofile_index_) {
916    // Fade the text of non active users to 50%.
917    SkColor text_color = additional->enabled_color();
918    text_color = SkColorSetA(text_color, SkColorGetA(text_color) / 2);
919    if (additional)
920      additional->SetDisabledColor(text_color);
921    if (username)
922      username->SetDisabledColor(text_color);
923  }
924
925  if (additional && username) {
926    views::View* details = new views::View;
927    details->SetLayoutManager(new views::BoxLayout(
928        views::BoxLayout::kVertical, 0, kUserDetailsVerticalPadding, 0));
929    details->AddChildView(username);
930    details->AddChildView(additional);
931    user_card_view_->AddChildView(details);
932  } else {
933    if (username)
934      user_card_view_->AddChildView(username);
935    if (additional)
936      user_card_view_->AddChildView(additional);
937  }
938}
939
940views::View* UserView::CreateIconForUserCard(user::LoginStatus login) {
941  RoundedImageView* icon = new RoundedImageView(kProfileRoundedCornerRadius,
942                                                multiprofile_index_ == 0);
943  icon->SetEnabled(false);
944  if (login == user::LOGGED_IN_GUEST) {
945    icon->SetImage(*ui::ResourceBundle::GetSharedInstance().
946        GetImageNamed(IDR_AURA_UBER_TRAY_GUEST_ICON).ToImageSkia(),
947        gfx::Size(kUserIconSize, kUserIconSize));
948  } else {
949    icon->SetImage(
950        Shell::GetInstance()->session_state_delegate()->
951            GetUserImage(multiprofile_index_),
952        gfx::Size(kUserIconSize, kUserIconSize));
953  }
954  return icon;
955}
956
957void UserView::AddLoggedInRetailModeUserCardContent() {
958  views::Label* details = new views::Label;
959  ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
960  details->SetText(
961      bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_KIOSK_LABEL));
962  details->set_border(views::Border::CreateEmptyBorder(0, 4, 0, 1));
963  details->SetHorizontalAlignment(gfx::ALIGN_LEFT);
964  user_card_view_->AddChildView(details);
965}
966
967void UserView::AddLoggedInPublicModeUserCardContent(SystemTrayItem* owner) {
968  user_card_view_->AddChildView(CreateIconForUserCard(user::LOGGED_IN_PUBLIC));
969  user_card_view_->AddChildView(new PublicAccountUserDetails(
970      owner, GetPreferredSize().width() + kTrayPopupPaddingBetweenItems));
971}
972
973void UserView::ToggleAddUserMenuOption() {
974  if (add_menu_option_.get()) {
975    popup_message_.reset();
976    mouse_watcher_.reset();
977    add_menu_option_.reset();
978    return;
979  }
980
981  // Note: We do not need to install a global event handler to delete this
982  // item since it will destroyed automatically before the menu / user menu item
983  // gets destroyed..
984  const SessionStateDelegate* session_state_delegate =
985      Shell::GetInstance()->session_state_delegate();
986  add_user_visible_but_disabled_ =
987      session_state_delegate->NumberOfLoggedInUsers() >=
988          session_state_delegate->GetMaximumNumberOfLoggedInUsers();
989  add_menu_option_.reset(new views::Widget);
990  views::Widget::InitParams params;
991  params.type = views::Widget::InitParams::TYPE_TOOLTIP;
992  params.keep_on_top = true;
993  params.context = this->GetWidget()->GetNativeWindow();
994  params.accept_events = true;
995  params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
996  params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
997  add_menu_option_->Init(params);
998  add_menu_option_->SetOpacity(0xFF);
999  add_menu_option_->GetNativeWindow()->set_owned_by_parent(false);
1000  SetShadowType(add_menu_option_->GetNativeView(),
1001                views::corewm::SHADOW_TYPE_NONE);
1002
1003  // Position it below our user card.
1004  gfx::Rect bounds = user_card_view_->GetBoundsInScreen();
1005  bounds.set_y(bounds.y() + bounds.height());
1006  add_menu_option_->SetBounds(bounds);
1007
1008  // Show the content.
1009  AddUserView* add_user_view = new AddUserView(
1010      static_cast<UserCard*>(user_card_view_), this);
1011  add_menu_option_->SetContentsView(add_user_view);
1012  add_menu_option_->SetAlwaysOnTop(true);
1013  add_menu_option_->Show();
1014  if (add_user_visible_but_disabled_) {
1015    ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
1016    popup_message_.reset(new PopupMessage(
1017        bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_CAPTION_CANNOT_ADD_USER),
1018        bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_MESSAGE_CANNOT_ADD_USER),
1019        PopupMessage::ICON_WARNING,
1020        add_user_view->anchor(),
1021        views::BubbleBorder::TOP_LEFT,
1022        gfx::Size(parent()->bounds().width() - kPopupMessageOffset, 0),
1023        2 * kPopupMessageOffset));
1024  }
1025  // Find the screen area which encloses both elements and sets then a mouse
1026  // watcher which will close the "menu".
1027  gfx::Rect area = user_card_view_->GetBoundsInScreen();
1028  area.set_height(2 * area.height());
1029  mouse_watcher_.reset(new views::MouseWatcher(
1030      new UserViewMouseWatcherHost(area),
1031      this));
1032  mouse_watcher_->Start();
1033}
1034
1035bool UserView::SupportsMultiProfile() {
1036  return Shell::GetInstance()->delegate()->IsMultiProfilesEnabled();
1037}
1038
1039AddUserView::AddUserView(UserCard* owner, views::ButtonListener* listener)
1040    : CustomButton(listener_),
1041      add_user_(NULL),
1042      listener_(listener),
1043      owner_(owner),
1044      anchor_(NULL) {
1045  AddContent();
1046  owner_->ForceBorderVisible(true);
1047}
1048
1049AddUserView::~AddUserView() {
1050  owner_->ForceBorderVisible(false);
1051}
1052
1053gfx::Size AddUserView::GetPreferredSize() {
1054  return owner_->bounds().size();
1055}
1056
1057int AddUserView::GetHeightForWidth(int width) {
1058  return owner_->bounds().size().height();
1059}
1060
1061void AddUserView::Layout() {
1062  gfx::Rect contents_area(GetContentsBounds());
1063  add_user_->SetBoundsRect(contents_area);
1064}
1065
1066void AddUserView::ButtonPressed(views::Button* sender, const ui::Event& event) {
1067  if (add_user_ == sender)
1068    listener_->ButtonPressed(this, event);
1069  else
1070    NOTREACHED();
1071}
1072
1073void AddUserView::AddContent() {
1074  set_notify_enter_exit_on_child(true);
1075
1076  const SessionStateDelegate* delegate =
1077      Shell::GetInstance()->session_state_delegate();
1078  bool enable = delegate->NumberOfLoggedInUsers() <
1079                    delegate->GetMaximumNumberOfLoggedInUsers();
1080
1081  SetLayoutManager(new views::FillLayout());
1082  set_background(views::Background::CreateSolidBackground(kBackgroundColor));
1083
1084  // Add padding around the panel.
1085  set_border(views::Border::CreateSolidBorder(1, kBorderColor));
1086
1087  add_user_ = new UserCard(this, enable);
1088  add_user_->set_border(views::Border::CreateEmptyBorder(
1089      kUserCardVerticalPadding,
1090      kTrayPopupPaddingHorizontal- kTrayUserTileHoverBorderInset,
1091      kUserCardVerticalPadding,
1092      kTrayPopupPaddingHorizontal- kTrayUserTileHoverBorderInset));
1093
1094  add_user_->SetLayoutManager(new views::BoxLayout(
1095      views::BoxLayout::kHorizontal, 0, 0 , kTrayPopupPaddingBetweenItems));
1096  AddChildViewAt(add_user_, 0);
1097
1098  // Add the [+] icon which is also the anchor for messages.
1099  ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
1100  RoundedImageView* icon = new RoundedImageView(kProfileRoundedCornerRadius,
1101                                                true);
1102  anchor_ = icon;
1103  icon->SetImage(*ui::ResourceBundle::GetSharedInstance().
1104      GetImageNamed(IDR_AURA_UBER_TRAY_ADD_MULTIPROFILE_USER).ToImageSkia(),
1105      gfx::Size(kUserIconSize, kUserIconSize));
1106  add_user_->AddChildView(icon);
1107
1108  // Add the command text.
1109  views::Label* command_label = new views::Label(
1110      bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_SIGN_IN_ANOTHER_ACCOUNT));
1111  command_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1112  add_user_->AddChildView(command_label);
1113}
1114
1115}  // namespace tray
1116
1117TrayUser::TrayUser(SystemTray* system_tray, MultiProfileIndex index)
1118    : SystemTrayItem(system_tray),
1119      multiprofile_index_(index),
1120      user_(NULL),
1121      layout_view_(NULL),
1122      avatar_(NULL),
1123      label_(NULL),
1124      separator_shown_(false) {
1125  Shell::GetInstance()->system_tray_notifier()->AddUserObserver(this);
1126}
1127
1128TrayUser::~TrayUser() {
1129  Shell::GetInstance()->system_tray_notifier()->RemoveUserObserver(this);
1130}
1131
1132TrayUser::TestState TrayUser::GetStateForTest() const {
1133  if (separator_shown_)
1134    return SEPARATOR;
1135  if (!user_)
1136    return HIDDEN;
1137  return user_->GetStateForTest();
1138}
1139
1140bool TrayUser::CanDropWindowHereToTransferToUser(
1141    const gfx::Point& point_in_screen) {
1142  // Check that this item is shown in the system tray (which means it must have
1143  // a view there) and that the user it represents is not the current user (in
1144  // which case |GetTrayIndex()| would return NULL).
1145  if (!layout_view_ || !GetTrayIndex())
1146    return false;
1147  return layout_view_->GetBoundsInScreen().Contains(point_in_screen);
1148}
1149
1150bool TrayUser::TransferWindowToUser(aura::Window* window) {
1151  const SessionStateDelegate* session_state_delegate =
1152      ash::Shell::GetInstance()->session_state_delegate();
1153  return session_state_delegate->TransferWindowToDesktopOfUser(window,
1154                                                               GetTrayIndex());
1155}
1156
1157gfx::Rect TrayUser::GetUserPanelBoundsInScreenForTest() const {
1158  DCHECK(user_);
1159  return user_->GetBoundsInScreenOfUserButtonForTest();
1160}
1161
1162views::View* TrayUser::CreateTrayView(user::LoginStatus status) {
1163  CHECK(layout_view_ == NULL);
1164  // When the full multi profile mode is used, only the active user will be
1165  // shown in the system tray, otherwise all users which are logged in.
1166  if (GetTrayIndex() && switches::UseFullMultiProfileMode())
1167    return NULL;
1168
1169  layout_view_ = new views::View();
1170  layout_view_->SetLayoutManager(
1171      new views::BoxLayout(views::BoxLayout::kHorizontal,
1172                           0, 0, kUserLabelToIconPadding));
1173  UpdateAfterLoginStatusChange(status);
1174  return layout_view_;
1175}
1176
1177views::View* TrayUser::CreateDefaultView(user::LoginStatus status) {
1178  if (status == user::LOGGED_IN_NONE)
1179    return NULL;
1180
1181  CHECK(user_ == NULL);
1182
1183  const SessionStateDelegate* session_state_delegate =
1184      Shell::GetInstance()->session_state_delegate();
1185  int logged_in_users = session_state_delegate->NumberOfLoggedInUsers();
1186
1187  // If there are multiple users logged in, the users will be separated from the
1188  // rest of the menu by a separator.
1189  if (multiprofile_index_ ==
1190          session_state_delegate->GetMaximumNumberOfLoggedInUsers() &&
1191      logged_in_users > 1) {
1192    separator_shown_ = true;
1193    return new views::View();
1194  }
1195
1196  // Do not show more UserView's then there are logged in users.
1197  if (multiprofile_index_ >= logged_in_users)
1198    return NULL;
1199
1200  user_ = new tray::UserView(this, status, multiprofile_index_);
1201  return user_;
1202}
1203
1204views::View* TrayUser::CreateDetailedView(user::LoginStatus status) {
1205  return NULL;
1206}
1207
1208void TrayUser::DestroyTrayView() {
1209  layout_view_ = NULL;
1210  avatar_ = NULL;
1211  label_ = NULL;
1212  separator_shown_ = false;
1213}
1214
1215void TrayUser::DestroyDefaultView() {
1216  user_ = NULL;
1217}
1218
1219void TrayUser::DestroyDetailedView() {
1220}
1221
1222void TrayUser::UpdateAfterLoginStatusChange(user::LoginStatus status) {
1223  // Only the active user is represented in the tray.
1224  if (!layout_view_)
1225    return;
1226  bool need_label = false;
1227  bool need_avatar = false;
1228  switch (status) {
1229    case user::LOGGED_IN_LOCKED:
1230    case user::LOGGED_IN_USER:
1231    case user::LOGGED_IN_OWNER:
1232    case user::LOGGED_IN_PUBLIC:
1233      need_avatar = true;
1234      break;
1235    case user::LOGGED_IN_LOCALLY_MANAGED:
1236      need_avatar = true;
1237      need_label = true;
1238      break;
1239    case user::LOGGED_IN_GUEST:
1240      need_label = true;
1241      break;
1242    case user::LOGGED_IN_RETAIL_MODE:
1243    case user::LOGGED_IN_KIOSK_APP:
1244    case user::LOGGED_IN_NONE:
1245      break;
1246  }
1247
1248  if ((need_avatar != (avatar_ != NULL)) ||
1249      (need_label != (label_ != NULL))) {
1250    layout_view_->RemoveAllChildViews(true);
1251    if (need_label) {
1252      label_ = new views::Label;
1253      SetupLabelForTray(label_);
1254      layout_view_->AddChildView(label_);
1255    } else {
1256      label_ = NULL;
1257    }
1258    if (need_avatar) {
1259      MultiProfileIndex tray_index = GetTrayIndex();
1260      if (!tray_index) {
1261        // The active user (index #0) will always be the first.
1262        avatar_ = new tray::RoundedImageView(kProfileRoundedCornerRadius, true);
1263      } else {
1264        // All other users will be inactive users.
1265        avatar_ = new tray::UserSwitcherView(kProfileRoundedCornerRadius,
1266                                             tray_index);
1267      }
1268      layout_view_->AddChildView(avatar_);
1269    } else {
1270      avatar_ = NULL;
1271    }
1272  }
1273
1274  ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
1275  if (status == user::LOGGED_IN_LOCALLY_MANAGED) {
1276    label_->SetText(
1277        bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_LOCALLY_MANAGED_LABEL));
1278  } else if (status == user::LOGGED_IN_GUEST) {
1279    label_->SetText(bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_GUEST_LABEL));
1280  }
1281
1282  if (avatar_ && switches::UseAlternateShelfLayout()) {
1283    int corner_radius = GetTrayItemRadius();
1284    avatar_->SetCornerRadii(0, corner_radius, corner_radius, 0);
1285    avatar_->set_border(NULL);
1286  }
1287  UpdateAvatarImage(status);
1288
1289  // Update layout after setting label_ and avatar_ with new login status.
1290  UpdateLayoutOfItem();
1291}
1292
1293void TrayUser::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) {
1294  // Inactive users won't have a layout.
1295  if (!layout_view_)
1296    return;
1297  int corner_radius = GetTrayItemRadius();
1298  if (alignment == SHELF_ALIGNMENT_BOTTOM ||
1299      alignment == SHELF_ALIGNMENT_TOP) {
1300    if (avatar_) {
1301      if (switches::UseAlternateShelfLayout()) {
1302        if (multiprofile_index_) {
1303          avatar_->set_border(
1304              views::Border::CreateEmptyBorder(0, kTrayLabelSpacing, 0, 0));
1305        } else {
1306          avatar_->set_border(NULL);
1307        }
1308        avatar_->SetCornerRadii(0, corner_radius, corner_radius, 0);
1309      } else {
1310        avatar_->set_border(views::Border::CreateEmptyBorder(
1311            0, kTrayImageItemHorizontalPaddingBottomAlignment + 2,
1312            0, kTrayImageItemHorizontalPaddingBottomAlignment));
1313      }
1314    }
1315    if (label_) {
1316      label_->set_border(views::Border::CreateEmptyBorder(
1317          0, kTrayLabelItemHorizontalPaddingBottomAlignment,
1318          0, kTrayLabelItemHorizontalPaddingBottomAlignment));
1319    }
1320    layout_view_->SetLayoutManager(
1321        new views::BoxLayout(views::BoxLayout::kHorizontal,
1322                             0, 0, kUserLabelToIconPadding));
1323  } else {
1324    if (avatar_) {
1325      if (switches::UseAlternateShelfLayout()) {
1326        if (multiprofile_index_) {
1327          avatar_->set_border(
1328              views::Border::CreateEmptyBorder(kTrayLabelSpacing, 0, 0, 0));
1329        } else {
1330          avatar_->set_border(NULL);
1331        }
1332        avatar_->SetCornerRadii(0, 0, corner_radius, corner_radius);
1333      } else {
1334        SetTrayImageItemBorder(avatar_, alignment);
1335      }
1336    }
1337    if (label_) {
1338      label_->set_border(views::Border::CreateEmptyBorder(
1339          kTrayLabelItemVerticalPaddingVerticalAlignment,
1340          kTrayLabelItemHorizontalPaddingBottomAlignment,
1341          kTrayLabelItemVerticalPaddingVerticalAlignment,
1342          kTrayLabelItemHorizontalPaddingBottomAlignment));
1343    }
1344    layout_view_->SetLayoutManager(
1345        new views::BoxLayout(views::BoxLayout::kVertical,
1346                             0, 0, kUserLabelToIconPadding));
1347  }
1348}
1349
1350void TrayUser::OnUserUpdate() {
1351  UpdateAvatarImage(Shell::GetInstance()->system_tray_delegate()->
1352      GetUserLoginStatus());
1353}
1354
1355void TrayUser::OnUserAddedToSession() {
1356  SessionStateDelegate* session_state_delegate =
1357      Shell::GetInstance()->session_state_delegate();
1358  // Only create views for user items which are logged in.
1359  if (GetTrayIndex() >= session_state_delegate->NumberOfLoggedInUsers())
1360    return;
1361
1362  // Enforce a layout change that newly added items become visible.
1363  UpdateLayoutOfItem();
1364
1365  // Update the user item.
1366  UpdateAvatarImage(
1367      Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus());
1368}
1369
1370void TrayUser::UpdateAvatarImage(user::LoginStatus status) {
1371  SessionStateDelegate* session_state_delegate =
1372      Shell::GetInstance()->session_state_delegate();
1373  if (!avatar_ ||
1374      GetTrayIndex() >= session_state_delegate->NumberOfLoggedInUsers())
1375    return;
1376
1377  int icon_size = switches::UseAlternateShelfLayout() ?
1378      kUserIconLargeSize : kUserIconSize;
1379
1380  avatar_->SetImage(
1381      Shell::GetInstance()->session_state_delegate()->GetUserImage(
1382          GetTrayIndex()),
1383      gfx::Size(icon_size, icon_size));
1384
1385  // Unit tests might come here with no images for some users.
1386  if (avatar_->size().IsEmpty())
1387    avatar_->SetSize(gfx::Size(icon_size, icon_size));
1388}
1389
1390MultiProfileIndex TrayUser::GetTrayIndex() {
1391  Shell* shell = Shell::GetInstance();
1392  // If multi profile is not enabled we can use the normal index.
1393  if (!shell->delegate()->IsMultiProfilesEnabled())
1394    return multiprofile_index_;
1395  // In case of multi profile we need to mirror the indices since the system
1396  // tray items are in the reverse order then the menu items.
1397  return shell->session_state_delegate()->GetMaximumNumberOfLoggedInUsers() -
1398             multiprofile_index_;
1399}
1400
1401int TrayUser::GetTrayItemRadius() {
1402  SessionStateDelegate* delegate =
1403      Shell::GetInstance()->session_state_delegate();
1404  bool is_last_item = GetTrayIndex() == (delegate->NumberOfLoggedInUsers() - 1);
1405  return is_last_item ? kUserIconLargeCornerRadius : 0;
1406}
1407
1408void TrayUser::UpdateLayoutOfItem() {
1409  internal::RootWindowController* controller =
1410      internal::GetRootWindowController(
1411          system_tray()->GetWidget()->GetNativeWindow()->GetRootWindow());
1412  if (controller && controller->shelf()) {
1413    UpdateAfterShelfAlignmentChange(
1414        controller->GetShelfLayoutManager()->GetAlignment());
1415  }
1416}
1417
1418}  // namespace internal
1419}  // namespace ash
1420