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