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