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