// 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