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