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