tray_user.cc revision b2df76ea8fec9e32f6f3718986dba0d95315b29c
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/shell.h"
12#include "ash/shell_delegate.h"
13#include "ash/system/tray/system_tray.h"
14#include "ash/system/tray/system_tray_delegate.h"
15#include "ash/system/tray/system_tray_notifier.h"
16#include "ash/system/tray/tray_constants.h"
17#include "ash/system/tray/tray_item_view.h"
18#include "ash/system/tray/tray_popup_label_button.h"
19#include "ash/system/tray/tray_popup_label_button_border.h"
20#include "ash/system/tray/tray_utils.h"
21#include "base/i18n/rtl.h"
22#include "base/logging.h"
23#include "base/memory/scoped_vector.h"
24#include "base/string16.h"
25#include "base/string_util.h"
26#include "base/utf_string_conversions.h"
27#include "grit/ash_resources.h"
28#include "grit/ash_strings.h"
29#include "skia/ext/image_operations.h"
30#include "third_party/skia/include/core/SkCanvas.h"
31#include "third_party/skia/include/core/SkPaint.h"
32#include "third_party/skia/include/core/SkPath.h"
33#include "ui/base/l10n/l10n_util.h"
34#include "ui/base/range/range.h"
35#include "ui/base/resource/resource_bundle.h"
36#include "ui/base/text/text_elider.h"
37#include "ui/gfx/canvas.h"
38#include "ui/gfx/font.h"
39#include "ui/gfx/image/image.h"
40#include "ui/gfx/image/image_skia_operations.h"
41#include "ui/gfx/insets.h"
42#include "ui/gfx/rect.h"
43#include "ui/gfx/render_text.h"
44#include "ui/gfx/size.h"
45#include "ui/gfx/skia_util.h"
46#include "ui/views/border.h"
47#include "ui/views/bubble/tray_bubble_view.h"
48#include "ui/views/controls/button/button.h"
49#include "ui/views/controls/button/custom_button.h"
50#include "ui/views/controls/image_view.h"
51#include "ui/views/controls/label.h"
52#include "ui/views/controls/link.h"
53#include "ui/views/controls/link_listener.h"
54#include "ui/views/layout/box_layout.h"
55#include "ui/views/layout/fill_layout.h"
56#include "ui/views/painter.h"
57#include "ui/views/view.h"
58#include "ui/views/widget/widget.h"
59
60namespace {
61
62const int kUserDetailsVerticalPadding = 5;
63const int kUserCardVerticalPadding = 10;
64const int kProfileRoundedCornerRadius = 2;
65const int kUserIconSize = 27;
66const int kUserLabelToIconPadding = 5;
67
68// The invisible word joiner character, used as a marker to indicate the start
69// and end of the user's display name in the public account user card's text.
70const char16 kDisplayNameMark[] = { 0x2060, 0 };
71
72const int kPublicAccountLogoutButtonBorderImagesNormal[] = {
73    IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
74    IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
75    IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
76    IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
77    IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
78    IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
79    IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
80    IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
81    IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
82};
83
84const int kPublicAccountLogoutButtonBorderImagesHovered[] = {
85    IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
86    IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
87    IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
88    IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
89    IDR_AURA_TRAY_POPUP_LABEL_BUTTON_HOVER_BACKGROUND,
90    IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
91    IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
92    IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
93    IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
94};
95
96}  // namespace
97
98namespace ash {
99namespace internal {
100
101namespace tray {
102
103// A custom image view with rounded edges.
104class RoundedImageView : public views::View {
105 public:
106  // Constructs a new rounded image view with rounded corners of radius
107  // |corner_radius|.
108  explicit RoundedImageView(int corner_radius);
109  virtual ~RoundedImageView();
110
111  // Set the image that should be displayed. The image contents is copied to the
112  // receiver's image.
113  void SetImage(const gfx::ImageSkia& img, const gfx::Size& size);
114
115 private:
116  // Overridden from views::View.
117  virtual gfx::Size GetPreferredSize() OVERRIDE;
118  virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
119
120  gfx::ImageSkia image_;
121  gfx::ImageSkia resized_;
122  gfx::Size image_size_;
123  int corner_radius_;
124
125  DISALLOW_COPY_AND_ASSIGN(RoundedImageView);
126};
127
128class ClickableAvatar : public views::CustomButton {
129 public:
130  explicit ClickableAvatar(views::ButtonListener* listener);
131  virtual ~ClickableAvatar();
132
133 private:
134  DISALLOW_COPY_AND_ASSIGN(ClickableAvatar);
135};
136
137// The user details shown in public account mode. This is essentially a label
138// but with custom painting code as the text is styled with multiple colors and
139// contains a link.
140class PublicAccountUserDetails : public views::View,
141                                 public views::LinkListener {
142 public:
143  PublicAccountUserDetails(SystemTrayItem* owner, int used_width);
144  virtual ~PublicAccountUserDetails();
145
146 private:
147  // Overridden from views::View.
148  virtual void Layout() OVERRIDE;
149  virtual gfx::Size GetPreferredSize() OVERRIDE;
150  virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
151
152  // Overridden from views::LinkListener.
153  virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE;
154
155  // Calculate a preferred size that ensures the label text and the following
156  // link do not wrap over more than three lines in total for aesthetic reasons
157  // if possible.
158  void CalculatePreferredSize(SystemTrayItem* owner, int used_width);
159
160  base::string16 text_;
161  views::Link* learn_more_;
162  gfx::Size preferred_size_;
163  ScopedVector<gfx::RenderText> lines_;
164
165  DISALLOW_COPY_AND_ASSIGN(PublicAccountUserDetails);
166};
167
168class UserView : public views::View,
169                 public views::ButtonListener {
170 public:
171  explicit UserView(SystemTrayItem* owner, ash::user::LoginStatus login);
172  virtual ~UserView();
173
174 private:
175  // Overridden from views::View.
176  virtual gfx::Size GetPreferredSize() OVERRIDE;
177  virtual int GetHeightForWidth(int width) OVERRIDE;
178  virtual void Layout() OVERRIDE;
179
180  // Overridden from views::ButtonListener.
181  virtual void ButtonPressed(views::Button* sender,
182                             const ui::Event& event) OVERRIDE;
183
184  void AddLogoutButton(ash::user::LoginStatus login);
185  void AddUserCard(SystemTrayItem* owner, ash::user::LoginStatus login);
186
187  views::View* user_card_;
188  views::View* logout_button_;
189  ClickableAvatar* profile_picture_;
190
191  DISALLOW_COPY_AND_ASSIGN(UserView);
192};
193
194RoundedImageView::RoundedImageView(int corner_radius)
195    : corner_radius_(corner_radius) {}
196
197RoundedImageView::~RoundedImageView() {}
198
199void RoundedImageView::SetImage(const gfx::ImageSkia& img,
200                                const gfx::Size& size) {
201  image_ = img;
202  image_size_ = size;
203
204  // Try to get the best image quality for the avatar.
205  resized_ = gfx::ImageSkiaOperations::CreateResizedImage(image_,
206      skia::ImageOperations::RESIZE_BEST, size);
207  if (GetWidget() && visible()) {
208    PreferredSizeChanged();
209    SchedulePaint();
210  }
211}
212
213gfx::Size RoundedImageView::GetPreferredSize() {
214  return gfx::Size(image_size_.width() + GetInsets().width(),
215                   image_size_.height() + GetInsets().height());
216}
217
218void RoundedImageView::OnPaint(gfx::Canvas* canvas) {
219  View::OnPaint(canvas);
220  gfx::Rect image_bounds(size());
221  image_bounds.ClampToCenteredSize(GetPreferredSize());
222  image_bounds.Inset(GetInsets());
223  const SkScalar kRadius = SkIntToScalar(corner_radius_);
224  SkPath path;
225  path.addRoundRect(gfx::RectToSkRect(image_bounds), kRadius, kRadius);
226  SkPaint paint;
227  paint.setAntiAlias(true);
228  paint.setXfermodeMode(SkXfermode::kSrcOver_Mode);
229  canvas->DrawImageInPath(resized_, image_bounds.x(), image_bounds.y(),
230                          path, paint);
231}
232
233ClickableAvatar::ClickableAvatar(views::ButtonListener* listener)
234    : views::CustomButton(listener) {
235  SetLayoutManager(new views::FillLayout());
236  RoundedImageView* user_picture =
237      new RoundedImageView(kProfileRoundedCornerRadius);
238  user_picture->SetImage(
239      ash::Shell::GetInstance()->system_tray_delegate()->GetUserImage(),
240      gfx::Size(kUserIconSize, kUserIconSize));
241  AddChildView(user_picture);
242}
243
244ClickableAvatar::~ClickableAvatar() {}
245
246PublicAccountUserDetails::PublicAccountUserDetails(SystemTrayItem* owner,
247                                                   int used_width)
248    : learn_more_(NULL) {
249  const int inner_padding =
250      kTrayPopupPaddingHorizontal - kTrayPopupPaddingBetweenItems;
251  const bool rtl = base::i18n::IsRTL();
252  set_border(views::Border::CreateEmptyBorder(
253      kUserDetailsVerticalPadding, rtl ? 0 : inner_padding,
254      kUserDetailsVerticalPadding, rtl ? inner_padding : 0));
255
256  ash::SystemTrayDelegate* delegate =
257      ash::Shell::GetInstance()->system_tray_delegate();
258  // Retrieve the user's display name and wrap it with markers.
259  base::string16 display_name = delegate->GetUserDisplayName();
260  RemoveChars(display_name, kDisplayNameMark, &display_name);
261  display_name = kDisplayNameMark[0] + display_name + kDisplayNameMark[0];
262  // Retrieve the domain managing the device and wrap it with markers.
263  base::string16 domain = UTF8ToUTF16(delegate->GetEnterpriseDomain());
264  RemoveChars(domain, kDisplayNameMark, &domain);
265  base::i18n::WrapStringWithLTRFormatting(&domain);
266  // Retrieve the label text, inserting the display name and domain.
267  text_ = l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_PUBLIC_LABEL,
268                                     display_name, domain);
269
270  learn_more_ = new views::Link(l10n_util::GetStringUTF16(IDS_ASH_LEARN_MORE));
271  learn_more_->SetUnderline(false);
272  learn_more_->set_listener(this);
273  AddChildView(learn_more_);
274
275  CalculatePreferredSize(owner, used_width);
276}
277
278PublicAccountUserDetails::~PublicAccountUserDetails() {}
279
280void PublicAccountUserDetails::Layout() {
281  lines_.clear();
282  const gfx::Rect contents_area = GetContentsBounds();
283  if (contents_area.IsEmpty())
284     return;
285
286  // Word-wrap the label text.
287  const gfx::Font font;
288  std::vector<base::string16> lines;
289  ui::ElideRectangleText(text_, font, contents_area.width(),
290                         contents_area.height(), ui::ELIDE_LONG_WORDS, &lines);
291  // Loop through the lines, creating a renderer for each.
292  gfx::Point position = contents_area.origin();
293  ui::Range display_name(ui::Range::InvalidRange());
294  for (std::vector<base::string16>::const_iterator it = lines.begin();
295       it != lines.end(); ++it) {
296    gfx::RenderText* line = gfx::RenderText::CreateInstance();
297    line->SetDirectionalityMode(gfx::DIRECTIONALITY_FROM_UI);
298    line->SetText(*it);
299    const gfx::Size size(contents_area.width(), line->GetStringSize().height());
300    line->SetDisplayRect(gfx::Rect(position, size));
301    position.set_y(position.y() + size.height());
302
303    // Set the default text color for the line.
304    line->SetColor(kPublicAccountUserCardTextColor);
305
306    // If a range of the line contains the user's display name, apply a custom
307    // text color to it.
308    if (display_name.is_empty())
309      display_name.set_start(it->find(kDisplayNameMark));
310    if (!display_name.is_empty()) {
311      display_name.set_end(
312          it->find(kDisplayNameMark, display_name.start() + 1));
313      ui::Range line_range(0, it->size());
314      line->ApplyColor(kPublicAccountUserCardNameColor,
315                       display_name.Intersect(line_range));
316      // Update the range for the next line.
317      if (display_name.end() >= line_range.end())
318        display_name.set_start(0);
319      else
320        display_name = ui::Range::InvalidRange();
321    }
322
323    lines_.push_back(line);
324  }
325
326  // Position the link after the label text, separated by a space. If it does
327  // not fit onto the last line of the text, wrap the link onto its own line.
328  const gfx::Size last_line_size = lines_.back()->GetStringSize();
329  const int space_width = font.GetStringWidth(ASCIIToUTF16(" "));
330  const gfx::Size link_size = learn_more_->GetPreferredSize();
331  if (contents_area.width() - last_line_size.width() >=
332      space_width + link_size.width()) {
333    position.set_x(position.x() + last_line_size.width() + space_width);
334    position.set_y(position.y() - last_line_size.height());
335  }
336  position.set_y(position.y() - learn_more_->GetInsets().top());
337  gfx::Rect learn_more_bounds(position, link_size);
338  learn_more_bounds.Intersect(contents_area);
339  if (base::i18n::IsRTL()) {
340    const gfx::Insets insets = GetInsets();
341    learn_more_bounds.Offset(insets.right() - insets.left(), 0);
342  }
343  learn_more_->SetBoundsRect(learn_more_bounds);
344}
345
346gfx::Size PublicAccountUserDetails::GetPreferredSize() {
347  return preferred_size_;
348}
349
350void PublicAccountUserDetails::OnPaint(gfx::Canvas* canvas) {
351  for (ScopedVector<gfx::RenderText>::const_iterator it = lines_.begin();
352       it != lines_.end(); ++it) {
353    (*it)->Draw(canvas);
354  }
355  views::View::OnPaint(canvas);
356}
357
358void PublicAccountUserDetails::LinkClicked(views::Link* source,
359                                           int event_flags) {
360  DCHECK_EQ(source, learn_more_);
361  ash::Shell::GetInstance()->system_tray_delegate()->ShowPublicAccountInfo();
362}
363
364void PublicAccountUserDetails::CalculatePreferredSize(SystemTrayItem* owner,
365                                                      int used_width) {
366  const gfx::Font font;
367  const gfx::Size link_size = learn_more_->GetPreferredSize();
368  const int space_width = font.GetStringWidth(ASCIIToUTF16(" "));
369  const gfx::Insets insets = GetInsets();
370  views::TrayBubbleView* bubble_view =
371      owner->system_tray()->GetSystemBubble()->bubble_view();
372  int min_width = std::max(
373      link_size.width(),
374      bubble_view->GetPreferredSize().width() - (used_width + insets.width()));
375  int max_width = std::min(
376      font.GetStringWidth(text_) + space_width + link_size.width(),
377      bubble_view->GetMaximumSize().width() - (used_width + insets.width()));
378  // Do a binary search for the minimum width that ensures no more than three
379  // lines are needed. The lower bound is the minimum of the current bubble
380  // width and the width of the link (as no wrapping is permitted inside the
381  // link). The upper bound is the maximum of the largest allowed bubble width
382  // and the sum of the label text and link widths when put on a single line.
383  std::vector<base::string16> lines;
384  while (min_width < max_width) {
385    lines.clear();
386    const int width = (min_width + max_width) / 2;
387    const bool too_narrow = ui::ElideRectangleText(
388        text_, font, width, INT_MAX, ui::TRUNCATE_LONG_WORDS, &lines) != 0;
389    int line_count = lines.size();
390    if (!too_narrow && line_count == 3 &&
391            width - font.GetStringWidth(lines.back()) <=
392            space_width + link_size.width()) {
393      ++line_count;
394    }
395    if (too_narrow || line_count > 3)
396      min_width = width + 1;
397    else
398      max_width = width;
399  }
400
401  // Calculate the corresponding height and set the preferred size.
402  lines.clear();
403  ui::ElideRectangleText(
404      text_, font, min_width, INT_MAX, ui::TRUNCATE_LONG_WORDS, &lines);
405  int line_count = lines.size();
406  if (min_width - font.GetStringWidth(lines.back()) <=
407      space_width + link_size.width()) {
408    ++line_count;
409  }
410  const int line_height = font.GetHeight();
411  const int link_extra_height = std::max(
412      link_size.height() - learn_more_->GetInsets().top() - line_height, 0);
413  preferred_size_ = gfx::Size(
414      min_width + insets.width(),
415      line_count * line_height + link_extra_height + insets.height());
416
417  bubble_view->SetWidth(preferred_size_.width() + used_width);
418}
419
420UserView::UserView(SystemTrayItem* owner, ash::user::LoginStatus login)
421    : user_card_(NULL),
422      logout_button_(NULL),
423      profile_picture_(NULL) {
424  CHECK_NE(ash::user::LOGGED_IN_NONE, login);
425  set_background(views::Background::CreateSolidBackground(
426      login == ash::user::LOGGED_IN_PUBLIC ? kPublicAccountBackgroundColor :
427                                             kBackgroundColor));
428  SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0,
429                                        kTrayPopupPaddingBetweenItems));
430  // The logout button must be added before the user card so that the user card
431  // can correctly calculate the remaining available width.
432  AddLogoutButton(login);
433  AddUserCard(owner, login);
434}
435
436UserView::~UserView() {}
437
438gfx::Size UserView::GetPreferredSize() {
439  gfx::Size size = views::View::GetPreferredSize();
440  if (!user_card_) {
441    // Make sure the default user view item is at least as tall as the other
442    // items.
443    size.set_height(std::max(size.height(),
444                             kTrayPopupItemHeight + GetInsets().height()));
445  }
446  return size;
447}
448
449int UserView::GetHeightForWidth(int width) {
450  return GetPreferredSize().height();
451}
452
453void UserView::Layout() {
454  gfx::Rect contents_area(GetContentsBounds());
455  if (user_card_ && logout_button_) {
456    // Give the logout button the space it requests.
457    gfx::Rect logout_area = contents_area;
458    logout_area.ClampToCenteredSize(logout_button_->GetPreferredSize());
459    logout_area.set_x(contents_area.right() - logout_area.width());
460    logout_button_->SetBoundsRect(logout_area);
461
462    // Give the remaining space to the user card.
463    gfx::Rect user_card_area = contents_area;
464    int remaining_width = contents_area.width() -
465        (logout_area.width() + kTrayPopupPaddingBetweenItems);
466    user_card_area.set_width(std::max(0, remaining_width));
467    user_card_->SetBoundsRect(user_card_area);
468  } else if (user_card_) {
469    user_card_->SetBoundsRect(contents_area);
470  } else if (logout_button_) {
471    logout_button_->SetBoundsRect(contents_area);
472  }
473}
474
475void UserView::ButtonPressed(views::Button* sender, const ui::Event& event) {
476  if (sender == logout_button_) {
477    ash::Shell::GetInstance()->system_tray_delegate()->SignOut();
478  } else if (sender == profile_picture_) {
479    if (ash::Shell::GetInstance()->delegate()->IsMultiProfilesEnabled())
480      ash::Shell::GetInstance()->system_tray_delegate()->ShowUserLogin();
481    else
482      ash::Shell::GetInstance()->system_tray_delegate()->ChangeProfilePicture();
483  } else {
484    NOTREACHED();
485  }
486}
487
488void UserView::AddLogoutButton(ash::user::LoginStatus login) {
489  // A user should not be able to modify logged-in state when screen is
490  // locked.
491  if (login == ash::user::LOGGED_IN_LOCKED)
492    return;
493
494  const base::string16 title = ash::user::GetLocalizedSignOutStringForStatus(
495      login, true);
496  TrayPopupLabelButton* logout_button = new TrayPopupLabelButton(this, title);
497  logout_button->SetAccessibleName(title);
498  logout_button_ = logout_button;
499  // In public account mode, the logout button border has a custom color.
500  if (login == ash::user::LOGGED_IN_PUBLIC) {
501    TrayPopupLabelButtonBorder* border =
502        static_cast<TrayPopupLabelButtonBorder*>(logout_button_->border());
503    border->SetPainter(false, views::Button::STATE_NORMAL,
504                       views::Painter::CreateImageGridPainter(
505                           kPublicAccountLogoutButtonBorderImagesNormal));
506    border->SetPainter(false, views::Button::STATE_HOVERED,
507                       views::Painter::CreateImageGridPainter(
508                           kPublicAccountLogoutButtonBorderImagesHovered));
509    border->SetPainter(false, views::Button::STATE_PRESSED,
510                       views::Painter::CreateImageGridPainter(
511                           kPublicAccountLogoutButtonBorderImagesHovered));
512  }
513  AddChildView(logout_button_);
514}
515
516void UserView::AddUserCard(SystemTrayItem* owner,
517                           ash::user::LoginStatus login) {
518  if (login == ash::user::LOGGED_IN_GUEST)
519    return;
520
521  set_border(views::Border::CreateEmptyBorder(0, kTrayPopupPaddingHorizontal,
522                                              0, kTrayPopupPaddingHorizontal));
523
524  user_card_ = new views::View();
525  user_card_->SetLayoutManager(new views::BoxLayout(
526      views::BoxLayout::kHorizontal, 0, kUserCardVerticalPadding,
527      kTrayPopupPaddingBetweenItems));
528  AddChildViewAt(user_card_, 0);
529
530  if (login == ash::user::LOGGED_IN_RETAIL_MODE) {
531    views::Label* details = new views::Label;
532    ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
533    details->SetText(
534        bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_KIOSK_LABEL));
535    details->set_border(views::Border::CreateEmptyBorder(0, 4, 0, 1));
536    details->SetHorizontalAlignment(gfx::ALIGN_LEFT);
537    user_card_->AddChildView(details);
538    return;
539  }
540  profile_picture_ = new ClickableAvatar(this);
541  user_card_->AddChildView(profile_picture_);
542
543  if (login == ash::user::LOGGED_IN_PUBLIC) {
544    user_card_->AddChildView(new PublicAccountUserDetails(
545        owner, GetPreferredSize().width() + kTrayPopupPaddingBetweenItems));
546    return;
547  }
548
549  ash::SystemTrayDelegate* delegate =
550      ash::Shell::GetInstance()->system_tray_delegate();
551  views::View* details = new views::View;
552  details->SetLayoutManager(new views::BoxLayout(
553      views::BoxLayout::kVertical, 0, kUserDetailsVerticalPadding, 0));
554  views::Label* username = new views::Label(delegate->GetUserDisplayName());
555  username->SetHorizontalAlignment(gfx::ALIGN_LEFT);
556  details->AddChildView(username);
557
558  ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
559
560  views::Label* additional = new views::Label();
561
562  additional->SetText(login == ash::user::LOGGED_IN_LOCALLY_MANAGED ?
563      bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_LOCALLY_MANAGED_LABEL) :
564      UTF8ToUTF16(delegate->GetUserEmail()));
565
566  additional->SetFont(bundle.GetFont(ui::ResourceBundle::SmallFont));
567  additional->SetHorizontalAlignment(gfx::ALIGN_LEFT);
568  additional->SetEnabled(false);
569  details->AddChildView(additional);
570  user_card_->AddChildView(details);
571}
572
573}  // namespace tray
574
575TrayUser::TrayUser(SystemTray* system_tray)
576    : SystemTrayItem(system_tray),
577      user_(NULL),
578      layout_view_(NULL),
579      avatar_(NULL),
580      label_(NULL) {
581  Shell::GetInstance()->system_tray_notifier()->AddUserObserver(this);
582}
583
584TrayUser::~TrayUser() {
585  Shell::GetInstance()->system_tray_notifier()->RemoveUserObserver(this);
586}
587
588views::View* TrayUser::CreateTrayView(user::LoginStatus status) {
589  CHECK(layout_view_ == NULL);
590  layout_view_ = new views::View();
591  layout_view_->SetLayoutManager(
592      new views::BoxLayout(views::BoxLayout::kHorizontal,
593                           0, 0, kUserLabelToIconPadding));
594  UpdateAfterLoginStatusChange(status);
595  return layout_view_;
596}
597
598views::View* TrayUser::CreateDefaultView(user::LoginStatus status) {
599  if (status == user::LOGGED_IN_NONE)
600    return NULL;
601
602  CHECK(user_ == NULL);
603  user_ = new tray::UserView(this, status);
604  return user_;
605}
606
607views::View* TrayUser::CreateDetailedView(user::LoginStatus status) {
608  return NULL;
609}
610
611void TrayUser::DestroyTrayView() {
612  layout_view_ = NULL;
613  avatar_ = NULL;
614  label_ = NULL;
615}
616
617void TrayUser::DestroyDefaultView() {
618  user_ = NULL;
619}
620
621void TrayUser::DestroyDetailedView() {
622}
623
624void TrayUser::UpdateAfterLoginStatusChange(user::LoginStatus status) {
625  CHECK(layout_view_);
626  bool need_label = false;
627  bool need_avatar = false;
628  switch (status) {
629    case user::LOGGED_IN_LOCKED:
630    case user::LOGGED_IN_USER:
631    case user::LOGGED_IN_OWNER:
632    case user::LOGGED_IN_PUBLIC:
633      need_avatar = true;
634      break;
635    case user::LOGGED_IN_LOCALLY_MANAGED:
636      need_avatar = true;
637      need_label = true;
638      break;
639    case user::LOGGED_IN_GUEST:
640      need_label = true;
641      break;
642    case user::LOGGED_IN_RETAIL_MODE:
643    case user::LOGGED_IN_KIOSK_APP:
644    case user::LOGGED_IN_NONE:
645      break;
646  }
647
648  if ((need_avatar != (avatar_ != NULL)) ||
649      (need_label != (label_ != NULL))) {
650    layout_view_->RemoveAllChildViews(true);
651    if (need_label) {
652      label_ = new views::Label;
653      SetupLabelForTray(label_);
654      layout_view_->AddChildView(label_);
655    } else {
656      label_ = NULL;
657    }
658    if (need_avatar) {
659      avatar_ = new tray::RoundedImageView(kProfileRoundedCornerRadius);
660      layout_view_->AddChildView(avatar_);
661    } else {
662      avatar_ = NULL;
663    }
664  }
665
666  ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
667  if (status == user::LOGGED_IN_LOCALLY_MANAGED) {
668    label_->SetText(
669        bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_LOCALLY_MANAGED_LABEL));
670  } else if (status == user::LOGGED_IN_GUEST) {
671    label_->SetText(bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_GUEST_LABEL));
672  }
673
674  if (avatar_) {
675    avatar_->SetImage(
676        ash::Shell::GetInstance()->system_tray_delegate()->GetUserImage(),
677        gfx::Size(kUserIconSize, kUserIconSize));
678  }
679}
680
681void TrayUser::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) {
682  CHECK(layout_view_);
683  if (alignment == SHELF_ALIGNMENT_BOTTOM ||
684      alignment == SHELF_ALIGNMENT_TOP) {
685    if (avatar_) {
686      avatar_->set_border(views::Border::CreateEmptyBorder(
687          0, kTrayImageItemHorizontalPaddingBottomAlignment + 2,
688          0, kTrayImageItemHorizontalPaddingBottomAlignment));
689
690    }
691    if (label_) {
692      label_->set_border(views::Border::CreateEmptyBorder(
693          0, kTrayLabelItemHorizontalPaddingBottomAlignment,
694          0, kTrayLabelItemHorizontalPaddingBottomAlignment));
695    }
696    layout_view_->SetLayoutManager(
697        new views::BoxLayout(views::BoxLayout::kHorizontal,
698                             0, 0, kUserLabelToIconPadding));
699  } else {
700    if (avatar_)
701      SetTrayImageItemBorder(avatar_, alignment);
702    if (label_) {
703      label_->set_border(views::Border::CreateEmptyBorder(
704          kTrayLabelItemVerticalPaddingVeriticalAlignment,
705          kTrayLabelItemHorizontalPaddingBottomAlignment,
706          kTrayLabelItemVerticalPaddingVeriticalAlignment,
707          kTrayLabelItemHorizontalPaddingBottomAlignment));
708    }
709    layout_view_->SetLayoutManager(
710        new views::BoxLayout(views::BoxLayout::kVertical,
711                             0, 0, kUserLabelToIconPadding));
712  }
713}
714
715void TrayUser::OnUserUpdate() {
716  // Check for null to avoid crbug.com/150944.
717  if (avatar_) {
718    avatar_->SetImage(
719        ash::Shell::GetInstance()->system_tray_delegate()->GetUserImage(),
720        gfx::Size(kUserIconSize, kUserIconSize));
721  }
722}
723
724}  // namespace internal
725}  // namespace ash
726