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