diff options
Diffstat (limited to 'chromium/ash/system/user/tray_user.cc')
-rw-r--r-- | chromium/ash/system/user/tray_user.cc | 1425 |
1 files changed, 1425 insertions, 0 deletions
diff --git a/chromium/ash/system/user/tray_user.cc b/chromium/ash/system/user/tray_user.cc new file mode 100644 index 00000000000..ff14fe168c0 --- /dev/null +++ b/chromium/ash/system/user/tray_user.cc @@ -0,0 +1,1425 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ash/system/user/tray_user.h" + +#include <algorithm> +#include <climits> +#include <vector> + +#include "ash/ash_switches.h" +#include "ash/metrics/user_metrics_recorder.h" +#include "ash/multi_profile_uma.h" +#include "ash/popup_message.h" +#include "ash/root_window_controller.h" +#include "ash/session_state_delegate.h" +#include "ash/shelf/shelf_layout_manager.h" +#include "ash/shell.h" +#include "ash/shell_delegate.h" +#include "ash/system/tray/system_tray.h" +#include "ash/system/tray/system_tray_delegate.h" +#include "ash/system/tray/system_tray_notifier.h" +#include "ash/system/tray/tray_constants.h" +#include "ash/system/tray/tray_item_view.h" +#include "ash/system/tray/tray_popup_label_button.h" +#include "ash/system/tray/tray_popup_label_button_border.h" +#include "ash/system/tray/tray_utils.h" +#include "base/i18n/rtl.h" +#include "base/logging.h" +#include "base/memory/scoped_vector.h" +#include "base/strings/string16.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "grit/ash_resources.h" +#include "grit/ash_strings.h" +#include "skia/ext/image_operations.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkPaint.h" +#include "third_party/skia/include/core/SkPath.h" +#include "ui/aura/window.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/font_list.h" +#include "ui/gfx/image/image.h" +#include "ui/gfx/image/image_skia_operations.h" +#include "ui/gfx/insets.h" +#include "ui/gfx/range/range.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/render_text.h" +#include "ui/gfx/size.h" +#include "ui/gfx/skia_util.h" +#include "ui/gfx/text_elider.h" +#include "ui/gfx/text_utils.h" +#include "ui/views/border.h" +#include "ui/views/bubble/tray_bubble_view.h" +#include "ui/views/controls/button/button.h" +#include "ui/views/controls/button/custom_button.h" +#include "ui/views/controls/image_view.h" +#include "ui/views/controls/label.h" +#include "ui/views/controls/link.h" +#include "ui/views/controls/link_listener.h" +#include "ui/views/corewm/shadow_types.h" +#include "ui/views/layout/box_layout.h" +#include "ui/views/layout/fill_layout.h" +#include "ui/views/mouse_watcher.h" +#include "ui/views/painter.h" +#include "ui/views/view.h" +#include "ui/views/widget/widget.h" + +namespace { + +const int kUserDetailsVerticalPadding = 5; +const int kUserCardVerticalPadding = 10; +const int kProfileRoundedCornerRadius = 2; +const int kUserIconSize = 27; +const int kUserIconLargeSize = 32; +const int kUserIconLargeCornerRadius = 2; +const int kUserLabelToIconPadding = 5; +// When using multi login, this spacing is added between user icons. +const int kTrayLabelSpacing = 1; + +// When a hover border is used, it is starting this many pixels before the icon +// position. +const int kTrayUserTileHoverBorderInset = 10; + +// The border color of the user button. +const SkColor kBorderColor = 0xffdcdcdc; + +// The invisible word joiner character, used as a marker to indicate the start +// and end of the user's display name in the public account user card's text. +const char16 kDisplayNameMark[] = { 0x2060, 0 }; + +const int kPublicAccountLogoutButtonBorderImagesNormal[] = { + IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER, + IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND, + IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND, + IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER, + IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND, + IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND, + IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER, + IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND, + IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND, +}; + +const int kPublicAccountLogoutButtonBorderImagesHovered[] = { + IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER, + IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER, + IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER, + IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER, + IDR_AURA_TRAY_POPUP_LABEL_BUTTON_HOVER_BACKGROUND, + IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER, + IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER, + IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER, + IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER, +}; + +// Offsetting the popup message relative to the tray menu. +const int kPopupMessageOffset = 25; + +// Switch to a user with the given |user_index|. +void SwitchUser(ash::MultiProfileIndex user_index) { + // Do not switch users when the log screen is presented. + if (ash::Shell::GetInstance()->session_state_delegate()-> + IsUserSessionBlocked()) + return; + + DCHECK(user_index > 0); + ash::SessionStateDelegate* delegate = + ash::Shell::GetInstance()->session_state_delegate(); + ash::MultiProfileUMA::RecordSwitchActiveUser( + ash::MultiProfileUMA::SWITCH_ACTIVE_USER_BY_TRAY); + delegate->SwitchActiveUser(delegate->GetUserID(user_index)); +} + +} // namespace + +namespace ash { +namespace internal { + +namespace tray { + +// A custom image view with rounded edges. +class RoundedImageView : public views::View { + public: + // Constructs a new rounded image view with rounded corners of radius + // |corner_radius|. If |active_user| is set, the icon will be drawn in + // full colors - otherwise it will fade into the background. + RoundedImageView(int corner_radius, bool active_user); + virtual ~RoundedImageView(); + + // Set the image that should be displayed. The image contents is copied to the + // receiver's image. + void SetImage(const gfx::ImageSkia& img, const gfx::Size& size); + + // Set the radii of the corners independently. + void SetCornerRadii(int top_left, + int top_right, + int bottom_right, + int bottom_left); + + private: + // Overridden from views::View. + virtual gfx::Size GetPreferredSize() OVERRIDE; + virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE; + + gfx::ImageSkia image_; + gfx::ImageSkia resized_; + gfx::Size image_size_; + int corner_radius_[4]; + + // True if the given user is the active user and the icon should get + // painted as active. + bool active_user_; + + DISALLOW_COPY_AND_ASSIGN(RoundedImageView); +}; + +// An inactive user view which can be clicked to make active. Note that this +// "button" does not show as a button any click or hover changes. +class UserSwitcherView : public RoundedImageView { + public: + UserSwitcherView(int corner_radius, MultiProfileIndex user_index); + virtual ~UserSwitcherView() {} + + virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE; + virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE; + + private: + // The user index to activate when the item was clicked. Note that this + // index refers to the LRU list of logged in users. + MultiProfileIndex user_index_; + + DISALLOW_COPY_AND_ASSIGN(UserSwitcherView); +}; + +// The user details shown in public account mode. This is essentially a label +// but with custom painting code as the text is styled with multiple colors and +// contains a link. +class PublicAccountUserDetails : public views::View, + public views::LinkListener { + public: + PublicAccountUserDetails(SystemTrayItem* owner, int used_width); + virtual ~PublicAccountUserDetails(); + + private: + // Overridden from views::View. + virtual void Layout() OVERRIDE; + virtual gfx::Size GetPreferredSize() OVERRIDE; + virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE; + + // Overridden from views::LinkListener. + virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE; + + // Calculate a preferred size that ensures the label text and the following + // link do not wrap over more than three lines in total for aesthetic reasons + // if possible. + void CalculatePreferredSize(SystemTrayItem* owner, int used_width); + + base::string16 text_; + views::Link* learn_more_; + gfx::Size preferred_size_; + ScopedVector<gfx::RenderText> lines_; + + DISALLOW_COPY_AND_ASSIGN(PublicAccountUserDetails); +}; + +// The button which holds the user card in case of multi profile. +class UserCard : public views::CustomButton { + public: + UserCard(views::ButtonListener* listener, bool active_user); + virtual ~UserCard(); + + // Called when the border should remain even in the non highlighted state. + void ForceBorderVisible(bool show); + + // Overridden from views::View + virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE; + virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE; + + // Check if the item is hovered. + bool is_hovered_for_test() {return button_hovered_; } + + private: + // Change the hover/active state of the "button" when the status changes. + void ShowActive(); + + // True if this is the active user. + bool is_active_user_; + + // True if button is hovered. + bool button_hovered_; + + // True if the border should be visible. + bool show_border_; + + DISALLOW_COPY_AND_ASSIGN(UserCard); +}; + +class UserViewMouseWatcherHost : public views::MouseWatcherHost { +public: + explicit UserViewMouseWatcherHost(const gfx::Rect& screen_area) + : screen_area_(screen_area) {} + virtual ~UserViewMouseWatcherHost() {} + + // Implementation of MouseWatcherHost. + virtual bool Contains(const gfx::Point& screen_point, + views::MouseWatcherHost::MouseEventType type) OVERRIDE { + return screen_area_.Contains(screen_point); + } + +private: + gfx::Rect screen_area_; + + DISALLOW_COPY_AND_ASSIGN(UserViewMouseWatcherHost); +}; + +// The view of a user item. +class UserView : public views::View, + public views::ButtonListener, + public views::MouseWatcherListener { + public: + UserView(SystemTrayItem* owner, + ash::user::LoginStatus login, + MultiProfileIndex index); + virtual ~UserView(); + + // Overridden from MouseWatcherListener: + virtual void MouseMovedOutOfHost() OVERRIDE; + + TrayUser::TestState GetStateForTest() const; + gfx::Rect GetBoundsInScreenOfUserButtonForTest(); + + private: + // Overridden from views::View. + virtual gfx::Size GetPreferredSize() OVERRIDE; + virtual int GetHeightForWidth(int width) OVERRIDE; + virtual void Layout() OVERRIDE; + + // Overridden from views::ButtonListener. + virtual void ButtonPressed(views::Button* sender, + const ui::Event& event) OVERRIDE; + + void AddLogoutButton(user::LoginStatus login); + void AddUserCard(SystemTrayItem* owner, user::LoginStatus login); + + // Create a user icon representation for the user card. + views::View* CreateIconForUserCard(user::LoginStatus login); + + // Create the additional user card content for the retail logged in mode. + void AddLoggedInRetailModeUserCardContent(); + + // Create the additional user card content for the public mode. + void AddLoggedInPublicModeUserCardContent(SystemTrayItem* owner); + + // Create the menu option to add another user. If |disabled| is set the user + // cannot actively click on the item. + void ToggleAddUserMenuOption(); + + // Returns true when multi profile is supported. + bool SupportsMultiProfile(); + + MultiProfileIndex multiprofile_index_; + // The view of the user card. + views::View* user_card_view_; + + // This is the owner system tray item of this view. + SystemTrayItem* owner_; + + // True if |user_card_view_| is a |UserView| - otherwise it is only a + // |views::View|. + bool is_user_card_; + views::View* logout_button_; + scoped_ptr<PopupMessage> popup_message_; + scoped_ptr<views::Widget> add_menu_option_; + + // True when the add user panel is visible but not activatable. + bool add_user_visible_but_disabled_; + + // The mouse watcher which takes care of out of window hover events. + scoped_ptr<views::MouseWatcher> mouse_watcher_; + + DISALLOW_COPY_AND_ASSIGN(UserView); +}; + +// The menu item view which gets shown when the user clicks in multi profile +// mode onto the user item. +class AddUserView : public views::CustomButton, + public views::ButtonListener { + public: + // The |owner| is the view for which this view gets created. The |listener| + // will get notified when this item gets clicked. + AddUserView(UserCard* owner, views::ButtonListener* listener); + virtual ~AddUserView(); + + // Get the anchor view for a message. + views::View* anchor() { return anchor_; } + + // Overridden from views::ButtonListener. + virtual void ButtonPressed(views::Button* sender, + const ui::Event& event) OVERRIDE; + + private: + // Overridden from views::View. + virtual gfx::Size GetPreferredSize() OVERRIDE; + virtual int GetHeightForWidth(int width) OVERRIDE; + virtual void Layout() OVERRIDE; + + // Create the additional client content for this item. + void AddContent(); + + // This is the content we create and show. + views::View* add_user_; + + // This listener will get informed when someone clicks on this button. + views::ButtonListener* listener_; + + // This is the owner view of this item. + UserCard* owner_; + + // The anchor view for targetted bubble messages. + views::View* anchor_; + + DISALLOW_COPY_AND_ASSIGN(AddUserView); +}; + +RoundedImageView::RoundedImageView(int corner_radius, bool active_user) + : active_user_(active_user) { + for (int i = 0; i < 4; ++i) + corner_radius_[i] = corner_radius; +} + +RoundedImageView::~RoundedImageView() {} + +void RoundedImageView::SetImage(const gfx::ImageSkia& img, + const gfx::Size& size) { + image_ = img; + image_size_ = size; + + // Try to get the best image quality for the avatar. + resized_ = gfx::ImageSkiaOperations::CreateResizedImage(image_, + skia::ImageOperations::RESIZE_BEST, size); + if (GetWidget() && visible()) { + PreferredSizeChanged(); + SchedulePaint(); + } +} + +void RoundedImageView::SetCornerRadii(int top_left, + int top_right, + int bottom_right, + int bottom_left) { + corner_radius_[0] = top_left; + corner_radius_[1] = top_right; + corner_radius_[2] = bottom_right; + corner_radius_[3] = bottom_left; +} + +gfx::Size RoundedImageView::GetPreferredSize() { + return gfx::Size(image_size_.width() + GetInsets().width(), + image_size_.height() + GetInsets().height()); +} + +void RoundedImageView::OnPaint(gfx::Canvas* canvas) { + View::OnPaint(canvas); + gfx::Rect image_bounds(size()); + image_bounds.ClampToCenteredSize(GetPreferredSize()); + image_bounds.Inset(GetInsets()); + const SkScalar kRadius[8] = { + SkIntToScalar(corner_radius_[0]), + SkIntToScalar(corner_radius_[0]), + SkIntToScalar(corner_radius_[1]), + SkIntToScalar(corner_radius_[1]), + SkIntToScalar(corner_radius_[2]), + SkIntToScalar(corner_radius_[2]), + SkIntToScalar(corner_radius_[3]), + SkIntToScalar(corner_radius_[3]) + }; + SkPath path; + path.addRoundRect(gfx::RectToSkRect(image_bounds), kRadius); + SkPaint paint; + paint.setAntiAlias(true); + paint.setXfermodeMode(active_user_ ? SkXfermode::kSrcOver_Mode : + SkXfermode::kLuminosity_Mode); + canvas->DrawImageInPath(resized_, image_bounds.x(), image_bounds.y(), + path, paint); +} + +UserSwitcherView::UserSwitcherView(int corner_radius, + MultiProfileIndex user_index) + : RoundedImageView(corner_radius, false), + user_index_(user_index) { + SetEnabled(true); +} + +void UserSwitcherView::OnMouseEvent(ui::MouseEvent* event) { + if (event->type() == ui::ET_MOUSE_PRESSED) { + SwitchUser(user_index_); + event->SetHandled(); + } +} + +void UserSwitcherView::OnTouchEvent(ui::TouchEvent* event) { + if (event->type() == ui::ET_TOUCH_PRESSED) { + SwitchUser(user_index_); + event->SetHandled(); + } +} + +PublicAccountUserDetails::PublicAccountUserDetails(SystemTrayItem* owner, + int used_width) + : learn_more_(NULL) { + const int inner_padding = + kTrayPopupPaddingHorizontal - kTrayPopupPaddingBetweenItems; + const bool rtl = base::i18n::IsRTL(); + set_border(views::Border::CreateEmptyBorder( + kUserDetailsVerticalPadding, rtl ? 0 : inner_padding, + kUserDetailsVerticalPadding, rtl ? inner_padding : 0)); + + // Retrieve the user's display name and wrap it with markers. + // Note that since this is a public account it always has to be the primary + // user. + base::string16 display_name = + Shell::GetInstance()->session_state_delegate()->GetUserDisplayName(0); + base::RemoveChars(display_name, kDisplayNameMark, &display_name); + display_name = kDisplayNameMark[0] + display_name + kDisplayNameMark[0]; + // Retrieve the domain managing the device and wrap it with markers. + base::string16 domain = UTF8ToUTF16( + Shell::GetInstance()->system_tray_delegate()->GetEnterpriseDomain()); + base::RemoveChars(domain, kDisplayNameMark, &domain); + base::i18n::WrapStringWithLTRFormatting(&domain); + // Retrieve the label text, inserting the display name and domain. + text_ = l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_PUBLIC_LABEL, + display_name, domain); + + learn_more_ = new views::Link(l10n_util::GetStringUTF16(IDS_ASH_LEARN_MORE)); + learn_more_->SetUnderline(false); + learn_more_->set_listener(this); + AddChildView(learn_more_); + + CalculatePreferredSize(owner, used_width); +} + +PublicAccountUserDetails::~PublicAccountUserDetails() {} + +void PublicAccountUserDetails::Layout() { + lines_.clear(); + const gfx::Rect contents_area = GetContentsBounds(); + if (contents_area.IsEmpty()) + return; + + // Word-wrap the label text. + const gfx::FontList font_list; + std::vector<base::string16> lines; + gfx::ElideRectangleText(text_, font_list, contents_area.width(), + contents_area.height(), gfx::ELIDE_LONG_WORDS, + &lines); + // Loop through the lines, creating a renderer for each. + gfx::Point position = contents_area.origin(); + gfx::Range display_name(gfx::Range::InvalidRange()); + for (std::vector<base::string16>::const_iterator it = lines.begin(); + it != lines.end(); ++it) { + gfx::RenderText* line = gfx::RenderText::CreateInstance(); + line->SetDirectionalityMode(gfx::DIRECTIONALITY_FROM_UI); + line->SetText(*it); + const gfx::Size size(contents_area.width(), line->GetStringSize().height()); + line->SetDisplayRect(gfx::Rect(position, size)); + position.set_y(position.y() + size.height()); + + // Set the default text color for the line. + line->SetColor(kPublicAccountUserCardTextColor); + + // If a range of the line contains the user's display name, apply a custom + // text color to it. + if (display_name.is_empty()) + display_name.set_start(it->find(kDisplayNameMark)); + if (!display_name.is_empty()) { + display_name.set_end( + it->find(kDisplayNameMark, display_name.start() + 1)); + gfx::Range line_range(0, it->size()); + line->ApplyColor(kPublicAccountUserCardNameColor, + display_name.Intersect(line_range)); + // Update the range for the next line. + if (display_name.end() >= line_range.end()) + display_name.set_start(0); + else + display_name = gfx::Range::InvalidRange(); + } + + lines_.push_back(line); + } + + // Position the link after the label text, separated by a space. If it does + // not fit onto the last line of the text, wrap the link onto its own line. + const gfx::Size last_line_size = lines_.back()->GetStringSize(); + const int space_width = gfx::GetStringWidth(ASCIIToUTF16(" "), font_list); + const gfx::Size link_size = learn_more_->GetPreferredSize(); + if (contents_area.width() - last_line_size.width() >= + space_width + link_size.width()) { + position.set_x(position.x() + last_line_size.width() + space_width); + position.set_y(position.y() - last_line_size.height()); + } + position.set_y(position.y() - learn_more_->GetInsets().top()); + gfx::Rect learn_more_bounds(position, link_size); + learn_more_bounds.Intersect(contents_area); + if (base::i18n::IsRTL()) { + const gfx::Insets insets = GetInsets(); + learn_more_bounds.Offset(insets.right() - insets.left(), 0); + } + learn_more_->SetBoundsRect(learn_more_bounds); +} + +gfx::Size PublicAccountUserDetails::GetPreferredSize() { + return preferred_size_; +} + +void PublicAccountUserDetails::OnPaint(gfx::Canvas* canvas) { + for (ScopedVector<gfx::RenderText>::const_iterator it = lines_.begin(); + it != lines_.end(); ++it) { + (*it)->Draw(canvas); + } + views::View::OnPaint(canvas); +} + +void PublicAccountUserDetails::LinkClicked(views::Link* source, + int event_flags) { + DCHECK_EQ(source, learn_more_); + Shell::GetInstance()->system_tray_delegate()->ShowPublicAccountInfo(); +} + +void PublicAccountUserDetails::CalculatePreferredSize(SystemTrayItem* owner, + int used_width) { + const gfx::FontList font_list; + const gfx::Size link_size = learn_more_->GetPreferredSize(); + const int space_width = gfx::GetStringWidth(ASCIIToUTF16(" "), font_list); + const gfx::Insets insets = GetInsets(); + views::TrayBubbleView* bubble_view = + owner->system_tray()->GetSystemBubble()->bubble_view(); + int min_width = std::max( + link_size.width(), + bubble_view->GetPreferredSize().width() - (used_width + insets.width())); + int max_width = std::min( + gfx::GetStringWidth(text_, font_list) + space_width + link_size.width(), + bubble_view->GetMaximumSize().width() - (used_width + insets.width())); + // Do a binary search for the minimum width that ensures no more than three + // lines are needed. The lower bound is the minimum of the current bubble + // width and the width of the link (as no wrapping is permitted inside the + // link). The upper bound is the maximum of the largest allowed bubble width + // and the sum of the label text and link widths when put on a single line. + std::vector<base::string16> lines; + while (min_width < max_width) { + lines.clear(); + const int width = (min_width + max_width) / 2; + const bool too_narrow = + gfx::ElideRectangleText(text_, font_list, width, INT_MAX, + gfx::TRUNCATE_LONG_WORDS, &lines) != 0; + int line_count = lines.size(); + if (!too_narrow && line_count == 3 && + width - gfx::GetStringWidth(lines.back(), font_list) <= + space_width + link_size.width()) + ++line_count; + if (too_narrow || line_count > 3) + min_width = width + 1; + else + max_width = width; + } + + // Calculate the corresponding height and set the preferred size. + lines.clear(); + gfx::ElideRectangleText( + text_, font_list, min_width, INT_MAX, gfx::TRUNCATE_LONG_WORDS, &lines); + int line_count = lines.size(); + if (min_width - gfx::GetStringWidth(lines.back(), font_list) <= + space_width + link_size.width()) { + ++line_count; + } + const int line_height = font_list.GetHeight(); + const int link_extra_height = std::max( + link_size.height() - learn_more_->GetInsets().top() - line_height, 0); + preferred_size_ = gfx::Size( + min_width + insets.width(), + line_count * line_height + link_extra_height + insets.height()); + + bubble_view->SetWidth(preferred_size_.width() + used_width); +} + +UserCard::UserCard(views::ButtonListener* listener, bool active_user) + : CustomButton(listener), + is_active_user_(active_user), + button_hovered_(false), + show_border_(false) { + if (is_active_user_) { + set_background( + views::Background::CreateSolidBackground(kBackgroundColor)); + ShowActive(); + } +} + +UserCard::~UserCard() {} + +void UserCard::ForceBorderVisible(bool show) { + show_border_ = show; + ShowActive(); +} + +void UserCard::OnMouseEntered(const ui::MouseEvent& event) { + if (is_active_user_) { + button_hovered_ = true; + background()->SetNativeControlColor(kHoverBackgroundColor); + ShowActive(); + } +} + +void UserCard::OnMouseExited(const ui::MouseEvent& event) { + if (is_active_user_) { + button_hovered_ = false; + background()->SetNativeControlColor(kBackgroundColor); + ShowActive(); + } +} + +void UserCard::ShowActive() { + int width = button_hovered_ || show_border_ ? 1 : 0; + set_border(views::Border::CreateSolidSidedBorder(width, width, width, 1, + kBorderColor)); + SchedulePaint(); +} + +UserView::UserView(SystemTrayItem* owner, + user::LoginStatus login, + MultiProfileIndex index) + : multiprofile_index_(index), + user_card_view_(NULL), + owner_(owner), + is_user_card_(false), + logout_button_(NULL), + add_user_visible_but_disabled_(false) { + CHECK_NE(user::LOGGED_IN_NONE, login); + if (!index) { + // Only the logged in user will have a background. All other users will have + // to allow the TrayPopupContainer highlighting the menu line. + set_background(views::Background::CreateSolidBackground( + login == user::LOGGED_IN_PUBLIC ? kPublicAccountBackgroundColor : + kBackgroundColor)); + } + SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, + kTrayPopupPaddingBetweenItems)); + // The logout button must be added before the user card so that the user card + // can correctly calculate the remaining available width. + // Note that only the current multiprofile user gets a button. + if (!multiprofile_index_) + AddLogoutButton(login); + AddUserCard(owner, login); +} + +UserView::~UserView() {} + +void UserView::MouseMovedOutOfHost() { + popup_message_.reset(); + mouse_watcher_.reset(); + add_menu_option_.reset(); +} + +TrayUser::TestState UserView::GetStateForTest() const { + if (add_menu_option_.get()) { + return add_user_visible_but_disabled_ ? TrayUser::ACTIVE_BUT_DISABLED : + TrayUser::ACTIVE; + } + + if (!is_user_card_) + return TrayUser::SHOWN; + + return static_cast<UserCard*>(user_card_view_)->is_hovered_for_test() ? + TrayUser::HOVERED : TrayUser::SHOWN; +} + +gfx::Rect UserView::GetBoundsInScreenOfUserButtonForTest() { + DCHECK(user_card_view_); + return user_card_view_->GetBoundsInScreen(); +} + +gfx::Size UserView::GetPreferredSize() { + gfx::Size size = views::View::GetPreferredSize(); + // Only the active user panel will be forced to a certain height. + if (!multiprofile_index_) { + size.set_height(std::max(size.height(), + kTrayPopupItemHeight + GetInsets().height())); + } + return size; +} + +int UserView::GetHeightForWidth(int width) { + return GetPreferredSize().height(); +} + +void UserView::Layout() { + gfx::Rect contents_area(GetContentsBounds()); + if (user_card_view_ && logout_button_) { + // Give the logout button the space it requests. + gfx::Rect logout_area = contents_area; + logout_area.ClampToCenteredSize(logout_button_->GetPreferredSize()); + logout_area.set_x(contents_area.right() - logout_area.width()); + + // Give the remaining space to the user card. + gfx::Rect user_card_area = contents_area; + int remaining_width = contents_area.width() - logout_area.width(); + if (SupportsMultiProfile()) { + // In multiprofile case |user_card_view_| and |logout_button_| have to + // have the same height. + int y = std::min(user_card_area.y(), logout_area.y()); + int height = std::max(user_card_area.height(), logout_area.height()); + logout_area.set_y(y); + logout_area.set_height(height); + user_card_area.set_y(y); + user_card_area.set_height(height); + + // In multiprofile mode we have also to increase the size of the card by + // the size of the border to make it overlap with the logout button. + user_card_area.set_width(std::max(0, remaining_width + 1)); + + // To make the logout button symmetrical with the user card we also make + // the button longer by the same size the hover area in front of the icon + // got inset. + logout_area.set_width(logout_area.width() + + kTrayUserTileHoverBorderInset); + } else { + // In all other modes we have to make sure that there is enough spacing + // between the two. + remaining_width -= kTrayPopupPaddingBetweenItems; + } + user_card_area.set_width(remaining_width); + user_card_view_->SetBoundsRect(user_card_area); + logout_button_->SetBoundsRect(logout_area); + } else if (user_card_view_) { + user_card_view_->SetBoundsRect(contents_area); + } else if (logout_button_) { + logout_button_->SetBoundsRect(contents_area); + } +} + +void UserView::ButtonPressed(views::Button* sender, const ui::Event& event) { + if (sender == logout_button_) { + Shell::GetInstance()->metrics()->RecordUserMetricsAction( + ash::UMA_STATUS_AREA_SIGN_OUT); + Shell::GetInstance()->system_tray_delegate()->SignOut(); + } else if (sender == user_card_view_ && SupportsMultiProfile()) { + if (!multiprofile_index_) { + ToggleAddUserMenuOption(); + } else { + SwitchUser(multiprofile_index_); + // Since the user list is about to change the system menu should get + // closed. + owner_->system_tray()->CloseSystemBubble(); + } + } else if (add_menu_option_.get() && + sender == add_menu_option_->GetContentsView()) { + // Let the user add another account to the session. + MultiProfileUMA::RecordSigninUser(MultiProfileUMA::SIGNIN_USER_BY_TRAY); + Shell::GetInstance()->system_tray_delegate()->ShowUserLogin(); + } else { + NOTREACHED(); + } +} + +void UserView::AddLogoutButton(user::LoginStatus login) { + const base::string16 title = user::GetLocalizedSignOutStringForStatus(login, + true); + TrayPopupLabelButton* logout_button = new TrayPopupLabelButton(this, title); + logout_button->SetAccessibleName(title); + logout_button_ = logout_button; + // In public account mode, the logout button border has a custom color. + if (login == user::LOGGED_IN_PUBLIC) { + TrayPopupLabelButtonBorder* border = + static_cast<TrayPopupLabelButtonBorder*>(logout_button_->border()); + border->SetPainter(false, views::Button::STATE_NORMAL, + views::Painter::CreateImageGridPainter( + kPublicAccountLogoutButtonBorderImagesNormal)); + border->SetPainter(false, views::Button::STATE_HOVERED, + views::Painter::CreateImageGridPainter( + kPublicAccountLogoutButtonBorderImagesHovered)); + border->SetPainter(false, views::Button::STATE_PRESSED, + views::Painter::CreateImageGridPainter( + kPublicAccountLogoutButtonBorderImagesHovered)); + } + AddChildView(logout_button_); +} + +void UserView::AddUserCard(SystemTrayItem* owner, user::LoginStatus login) { + // Add padding around the panel. + set_border(views::Border::CreateEmptyBorder( + kUserCardVerticalPadding, kTrayPopupPaddingHorizontal, + kUserCardVerticalPadding, kTrayPopupPaddingHorizontal)); + + if (SupportsMultiProfile() && login != user::LOGGED_IN_RETAIL_MODE) { + user_card_view_ = new UserCard(this, multiprofile_index_ == 0); + is_user_card_ = true; + } else { + user_card_view_ = new views::View(); + is_user_card_ = false; + } + + user_card_view_->SetLayoutManager(new views::BoxLayout( + views::BoxLayout::kHorizontal, 0, 0 , kTrayPopupPaddingBetweenItems)); + AddChildViewAt(user_card_view_, 0); + + if (login == user::LOGGED_IN_RETAIL_MODE) { + AddLoggedInRetailModeUserCardContent(); + return; + } + + // The entire user card should trigger hover (the inner items get disabled). + user_card_view_->SetEnabled(true); + user_card_view_->set_notify_enter_exit_on_child(true); + + if (login == user::LOGGED_IN_PUBLIC) { + AddLoggedInPublicModeUserCardContent(owner); + return; + } + + views::View* icon = CreateIconForUserCard(login); + user_card_view_->AddChildView(icon); + + // To allow the border to start before the icon, reduce the size before and + // add an inset to the icon to get the spacing. + if (multiprofile_index_ == 0 && SupportsMultiProfile()) { + icon->set_border(views::Border::CreateEmptyBorder( + 0, kTrayUserTileHoverBorderInset, 0, 0)); + set_border(views::Border::CreateEmptyBorder( + kUserCardVerticalPadding, + kTrayPopupPaddingHorizontal - kTrayUserTileHoverBorderInset, + kUserCardVerticalPadding, + kTrayPopupPaddingHorizontal)); + } + SessionStateDelegate* delegate = + Shell::GetInstance()->session_state_delegate(); + views::Label* username = NULL; + ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); + if (!multiprofile_index_) { + base::string16 user_name_string = + login == user::LOGGED_IN_GUEST ? + bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_GUEST_LABEL) : + delegate->GetUserDisplayName(multiprofile_index_); + if (!user_name_string.empty()) { + username = new views::Label(user_name_string); + username->SetHorizontalAlignment(gfx::ALIGN_LEFT); + } + } + + views::Label* additional = NULL; + if (login != user::LOGGED_IN_GUEST) { + base::string16 user_email_string = + login == user::LOGGED_IN_LOCALLY_MANAGED ? + bundle.GetLocalizedString( + IDS_ASH_STATUS_TRAY_LOCALLY_MANAGED_LABEL) : + UTF8ToUTF16(delegate->GetUserEmail(multiprofile_index_)); + if (!user_email_string.empty()) { + additional = new views::Label(user_email_string); + additional->SetFontList( + bundle.GetFontList(ui::ResourceBundle::SmallFont)); + additional->SetHorizontalAlignment(gfx::ALIGN_LEFT); + } + } + + // Adjust text properties dependent on if it is an active or inactive user. + if (multiprofile_index_) { + // Fade the text of non active users to 50%. + SkColor text_color = additional->enabled_color(); + text_color = SkColorSetA(text_color, SkColorGetA(text_color) / 2); + if (additional) + additional->SetDisabledColor(text_color); + if (username) + username->SetDisabledColor(text_color); + } + + if (additional && username) { + views::View* details = new views::View; + details->SetLayoutManager(new views::BoxLayout( + views::BoxLayout::kVertical, 0, kUserDetailsVerticalPadding, 0)); + details->AddChildView(username); + details->AddChildView(additional); + user_card_view_->AddChildView(details); + } else { + if (username) + user_card_view_->AddChildView(username); + if (additional) + user_card_view_->AddChildView(additional); + } +} + +views::View* UserView::CreateIconForUserCard(user::LoginStatus login) { + RoundedImageView* icon = new RoundedImageView(kProfileRoundedCornerRadius, + multiprofile_index_ == 0); + icon->SetEnabled(false); + if (login == user::LOGGED_IN_GUEST) { + icon->SetImage(*ui::ResourceBundle::GetSharedInstance(). + GetImageNamed(IDR_AURA_UBER_TRAY_GUEST_ICON).ToImageSkia(), + gfx::Size(kUserIconSize, kUserIconSize)); + } else { + icon->SetImage( + Shell::GetInstance()->session_state_delegate()-> + GetUserImage(multiprofile_index_), + gfx::Size(kUserIconSize, kUserIconSize)); + } + return icon; +} + +void UserView::AddLoggedInRetailModeUserCardContent() { + views::Label* details = new views::Label; + ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); + details->SetText( + bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_KIOSK_LABEL)); + details->set_border(views::Border::CreateEmptyBorder(0, 4, 0, 1)); + details->SetHorizontalAlignment(gfx::ALIGN_LEFT); + user_card_view_->AddChildView(details); +} + +void UserView::AddLoggedInPublicModeUserCardContent(SystemTrayItem* owner) { + user_card_view_->AddChildView(CreateIconForUserCard(user::LOGGED_IN_PUBLIC)); + user_card_view_->AddChildView(new PublicAccountUserDetails( + owner, GetPreferredSize().width() + kTrayPopupPaddingBetweenItems)); +} + +void UserView::ToggleAddUserMenuOption() { + if (add_menu_option_.get()) { + popup_message_.reset(); + mouse_watcher_.reset(); + add_menu_option_.reset(); + return; + } + + // Note: We do not need to install a global event handler to delete this + // item since it will destroyed automatically before the menu / user menu item + // gets destroyed.. + const SessionStateDelegate* session_state_delegate = + Shell::GetInstance()->session_state_delegate(); + add_user_visible_but_disabled_ = + session_state_delegate->NumberOfLoggedInUsers() >= + session_state_delegate->GetMaximumNumberOfLoggedInUsers(); + add_menu_option_.reset(new views::Widget); + views::Widget::InitParams params; + params.type = views::Widget::InitParams::TYPE_TOOLTIP; + params.keep_on_top = true; + params.context = this->GetWidget()->GetNativeWindow(); + params.accept_events = true; + params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; + add_menu_option_->Init(params); + add_menu_option_->SetOpacity(0xFF); + add_menu_option_->GetNativeWindow()->set_owned_by_parent(false); + SetShadowType(add_menu_option_->GetNativeView(), + views::corewm::SHADOW_TYPE_NONE); + + // Position it below our user card. + gfx::Rect bounds = user_card_view_->GetBoundsInScreen(); + bounds.set_y(bounds.y() + bounds.height()); + add_menu_option_->SetBounds(bounds); + + // Show the content. + AddUserView* add_user_view = new AddUserView( + static_cast<UserCard*>(user_card_view_), this); + add_menu_option_->SetContentsView(add_user_view); + add_menu_option_->SetAlwaysOnTop(true); + add_menu_option_->Show(); + if (add_user_visible_but_disabled_) { + ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); + popup_message_.reset(new PopupMessage( + bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_CAPTION_CANNOT_ADD_USER), + bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_MESSAGE_CANNOT_ADD_USER), + PopupMessage::ICON_WARNING, + add_user_view->anchor(), + views::BubbleBorder::TOP_LEFT, + gfx::Size(parent()->bounds().width() - kPopupMessageOffset, 0), + 2 * kPopupMessageOffset)); + } + // Find the screen area which encloses both elements and sets then a mouse + // watcher which will close the "menu". + gfx::Rect area = user_card_view_->GetBoundsInScreen(); + area.set_height(2 * area.height()); + mouse_watcher_.reset(new views::MouseWatcher( + new UserViewMouseWatcherHost(area), + this)); + mouse_watcher_->Start(); +} + +bool UserView::SupportsMultiProfile() { + // We do not want to see any multi profile additions to a user view when the + // log in screen is shown. + return Shell::GetInstance()->delegate()->IsMultiProfilesEnabled() && + !Shell::GetInstance()->session_state_delegate()->IsUserSessionBlocked(); +} + +AddUserView::AddUserView(UserCard* owner, views::ButtonListener* listener) + : CustomButton(listener_), + add_user_(NULL), + listener_(listener), + owner_(owner), + anchor_(NULL) { + AddContent(); + owner_->ForceBorderVisible(true); +} + +AddUserView::~AddUserView() { + owner_->ForceBorderVisible(false); +} + +gfx::Size AddUserView::GetPreferredSize() { + return owner_->bounds().size(); +} + +int AddUserView::GetHeightForWidth(int width) { + return owner_->bounds().size().height(); +} + +void AddUserView::Layout() { + gfx::Rect contents_area(GetContentsBounds()); + add_user_->SetBoundsRect(contents_area); +} + +void AddUserView::ButtonPressed(views::Button* sender, const ui::Event& event) { + if (add_user_ == sender) + listener_->ButtonPressed(this, event); + else + NOTREACHED(); +} + +void AddUserView::AddContent() { + set_notify_enter_exit_on_child(true); + + const SessionStateDelegate* delegate = + Shell::GetInstance()->session_state_delegate(); + bool enable = delegate->NumberOfLoggedInUsers() < + delegate->GetMaximumNumberOfLoggedInUsers(); + + SetLayoutManager(new views::FillLayout()); + set_background(views::Background::CreateSolidBackground(kBackgroundColor)); + + // Add padding around the panel. + set_border(views::Border::CreateSolidBorder(1, kBorderColor)); + + add_user_ = new UserCard(this, enable); + add_user_->set_border(views::Border::CreateEmptyBorder( + kUserCardVerticalPadding, + kTrayPopupPaddingHorizontal- kTrayUserTileHoverBorderInset, + kUserCardVerticalPadding, + kTrayPopupPaddingHorizontal- kTrayUserTileHoverBorderInset)); + + add_user_->SetLayoutManager(new views::BoxLayout( + views::BoxLayout::kHorizontal, 0, 0 , kTrayPopupPaddingBetweenItems)); + AddChildViewAt(add_user_, 0); + + // Add the [+] icon which is also the anchor for messages. + ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); + RoundedImageView* icon = new RoundedImageView(kProfileRoundedCornerRadius, + true); + anchor_ = icon; + icon->SetImage(*ui::ResourceBundle::GetSharedInstance(). + GetImageNamed(IDR_AURA_UBER_TRAY_ADD_MULTIPROFILE_USER).ToImageSkia(), + gfx::Size(kUserIconSize, kUserIconSize)); + add_user_->AddChildView(icon); + + // Add the command text. + views::Label* command_label = new views::Label( + bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_SIGN_IN_ANOTHER_ACCOUNT)); + command_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); + add_user_->AddChildView(command_label); +} + +} // namespace tray + +TrayUser::TrayUser(SystemTray* system_tray, MultiProfileIndex index) + : SystemTrayItem(system_tray), + multiprofile_index_(index), + user_(NULL), + layout_view_(NULL), + avatar_(NULL), + label_(NULL) { + Shell::GetInstance()->system_tray_notifier()->AddUserObserver(this); +} + +TrayUser::~TrayUser() { + Shell::GetInstance()->system_tray_notifier()->RemoveUserObserver(this); +} + +TrayUser::TestState TrayUser::GetStateForTest() const { + if (!user_) + return HIDDEN; + return user_->GetStateForTest(); +} + +bool TrayUser::CanDropWindowHereToTransferToUser( + const gfx::Point& point_in_screen) { + // Check that this item is shown in the system tray (which means it must have + // a view there) and that the user it represents is not the current user (in + // which case |GetTrayIndex()| would return NULL). + if (!layout_view_ || !GetTrayIndex()) + return false; + return layout_view_->GetBoundsInScreen().Contains(point_in_screen); +} + +bool TrayUser::TransferWindowToUser(aura::Window* window) { + SessionStateDelegate* session_state_delegate = + ash::Shell::GetInstance()->session_state_delegate(); + return session_state_delegate->TransferWindowToDesktopOfUser(window, + GetTrayIndex()); +} + +gfx::Rect TrayUser::GetUserPanelBoundsInScreenForTest() const { + DCHECK(user_); + return user_->GetBoundsInScreenOfUserButtonForTest(); +} + +views::View* TrayUser::CreateTrayView(user::LoginStatus status) { + CHECK(layout_view_ == NULL); + // When the full multi profile mode is used, only the active user will be + // shown in the system tray, otherwise all users which are logged in. + if (GetTrayIndex() && switches::UseFullMultiProfileMode()) + return NULL; + + layout_view_ = new views::View(); + layout_view_->SetLayoutManager( + new views::BoxLayout(views::BoxLayout::kHorizontal, + 0, 0, kUserLabelToIconPadding)); + UpdateAfterLoginStatusChange(status); + return layout_view_; +} + +views::View* TrayUser::CreateDefaultView(user::LoginStatus status) { + if (status == user::LOGGED_IN_NONE) + return NULL; + const SessionStateDelegate* session_state_delegate = + Shell::GetInstance()->session_state_delegate(); + + // If the screen is locked show only the currently active user. + if (multiprofile_index_ && session_state_delegate->IsUserSessionBlocked()) + return NULL; + + CHECK(user_ == NULL); + + int logged_in_users = session_state_delegate->NumberOfLoggedInUsers(); + + // Do not show more UserView's then there are logged in users. + if (multiprofile_index_ >= logged_in_users) + return NULL; + + user_ = new tray::UserView(this, status, multiprofile_index_); + return user_; +} + +views::View* TrayUser::CreateDetailedView(user::LoginStatus status) { + return NULL; +} + +void TrayUser::DestroyTrayView() { + layout_view_ = NULL; + avatar_ = NULL; + label_ = NULL; +} + +void TrayUser::DestroyDefaultView() { + user_ = NULL; +} + +void TrayUser::DestroyDetailedView() { +} + +void TrayUser::UpdateAfterLoginStatusChange(user::LoginStatus status) { + // Only the active user is represented in the tray. + if (!layout_view_) + return; + if (GetTrayIndex() > 0 && !ash::switches::UseMultiUserTray()) + return; + bool need_label = false; + bool need_avatar = false; + switch (status) { + case user::LOGGED_IN_LOCKED: + case user::LOGGED_IN_USER: + case user::LOGGED_IN_OWNER: + case user::LOGGED_IN_PUBLIC: + need_avatar = true; + break; + case user::LOGGED_IN_LOCALLY_MANAGED: + need_avatar = true; + need_label = true; + break; + case user::LOGGED_IN_GUEST: + need_label = true; + break; + case user::LOGGED_IN_RETAIL_MODE: + case user::LOGGED_IN_KIOSK_APP: + case user::LOGGED_IN_NONE: + break; + } + + if ((need_avatar != (avatar_ != NULL)) || + (need_label != (label_ != NULL))) { + layout_view_->RemoveAllChildViews(true); + if (need_label) { + label_ = new views::Label; + SetupLabelForTray(label_); + layout_view_->AddChildView(label_); + } else { + label_ = NULL; + } + if (need_avatar) { + MultiProfileIndex tray_index = GetTrayIndex(); + if (!tray_index) { + // The active user (index #0) will always be the first. + avatar_ = new tray::RoundedImageView(kProfileRoundedCornerRadius, true); + } else { + // All other users will be inactive users. + avatar_ = new tray::UserSwitcherView(kProfileRoundedCornerRadius, + tray_index); + } + layout_view_->AddChildView(avatar_); + } else { + avatar_ = NULL; + } + } + + ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); + if (status == user::LOGGED_IN_LOCALLY_MANAGED) { + label_->SetText( + bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_LOCALLY_MANAGED_LABEL)); + } else if (status == user::LOGGED_IN_GUEST) { + label_->SetText(bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_GUEST_LABEL)); + } + + if (avatar_ && switches::UseAlternateShelfLayout()) { + int corner_radius = GetTrayItemRadius(); + avatar_->SetCornerRadii(0, corner_radius, corner_radius, 0); + avatar_->set_border(NULL); + } + UpdateAvatarImage(status); + + // Update layout after setting label_ and avatar_ with new login status. + UpdateLayoutOfItem(); +} + +void TrayUser::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) { + // Inactive users won't have a layout. + if (!layout_view_) + return; + int corner_radius = GetTrayItemRadius(); + if (alignment == SHELF_ALIGNMENT_BOTTOM || + alignment == SHELF_ALIGNMENT_TOP) { + if (avatar_) { + if (switches::UseAlternateShelfLayout()) { + if (multiprofile_index_) { + avatar_->set_border( + views::Border::CreateEmptyBorder(0, kTrayLabelSpacing, 0, 0)); + } else { + avatar_->set_border(NULL); + } + avatar_->SetCornerRadii(0, corner_radius, corner_radius, 0); + } else { + avatar_->set_border(views::Border::CreateEmptyBorder( + 0, kTrayImageItemHorizontalPaddingBottomAlignment + 2, + 0, kTrayImageItemHorizontalPaddingBottomAlignment)); + } + } + if (label_) { + label_->set_border(views::Border::CreateEmptyBorder( + 0, kTrayLabelItemHorizontalPaddingBottomAlignment, + 0, kTrayLabelItemHorizontalPaddingBottomAlignment)); + } + layout_view_->SetLayoutManager( + new views::BoxLayout(views::BoxLayout::kHorizontal, + 0, 0, kUserLabelToIconPadding)); + } else { + if (avatar_) { + if (switches::UseAlternateShelfLayout()) { + if (multiprofile_index_) { + avatar_->set_border( + views::Border::CreateEmptyBorder(kTrayLabelSpacing, 0, 0, 0)); + } else { + avatar_->set_border(NULL); + } + avatar_->SetCornerRadii(0, 0, corner_radius, corner_radius); + } else { + SetTrayImageItemBorder(avatar_, alignment); + } + } + if (label_) { + label_->set_border(views::Border::CreateEmptyBorder( + kTrayLabelItemVerticalPaddingVerticalAlignment, + kTrayLabelItemHorizontalPaddingBottomAlignment, + kTrayLabelItemVerticalPaddingVerticalAlignment, + kTrayLabelItemHorizontalPaddingBottomAlignment)); + } + layout_view_->SetLayoutManager( + new views::BoxLayout(views::BoxLayout::kVertical, + 0, 0, kUserLabelToIconPadding)); + } +} + +void TrayUser::OnUserUpdate() { + UpdateAvatarImage(Shell::GetInstance()->system_tray_delegate()-> + GetUserLoginStatus()); +} + +void TrayUser::OnUserAddedToSession() { + SessionStateDelegate* session_state_delegate = + Shell::GetInstance()->session_state_delegate(); + // Only create views for user items which are logged in. + if (GetTrayIndex() >= session_state_delegate->NumberOfLoggedInUsers()) + return; + + // Enforce a layout change that newly added items become visible. + UpdateLayoutOfItem(); + + // Update the user item. + UpdateAvatarImage( + Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus()); +} + +void TrayUser::UpdateAvatarImage(user::LoginStatus status) { + SessionStateDelegate* session_state_delegate = + Shell::GetInstance()->session_state_delegate(); + if (!avatar_ || + GetTrayIndex() >= session_state_delegate->NumberOfLoggedInUsers()) + return; + + int icon_size = switches::UseAlternateShelfLayout() ? + kUserIconLargeSize : kUserIconSize; + + avatar_->SetImage( + Shell::GetInstance()->session_state_delegate()->GetUserImage( + GetTrayIndex()), + gfx::Size(icon_size, icon_size)); + + // Unit tests might come here with no images for some users. + if (avatar_->size().IsEmpty()) + avatar_->SetSize(gfx::Size(icon_size, icon_size)); +} + +MultiProfileIndex TrayUser::GetTrayIndex() { + Shell* shell = Shell::GetInstance(); + // If multi profile is not enabled we can use the normal index. + if (!shell->delegate()->IsMultiProfilesEnabled()) + return multiprofile_index_; + // In case of multi profile we need to mirror the indices since the system + // tray items are in the reverse order then the menu items. + return shell->session_state_delegate()->GetMaximumNumberOfLoggedInUsers() - + 1 - multiprofile_index_; +} + +int TrayUser::GetTrayItemRadius() { + SessionStateDelegate* delegate = + Shell::GetInstance()->session_state_delegate(); + bool is_last_item = GetTrayIndex() == (delegate->NumberOfLoggedInUsers() - 1); + return is_last_item ? kUserIconLargeCornerRadius : 0; +} + +void TrayUser::UpdateLayoutOfItem() { + internal::RootWindowController* controller = + internal::GetRootWindowController( + system_tray()->GetWidget()->GetNativeWindow()->GetRootWindow()); + if (controller && controller->shelf()) { + UpdateAfterShelfAlignmentChange( + controller->GetShelfLayoutManager()->GetAlignment()); + } +} + +} // namespace internal +} // namespace ash |