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