summaryrefslogtreecommitdiffstats
path: root/ash/system/user
diff options
context:
space:
mode:
authordzhioev@chromium.org <dzhioev@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-04-10 13:17:17 +0000
committerdzhioev@chromium.org <dzhioev@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-04-10 13:17:17 +0000
commitd7ffac75143b8ca0ba900844360f1c6583d36b0d (patch)
tree1c8d4210bd6786d695fa6cdb4910dc3dcdb95a40 /ash/system/user
parent1ddf5f18253195a96ff84e4eb2ac9428b7939da6 (diff)
downloadchromium_src-d7ffac75143b8ca0ba900844360f1c6583d36b0d.zip
chromium_src-d7ffac75143b8ca0ba900844360f1c6583d36b0d.tar.gz
chromium_src-d7ffac75143b8ca0ba900844360f1c6583d36b0d.tar.bz2
Implemented system tray UI for new account management.
* Added new mode in TrayUser for the case when new account management is enabled (--new-profile-management flag). In fact TrayUser is now supporting four different modes, depending of states of |multi-profiles| flag and |new-profile-management| flag. * Massive refactoring were made in tray_user.cc to isolate UserCardView creation in separate class and make code more clear. * UI for the cases when new account management is disabled remained without changes. Known issues: * There are no tests for new UI. Hopefully old UI is covered by tests already. * New UI is not accessible yet. * Stub implementation of UserAccountsDelegate is used for backend. BUG=344844 TEST=manually Review URL: https://codereview.chromium.org/210903003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@262959 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ash/system/user')
-rw-r--r--ash/system/user/accounts_detailed_view.cc227
-rw-r--r--ash/system/user/accounts_detailed_view.h70
-rw-r--r--ash/system/user/button_from_view.cc71
-rw-r--r--ash/system/user/button_from_view.h55
-rw-r--r--ash/system/user/config.cc36
-rw-r--r--ash/system/user/config.h22
-rw-r--r--ash/system/user/rounded_image_view.cc79
-rw-r--r--ash/system/user/rounded_image_view.h55
-rw-r--r--ash/system/user/tray_user.cc1085
-rw-r--r--ash/system/user/tray_user.h3
-rw-r--r--ash/system/user/user_accounts_delegate.cc27
-rw-r--r--ash/system/user/user_accounts_delegate.h69
-rw-r--r--ash/system/user/user_card_view.cc382
-rw-r--r--ash/system/user/user_card_view.h44
-rw-r--r--ash/system/user/user_view.cc490
-rw-r--r--ash/system/user/user_view.h94
16 files changed, 1739 insertions, 1070 deletions
diff --git a/ash/system/user/accounts_detailed_view.cc b/ash/system/user/accounts_detailed_view.cc
new file mode 100644
index 0000000..fe5bb68
--- /dev/null
+++ b/ash/system/user/accounts_detailed_view.cc
@@ -0,0 +1,227 @@
+// Copyright 2014 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/accounts_detailed_view.h"
+
+#include <vector>
+
+#include "ash/multi_profile_uma.h"
+#include "ash/shell.h"
+#include "ash/system/tray/fixed_sized_scroll_view.h"
+#include "ash/system/tray/hover_highlight_view.h"
+#include "ash/system/tray/system_tray.h"
+#include "ash/system/tray/system_tray_delegate.h"
+#include "ash/system/tray/tray_constants.h"
+#include "ash/system/tray/tray_popup_header_button.h"
+#include "ash/system/user/config.h"
+#include "ash/system/user/tray_user.h"
+#include "base/strings/utf_string_conversions.h"
+#include "grit/ash_resources.h"
+#include "grit/ui_resources.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/views/border.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/layout/grid_layout.h"
+
+namespace ash {
+namespace tray {
+
+namespace {
+
+const int kAccountsViewVerticalPadding = 12;
+const int kPrimaryAccountColumnSetID = 0;
+const int kSecondaryAccountColumnSetID = 1;
+const int kPaddingBetweenAccounts = 20;
+
+} // namespace
+
+AccountsDetailedView::AccountsDetailedView(TrayUser* owner,
+ user::LoginStatus login_status)
+ : TrayDetailsView(owner),
+ delegate_(NULL),
+ account_list_(NULL),
+ add_account_button_(NULL),
+ add_user_button_(NULL) {
+ std::string user_id =
+ Shell::GetInstance()->session_state_delegate()->GetUserID(0);
+ delegate_ =
+ Shell::GetInstance()->system_tray_delegate()->GetUserAccountsDelegate(
+ user_id);
+ delegate_->AddObserver(this);
+ AddHeader(login_status);
+ CreateScrollableList();
+ AddAccountList();
+ AddAddAccountButton();
+ AddFooter();
+}
+
+AccountsDetailedView::~AccountsDetailedView() {
+ delegate_->RemoveObserver(this);
+}
+
+void AccountsDetailedView::OnViewClicked(views::View* sender) {
+ if (sender == footer()->content())
+ TransitionToDefaultView();
+ else if (sender == add_account_button_)
+ delegate_->LaunchAddAccountDialog();
+ else
+ NOTREACHED();
+}
+
+void AccountsDetailedView::ButtonPressed(views::Button* sender,
+ const ui::Event& event) {
+ std::map<views::View*, std::string>::iterator it =
+ delete_button_to_account_id_.find(sender);
+ if (it != delete_button_to_account_id_.end()) {
+ delegate_->DeleteAccount(it->second);
+ } else if (add_user_button_ && add_user_button_ == sender) {
+ MultiProfileUMA::RecordSigninUser(MultiProfileUMA::SIGNIN_USER_BY_TRAY);
+ Shell::GetInstance()->system_tray_delegate()->ShowUserLogin();
+ owner()->system_tray()->CloseSystemBubble();
+ } else {
+ NOTREACHED();
+ }
+}
+
+void AccountsDetailedView::AccountListChanged() { UpdateAccountList(); }
+
+void AccountsDetailedView::AddHeader(user::LoginStatus login_status) {
+ views::View* user_view_container = new views::View;
+ user_view_container->SetLayoutManager(
+ new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
+ user_view_container->SetBorder(
+ views::Border::CreateSolidSidedBorder(0, 0, 1, 0, kBorderLightColor));
+ user_view_container->AddChildView(
+ new tray::UserView(owner(), login_status, 0, true));
+ AddChildView(user_view_container);
+}
+
+void AccountsDetailedView::AddAccountList() {
+ scroll_content()->SetBorder(
+ views::Border::CreateEmptyBorder(kAccountsViewVerticalPadding,
+ kTrayPopupPaddingHorizontal,
+ kAccountsViewVerticalPadding,
+ kTrayPopupPaddingHorizontal));
+ views::Label* account_list_title = new views::Label(
+ l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ACCOUNT_LIST_TITLE));
+ account_list_title->SetEnabledColor(SkColorSetARGB(0x7f, 0, 0, 0));
+ account_list_title->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ scroll_content()->AddChildView(account_list_title);
+ account_list_ = new views::View();
+ UpdateAccountList();
+ scroll_content()->AddChildView(account_list_);
+}
+
+void AccountsDetailedView::AddAddAccountButton() {
+ SessionStateDelegate* session_state_delegate =
+ Shell::GetInstance()->session_state_delegate();
+ HoverHighlightView* add_account_button = new HoverHighlightView(this);
+ base::string16 user_name = session_state_delegate->GetUserGivenName(0);
+ if (user_name.empty())
+ user_name = session_state_delegate->GetUserDisplayName(0);
+ if (user_name.empty())
+ user_name = base::ASCIIToUTF16(session_state_delegate->GetUserEmail(0));
+ add_account_button->AddLabel(
+ l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_ADD_ACCOUNT_LABEL,
+ user_name),
+ gfx::ALIGN_CENTER,
+ gfx::Font::NORMAL);
+ AddChildView(add_account_button);
+ add_account_button_ = add_account_button;
+}
+
+void AccountsDetailedView::AddFooter() {
+ CreateSpecialRow(IDS_ASH_STATUS_TRAY_ACCOUNTS_TITLE, this);
+ if (!IsMultiProfileSupportedAndUserActive())
+ return;
+ TrayPopupHeaderButton* add_user_button =
+ new TrayPopupHeaderButton(this,
+ IDR_AURA_UBER_TRAY_NETWORK_INFO,
+ IDR_AURA_UBER_TRAY_NETWORK_INFO,
+ IDR_AURA_UBER_TRAY_NETWORK_INFO_HOVER,
+ IDR_AURA_UBER_TRAY_NETWORK_INFO_HOVER,
+ IDS_ASH_STATUS_TRAY_SIGN_IN_ANOTHER_ACCOUNT);
+ add_user_button->SetTooltipText(
+ l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_SIGN_IN_ANOTHER_ACCOUNT));
+ footer()->AddButton(add_user_button);
+ add_user_button_ = add_user_button;
+}
+
+void AccountsDetailedView::UpdateAccountList() {
+ // Clear existing view.
+ delete_button_to_account_id_.clear();
+ account_list_->RemoveAllChildViews(true);
+
+ // Configuring layout manager.
+ views::GridLayout* layout = new views::GridLayout(account_list_);
+ account_list_->SetLayoutManager(layout);
+ views::ColumnSet* primary_account_row =
+ layout->AddColumnSet(kPrimaryAccountColumnSetID);
+ primary_account_row->AddColumn(views::GridLayout::LEADING,
+ views::GridLayout::BASELINE,
+ 1.0,
+ views::GridLayout::USE_PREF,
+ 0,
+ 0);
+ views::ColumnSet* secondary_account_row =
+ layout->AddColumnSet(kSecondaryAccountColumnSetID);
+ secondary_account_row->AddColumn(views::GridLayout::FILL,
+ views::GridLayout::BASELINE,
+ 1.0,
+ views::GridLayout::USE_PREF,
+ 0,
+ 0);
+ secondary_account_row->AddPaddingColumn(0.0, kTrayPopupPaddingBetweenItems);
+ secondary_account_row->AddColumn(views::GridLayout::FILL,
+ views::GridLayout::BASELINE,
+ 0.0,
+ views::GridLayout::USE_PREF,
+ 0,
+ 0);
+
+ // Adding primary account.
+ layout->AddPaddingRow(0.0, kPaddingBetweenAccounts);
+ layout->StartRow(0.0, kPrimaryAccountColumnSetID);
+ const std::string& primary_account = delegate_->GetPrimaryAccount();
+ views::Label* primary_account_label =
+ new views::Label(l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_PRIMARY_ACCOUNT_LABEL,
+ base::ASCIIToUTF16(
+ delegate_->GetAccountDisplayName(primary_account))));
+ layout->AddView(primary_account_label);
+
+ // Adding secondary accounts.
+ const std::vector<std::string>& secondary_accounts =
+ delegate_->GetSecondaryAccountsList();
+ for (size_t i = 0; i < secondary_accounts.size(); ++i) {
+ layout->AddPaddingRow(0.0, kPaddingBetweenAccounts);
+ layout->StartRow(0.0, kSecondaryAccountColumnSetID);
+ const std::string& account_id = secondary_accounts[i];
+ views::Label* account_label = new views::Label(
+ base::ASCIIToUTF16(delegate_->GetAccountDisplayName(account_id)));
+ account_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ layout->AddView(account_label);
+ views::View* delete_button = CreateDeleteButton();
+ delete_button_to_account_id_[delete_button] = account_id;
+ layout->AddView(delete_button);
+ }
+
+ scroll_content()->SizeToPreferredSize();
+ scroller()->Layout();
+}
+
+views::View* AccountsDetailedView::CreateDeleteButton() {
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
+ views::ImageButton* delete_button = new views::ImageButton(this);
+ delete_button->SetImage(views::Button::STATE_NORMAL,
+ rb.GetImageNamed(IDR_CLOSE_2).ToImageSkia());
+ delete_button->SetImage(views::Button::STATE_HOVERED,
+ rb.GetImageNamed(IDR_CLOSE_2_H).ToImageSkia());
+ delete_button->SetImage(views::Button::STATE_PRESSED,
+ rb.GetImageNamed(IDR_CLOSE_2_P).ToImageSkia());
+ return delete_button;
+}
+
+} // namespace tray
+} // namespace ash
diff --git a/ash/system/user/accounts_detailed_view.h b/ash/system/user/accounts_detailed_view.h
new file mode 100644
index 0000000..6dd9a0b
--- /dev/null
+++ b/ash/system/user/accounts_detailed_view.h
@@ -0,0 +1,70 @@
+// Copyright 2014 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.
+
+#ifndef ASH_SYSTEM_USER_ACCOUNTS_DETAILED_VIEW_H_
+#define ASH_SYSTEM_USER_ACCOUNTS_DETAILED_VIEW_H_
+
+#include <map>
+#include <string>
+
+#include "ash/system/tray/tray_details_view.h"
+#include "ash/system/tray/view_click_listener.h"
+#include "ash/system/user/login_status.h"
+#include "ash/system/user/user_accounts_delegate.h"
+#include "ash/system/user/user_view.h"
+#include "base/macros.h"
+#include "grit/ash_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/views/controls/button/button.h"
+#include "ui/views/controls/label.h"
+
+namespace ash {
+
+class TrayUser;
+
+namespace tray {
+
+// This detailed view appears after a click on the primary user's card when the
+// new account managment is enabled.
+class AccountsDetailedView : public TrayDetailsView,
+ public ViewClickListener,
+ public views::ButtonListener,
+ public ash::tray::UserAccountsDelegate::Observer {
+ public:
+ AccountsDetailedView(TrayUser* owner, user::LoginStatus login_status);
+ virtual ~AccountsDetailedView();
+
+ private:
+ // Overridden from ViewClickListener.
+ virtual void OnViewClicked(views::View* sender) OVERRIDE;
+
+ // Overridden from views::ButtonListener.
+ virtual void ButtonPressed(views::Button* sender,
+ const ui::Event& event) OVERRIDE;
+
+ // Overridden from ash::tray::UserAccountsDelegate::Observer.
+ virtual void AccountListChanged() OVERRIDE;
+
+ void AddHeader(user::LoginStatus login_status);
+ void AddAccountList();
+ void AddAddAccountButton();
+ void AddFooter();
+
+ void UpdateAccountList();
+
+ views::View* CreateDeleteButton();
+
+ ash::tray::UserAccountsDelegate* delegate_;
+ views::View* account_list_;
+ views::View* add_account_button_;
+ views::View* add_user_button_;
+ std::map<views::View*, std::string> delete_button_to_account_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(AccountsDetailedView);
+};
+
+} // namespace tray
+} // namespace ash
+
+#endif // ASH_SYSTEM_USER_ACCOUNTS_DETAILED_VIEW_H_
diff --git a/ash/system/user/button_from_view.cc b/ash/system/user/button_from_view.cc
new file mode 100644
index 0000000..64b641d
--- /dev/null
+++ b/ash/system/user/button_from_view.cc
@@ -0,0 +1,71 @@
+// Copyright 2014 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/button_from_view.h"
+
+#include "ash/system/tray/tray_constants.h"
+#include "ui/views/background.h"
+#include "ui/views/border.h"
+#include "ui/views/layout/box_layout.h"
+
+namespace ash {
+
+namespace {
+
+// The border color of the user button.
+const SkColor kBorderColor = 0xffdcdcdc;
+
+} // namespace
+
+namespace tray {
+
+ButtonFromView::ButtonFromView(views::View* content,
+ views::ButtonListener* listener,
+ bool highlight_on_hover)
+ : CustomButton(listener),
+ content_(content),
+ highlight_on_hover_(highlight_on_hover),
+ button_hovered_(false),
+ show_border_(false) {
+ set_notify_enter_exit_on_child(true);
+ SetLayoutManager(
+ new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0));
+ AddChildView(content_);
+ ShowActive();
+}
+
+ButtonFromView::~ButtonFromView() {}
+
+void ButtonFromView::ForceBorderVisible(bool show) {
+ show_border_ = show;
+ ShowActive();
+}
+
+void ButtonFromView::OnMouseEntered(const ui::MouseEvent& event) {
+ button_hovered_ = true;
+ ShowActive();
+}
+
+void ButtonFromView::OnMouseExited(const ui::MouseEvent& event) {
+ button_hovered_ = false;
+ ShowActive();
+}
+
+void ButtonFromView::ShowActive() {
+ bool border_visible =
+ (button_hovered_ && highlight_on_hover_) || show_border_;
+ SkColor border_color = border_visible ? kBorderColor : SK_ColorTRANSPARENT;
+ SetBorder(views::Border::CreateSolidBorder(1, border_color));
+ if (highlight_on_hover_) {
+ SkColor background_color =
+ button_hovered_ ? kHoverBackgroundColor : kBackgroundColor;
+ content_->set_background(
+ views::Background::CreateSolidBackground(background_color));
+ set_background(views::Background::CreateSolidBackground(background_color));
+ }
+ SchedulePaint();
+}
+
+} // namespace tray
+} // namespace ash
diff --git a/ash/system/user/button_from_view.h b/ash/system/user/button_from_view.h
new file mode 100644
index 0000000..1d391a6
--- /dev/null
+++ b/ash/system/user/button_from_view.h
@@ -0,0 +1,55 @@
+// Copyright 2014 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.
+
+#ifndef ASH_SYSTEM_USER_BUTTON_FROM_VIEW_H_
+#define ASH_SYSTEM_USER_BUTTON_FROM_VIEW_H_
+
+#include "base/macros.h"
+
+#include "ui/views/controls/button/custom_button.h"
+
+namespace ash {
+namespace tray {
+
+// This view is used to wrap it's content and transform it into button.
+class ButtonFromView : public views::CustomButton {
+ public:
+ ButtonFromView(views::View* content,
+ views::ButtonListener* listener,
+ bool highlight_on_hover);
+ virtual ~ButtonFromView();
+
+ // 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();
+
+ // Content of button.
+ views::View* content_;
+
+ // Whether button should be highligthed on hover.
+ bool highlight_on_hover_;
+
+ // True if button is hovered.
+ bool button_hovered_;
+
+ // True if the border should be always visible.
+ bool show_border_;
+
+ DISALLOW_COPY_AND_ASSIGN(ButtonFromView);
+};
+
+} // namespace tray
+} // namespace ash
+
+#endif // ASH_SYSTEM_USER_BUTTON_FROM_VIEW_H_
diff --git a/ash/system/user/config.cc b/ash/system/user/config.cc
new file mode 100644
index 0000000..d704393
--- /dev/null
+++ b/ash/system/user/config.cc
@@ -0,0 +1,36 @@
+// Copyright 2014 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/config.h"
+
+#include "ash/session_state_delegate.h"
+#include "ash/shell.h"
+#include "ash/shell_delegate.h"
+
+namespace ash {
+namespace tray {
+
+namespace {
+
+// Returns true if session is blocked by e.g. the login screen.
+bool IsUserSessionBlocked() {
+ return Shell::GetInstance()
+ ->session_state_delegate()
+ ->IsUserSessionBlocked();
+}
+
+} // namespace
+
+bool IsMultiProfileSupportedAndUserActive() {
+ return Shell::GetInstance()->delegate()->IsMultiProfilesEnabled() &&
+ !IsUserSessionBlocked();
+}
+
+bool IsMultiAccountSupportedAndUserActive() {
+ return Shell::GetInstance()->delegate()->IsMultiAccountEnabled() &&
+ !IsUserSessionBlocked();
+}
+
+} // namespace tray
+} // namespace ash
diff --git a/ash/system/user/config.h b/ash/system/user/config.h
new file mode 100644
index 0000000..c3676e4
--- /dev/null
+++ b/ash/system/user/config.h
@@ -0,0 +1,22 @@
+// Copyright 2014 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.
+
+#ifndef ASH_SYSTEM_USER_CONFIG_H_
+#define ASH_SYSTEM_USER_CONFIG_H_
+
+#include "base/macros.h"
+
+namespace ash {
+namespace tray {
+
+// Returns true when multi profile is supported and user is active.
+bool IsMultiProfileSupportedAndUserActive();
+
+// Returns true when multi account is supported and user is active.
+bool IsMultiAccountSupportedAndUserActive();
+
+} // namespace tray
+} // namespace ash
+
+#endif // ASH_SYSTEM_USER_CONFIG_H_
diff --git a/ash/system/user/rounded_image_view.cc b/ash/system/user/rounded_image_view.cc
new file mode 100644
index 0000000..b5a38bd
--- /dev/null
+++ b/ash/system/user/rounded_image_view.cc
@@ -0,0 +1,79 @@
+// Copyright 2014 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/rounded_image_view.h"
+#include "skia/ext/image_operations.h"
+#include "third_party/skia/include/core/SkPaint.h"
+#include "third_party/skia/include/core/SkPath.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/image/image_skia_operations.h"
+#include "ui/gfx/skia_util.h"
+
+namespace ash {
+namespace tray {
+
+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);
+}
+
+} // namespace tray
+} // namespace ash
diff --git a/ash/system/user/rounded_image_view.h b/ash/system/user/rounded_image_view.h
new file mode 100644
index 0000000..5723d37
--- /dev/null
+++ b/ash/system/user/rounded_image_view.h
@@ -0,0 +1,55 @@
+// Copyright 2014 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.
+
+#ifndef ASH_SYSTEM_USER_ROUNDED_IMAGE_VIEW_H_
+#define ASH_SYSTEM_USER_ROUNDED_IMAGE_VIEW_H_
+
+#include "base/macros.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/views/view.h"
+
+namespace ash {
+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);
+};
+
+} // namespace tray
+} // namespace ash
+
+#endif // ASH_SYSTEM_USER_ROUNDED_IMAGE_VIEW_H_
diff --git a/ash/system/user/tray_user.cc b/ash/system/user/tray_user.cc
index e8ea56e..e22cdf4 100644
--- a/ash/system/user/tray_user.cc
+++ b/ash/system/user/tray_user.cc
@@ -4,1093 +4,41 @@
#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 "ash/system/user/accounts_detailed_view.h"
+#include "ash/system/user/rounded_image_view.h"
+#include "ash/system/user/user_view.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/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"
-#include "ui/wm/core/shadow_types.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 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 base::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));
-}
+const int kTrayAvatarLargeSize = 32;
+const int kTrayAvatarLargeCornerRadius = 2;
} // namespace
namespace ash {
-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);
-};
-
-// 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);
-}
-
-PublicAccountUserDetails::PublicAccountUserDetails(SystemTrayItem* owner,
- int used_width)
- : learn_more_(NULL) {
- const int inner_padding =
- kTrayPopupPaddingHorizontal - kTrayPopupPaddingBetweenItems;
- const bool rtl = base::i18n::IsRTL();
- SetBorder(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 = base::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(base::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(base::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;
- SetBorder(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();
- owner_->system_tray()->CloseSystemBubble();
- } 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) {
- scoped_ptr<TrayPopupLabelButtonBorder> border(
- new TrayPopupLabelButtonBorder());
- 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));
- logout_button_->SetBorder(border.PassAs<views::Border>());
- }
- AddChildView(logout_button_);
-}
-
-void UserView::AddUserCard(SystemTrayItem* owner, user::LoginStatus login) {
- // Add padding around the panel.
- SetBorder(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->SetBorder(views::Border::CreateEmptyBorder(
- 0, kTrayUserTileHoverBorderInset, 0, 0));
- SetBorder(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) :
- base::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 {
- SessionStateDelegate* delegate =
- Shell::GetInstance()->session_state_delegate();
- content::BrowserContext* context = delegate->GetBrowserContextByIndex(
- multiprofile_index_);
- icon->SetImage(delegate->GetUserImage(context),
- 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->SetBorder(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(),
- wm::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.
- SetBorder(views::Border::CreateSolidBorder(1, kBorderColor));
-
- add_user_ = new UserCard(this, enable);
- add_user_->SetBorder(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),
@@ -1132,7 +80,7 @@ void TrayUser::UpdateAfterLoginStatusChangeForTest(user::LoginStatus status) {
views::View* TrayUser::CreateTrayView(user::LoginStatus status) {
CHECK(layout_view_ == NULL);
- layout_view_ = new views::View();
+ layout_view_ = new views::View;
layout_view_->SetLayoutManager(
new views::BoxLayout(views::BoxLayout::kHorizontal,
0, 0, kUserLabelToIconPadding));
@@ -1158,12 +106,12 @@ views::View* TrayUser::CreateDefaultView(user::LoginStatus status) {
if (multiprofile_index_ >= logged_in_users)
return NULL;
- user_ = new tray::UserView(this, status, multiprofile_index_);
+ user_ = new tray::UserView(this, status, multiprofile_index_, false);
return user_;
}
views::View* TrayUser::CreateDetailedView(user::LoginStatus status) {
- return NULL;
+ return new tray::AccountsDetailedView(this, status);
}
void TrayUser::DestroyTrayView() {
@@ -1218,24 +166,23 @@ void TrayUser::UpdateAfterLoginStatusChange(user::LoginStatus status) {
label_ = NULL;
}
if (need_avatar) {
- avatar_ = new tray::RoundedImageView(kProfileRoundedCornerRadius, true);
+ avatar_ = new tray::RoundedImageView(kTrayAvatarCornerRadius, true);
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));
+ l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_LOCALLY_MANAGED_LABEL));
} else if (status == user::LOGGED_IN_GUEST) {
- label_->SetText(bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_GUEST_LABEL));
+ label_->SetText(l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_GUEST_LABEL));
}
if (avatar_ && switches::UseAlternateShelfLayout()) {
avatar_->SetCornerRadii(
- 0, kUserIconLargeCornerRadius, kUserIconLargeCornerRadius, 0);
+ 0, kTrayAvatarLargeCornerRadius, kTrayAvatarLargeCornerRadius, 0);
avatar_->SetBorder(views::Border::NullBorder());
}
UpdateAvatarImage(status);
@@ -1254,7 +201,7 @@ void TrayUser::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) {
if (switches::UseAlternateShelfLayout()) {
avatar_->SetBorder(views::Border::NullBorder());
avatar_->SetCornerRadii(
- 0, kUserIconLargeCornerRadius, kUserIconLargeCornerRadius, 0);
+ 0, kTrayAvatarLargeCornerRadius, kTrayAvatarLargeCornerRadius, 0);
} else {
avatar_->SetBorder(views::Border::CreateEmptyBorder(
0,
@@ -1284,7 +231,7 @@ void TrayUser::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) {
if (switches::UseAlternateShelfLayout()) {
avatar_->SetBorder(views::Border::NullBorder());
avatar_->SetCornerRadii(
- 0, 0, kUserIconLargeCornerRadius, kUserIconLargeCornerRadius);
+ 0, 0, kTrayAvatarLargeCornerRadius, kTrayAvatarLargeCornerRadius);
} else {
SetTrayImageItemBorder(avatar_, alignment);
}
@@ -1329,8 +276,8 @@ void TrayUser::UpdateAvatarImage(user::LoginStatus status) {
GetTrayIndex() >= session_state_delegate->NumberOfLoggedInUsers())
return;
- int icon_size = switches::UseAlternateShelfLayout() ?
- kUserIconLargeSize : kUserIconSize;
+ int icon_size = switches::UseAlternateShelfLayout() ? kTrayAvatarLargeSize
+ : kTrayAvatarSize;
content::BrowserContext* context = session_state_delegate->
GetBrowserContextByIndex(GetTrayIndex());
diff --git a/ash/system/user/tray_user.h b/ash/system/user/tray_user.h
index d956c31..0b19d47 100644
--- a/ash/system/user/tray_user.h
+++ b/ash/system/user/tray_user.h
@@ -22,9 +22,10 @@ class Label;
}
namespace ash {
+
namespace tray {
-class UserView;
class RoundedImageView;
+class UserView;
}
class ASH_EXPORT TrayUser : public SystemTrayItem,
diff --git a/ash/system/user/user_accounts_delegate.cc b/ash/system/user/user_accounts_delegate.cc
new file mode 100644
index 0000000..2e29a9b
--- /dev/null
+++ b/ash/system/user/user_accounts_delegate.cc
@@ -0,0 +1,27 @@
+// Copyright 2014 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/user_accounts_delegate.h"
+
+namespace ash {
+namespace tray {
+
+UserAccountsDelegate::UserAccountsDelegate() {}
+
+UserAccountsDelegate::~UserAccountsDelegate() {}
+
+void UserAccountsDelegate::AddObserver(Observer* observer) {
+ observers_.AddObserver(observer);
+}
+
+void UserAccountsDelegate::RemoveObserver(Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+void UserAccountsDelegate::NotifyAccountListChanged() {
+ FOR_EACH_OBSERVER(Observer, observers_, AccountListChanged());
+}
+
+} // namespace tray
+} // namespace ash
diff --git a/ash/system/user/user_accounts_delegate.h b/ash/system/user/user_accounts_delegate.h
new file mode 100644
index 0000000..b717e83
--- /dev/null
+++ b/ash/system/user/user_accounts_delegate.h
@@ -0,0 +1,69 @@
+// Copyright 2014 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.
+
+#ifndef ASH_SYSTEM_USER_USER_ACCOUNTS_DELEGATE_H_
+#define ASH_SYSTEM_USER_USER_ACCOUNTS_DELEGATE_H_
+
+#include <string>
+#include <vector>
+
+#include "ash/ash_export.h"
+#include "base/macros.h"
+#include "base/observer_list.h"
+
+namespace ash {
+namespace tray {
+
+class ASH_EXPORT UserAccountsDelegate {
+ public:
+ class Observer {
+ public:
+ Observer() {}
+ virtual ~Observer() {}
+
+ // Called when the account list of user has been changed.
+ virtual void AccountListChanged() = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Observer);
+ };
+
+ UserAccountsDelegate();
+ virtual ~UserAccountsDelegate();
+
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ // Returns the user's primary account as a canonicalized email address.
+ virtual std::string GetPrimaryAccount() = 0;
+
+ // Returns a list of the user's secondary accounts in form of canonicalized
+ // email addresses.
+ virtual std::vector<std::string> GetSecondaryAccountsList() = 0;
+
+ // Returns display name for given |account_id|. It has form of email but could
+ // be uncanonical, e.g. account "test-user@gmail.com" could have display
+ // name "Test-user+AfterPlus@gmail.com".
+ virtual std::string GetAccountDisplayName(const std::string& account_id) = 0;
+
+ // Deletes given |account_id| from the list of user's account. Passing
+ // |account_id| that is not from list is no-op.
+ virtual void DeleteAccount(const std::string& account_id) = 0;
+
+ // Launches a dialog which lets the user add a new account.
+ virtual void LaunchAddAccountDialog() = 0;
+
+ protected:
+ void NotifyAccountListChanged();
+
+ private:
+ ObserverList<Observer> observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(UserAccountsDelegate);
+};
+
+} // namespace tray
+} // namespace ash
+
+#endif // ASH_SYSTEM_USER_USER_ACCOUNTS_DELEGATE_H_
diff --git a/ash/system/user/user_card_view.cc b/ash/system/user/user_card_view.cc
new file mode 100644
index 0000000..6d5413e
--- /dev/null
+++ b/ash/system/user/user_card_view.cc
@@ -0,0 +1,382 @@
+// Copyright 2014 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/user_card_view.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "ash/session_state_delegate.h"
+#include "ash/shell.h"
+#include "ash/system/tray/system_tray_delegate.h"
+#include "ash/system/tray/tray_constants.h"
+#include "ash/system/user/config.h"
+#include "ash/system/user/rounded_image_view.h"
+#include "base/i18n/rtl.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 "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.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/text_elider.h"
+#include "ui/gfx/text_utils.h"
+#include "ui/views/border.h"
+#include "ui/views/controls/link.h"
+#include "ui/views/controls/link_listener.h"
+#include "ui/views/layout/box_layout.h"
+
+namespace ash {
+namespace tray {
+
+namespace {
+
+const int kUserDetailsVerticalPadding = 5;
+
+// 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 base::char16 kDisplayNameMark[] = {0x2060, 0};
+
+// 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(int max_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(int max_allowed_width);
+
+ base::string16 text_;
+ views::Link* learn_more_;
+ gfx::Size preferred_size_;
+ ScopedVector<gfx::RenderText> lines_;
+
+ DISALLOW_COPY_AND_ASSIGN(PublicAccountUserDetails);
+};
+
+PublicAccountUserDetails::PublicAccountUserDetails(int max_width)
+ : learn_more_(NULL) {
+ const int inner_padding =
+ kTrayPopupPaddingHorizontal - kTrayPopupPaddingBetweenItems;
+ const bool rtl = base::i18n::IsRTL();
+ SetBorder(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 = base::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(max_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(base::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(int max_allowed_width) {
+ const gfx::FontList font_list;
+ const gfx::Size link_size = learn_more_->GetPreferredSize();
+ const int space_width =
+ gfx::GetStringWidth(base::ASCIIToUTF16(" "), font_list);
+ const gfx::Insets insets = GetInsets();
+ int min_width = link_size.width();
+ int max_width = std::min(
+ gfx::GetStringWidth(text_, font_list) + space_width + link_size.width(),
+ max_allowed_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());
+}
+
+} // namespace
+
+UserCardView::UserCardView(user::LoginStatus login_status,
+ int max_width,
+ int multiprofile_index) {
+ SetLayoutManager(new views::BoxLayout(
+ views::BoxLayout::kHorizontal, 0, 0, kTrayPopupPaddingBetweenItems));
+ switch (login_status) {
+ case user::LOGGED_IN_RETAIL_MODE:
+ AddRetailModeUserContent();
+ break;
+ case user::LOGGED_IN_PUBLIC:
+ AddPublicModeUserContent(max_width);
+ break;
+ default:
+ AddUserContent(login_status, multiprofile_index);
+ break;
+ }
+}
+
+UserCardView::~UserCardView() {}
+
+void UserCardView::AddRetailModeUserContent() {
+ views::Label* details = new views::Label;
+ details->SetText(l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_KIOSK_LABEL));
+ details->SetBorder(views::Border::CreateEmptyBorder(0, 4, 0, 1));
+ details->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ AddChildView(details);
+}
+
+void UserCardView::AddPublicModeUserContent(int max_width) {
+ views::View* icon = CreateIcon(user::LOGGED_IN_PUBLIC, 0);
+ AddChildView(icon);
+ int details_max_width = max_width - icon->GetPreferredSize().width() -
+ kTrayPopupPaddingBetweenItems;
+ AddChildView(new PublicAccountUserDetails(details_max_width));
+}
+
+void UserCardView::AddUserContent(user::LoginStatus login_status,
+ int multiprofile_index) {
+ views::View* icon = CreateIcon(login_status, multiprofile_index);
+ AddChildView(icon);
+ views::Label* username = NULL;
+ SessionStateDelegate* delegate =
+ Shell::GetInstance()->session_state_delegate();
+ if (!multiprofile_index) {
+ base::string16 user_name_string =
+ login_status == user::LOGGED_IN_GUEST
+ ? l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_GUEST_LABEL)
+ : delegate->GetUserDisplayName(multiprofile_index);
+ if (user_name_string.empty() && IsMultiAccountSupportedAndUserActive())
+ user_name_string =
+ base::ASCIIToUTF16(delegate->GetUserEmail(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_status != user::LOGGED_IN_GUEST &&
+ (multiprofile_index || !IsMultiAccountSupportedAndUserActive())) {
+ base::string16 user_email_string =
+ login_status == user::LOGGED_IN_LOCALLY_MANAGED
+ ? l10n_util::GetStringUTF16(
+ IDS_ASH_STATUS_TRAY_LOCALLY_MANAGED_LABEL)
+ : base::UTF8ToUTF16(delegate->GetUserEmail(multiprofile_index));
+ if (!user_email_string.empty()) {
+ additional = new views::Label(user_email_string);
+ additional->SetFontList(
+ ui::ResourceBundle::GetSharedInstance().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);
+ AddChildView(details);
+ } else {
+ if (username)
+ AddChildView(username);
+ if (additional)
+ AddChildView(additional);
+ }
+}
+
+views::View* UserCardView::CreateIcon(user::LoginStatus login_status,
+ int multiprofile_index) {
+ RoundedImageView* icon =
+ new RoundedImageView(kTrayAvatarCornerRadius, multiprofile_index == 0);
+ if (login_status == user::LOGGED_IN_GUEST) {
+ icon->SetImage(*ui::ResourceBundle::GetSharedInstance()
+ .GetImageNamed(IDR_AURA_UBER_TRAY_GUEST_ICON)
+ .ToImageSkia(),
+ gfx::Size(kTrayAvatarSize, kTrayAvatarSize));
+ } else {
+ SessionStateDelegate* delegate =
+ Shell::GetInstance()->session_state_delegate();
+ content::BrowserContext* context =
+ delegate->GetBrowserContextByIndex(multiprofile_index);
+ icon->SetImage(delegate->GetUserImage(context),
+ gfx::Size(kTrayAvatarSize, kTrayAvatarSize));
+ }
+ return icon;
+}
+
+} // namespace tray
+} // namespace ash
diff --git a/ash/system/user/user_card_view.h b/ash/system/user/user_card_view.h
new file mode 100644
index 0000000..fc9883c8
--- /dev/null
+++ b/ash/system/user/user_card_view.h
@@ -0,0 +1,44 @@
+// Copyright 2014 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.
+
+#ifndef ASH_SYSTEM_USER_USER_CARD_VIEW_H_
+#define ASH_SYSTEM_USER_USER_CARD_VIEW_H_
+
+#include "ash/system/user/login_status.h"
+#include "base/macros.h"
+#include "ui/views/view.h"
+
+namespace ash {
+namespace tray {
+
+// The view displaying information about the user, such as user's avatar, email
+// address, name, and more. View has no borders.
+class UserCardView : public views::View {
+ public:
+ // |max_width| takes effect only if |login_status| is LOGGED_IN_PUBLIC.
+ UserCardView(user::LoginStatus login_status,
+ int max_width,
+ int multiprofile_index);
+ virtual ~UserCardView();
+
+ private:
+ // Creates the content for the retail logged in mode.
+ void AddRetailModeUserContent();
+
+ // Creates the content for the public mode.
+ void AddPublicModeUserContent(int max_width);
+
+ void AddUserContent(user::LoginStatus login_status, int multiprofile_index);
+
+ // Create a user icon representation.
+ views::View* CreateIcon(user::LoginStatus login_status,
+ int multiprofile_index);
+
+ DISALLOW_COPY_AND_ASSIGN(UserCardView);
+};
+
+} // namespace tray
+} // namespace ash
+
+#endif // ASH_SYSTEM_USER_USER_CARD_VIEW_H_
diff --git a/ash/system/user/user_view.cc b/ash/system/user/user_view.cc
new file mode 100644
index 0000000..4743163
--- /dev/null
+++ b/ash/system/user/user_view.cc
@@ -0,0 +1,490 @@
+// Copyright 2014 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/user_view.h"
+
+#include <algorithm>
+
+#include "ash/multi_profile_uma.h"
+#include "ash/popup_message.h"
+#include "ash/session_state_delegate.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/tray_popup_label_button.h"
+#include "ash/system/tray/tray_popup_label_button_border.h"
+#include "ash/system/user/button_from_view.h"
+#include "ash/system/user/config.h"
+#include "ash/system/user/rounded_image_view.h"
+#include "ash/system/user/user_card_view.h"
+#include "grit/ash_resources.h"
+#include "grit/ash_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/views/layout/fill_layout.h"
+#include "ui/views/painter.h"
+#include "ui/wm/core/shadow_types.h"
+
+namespace ash {
+namespace tray {
+
+namespace {
+
+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,
+};
+
+// When a hover border is used, it is starting this many pixels before the icon
+// position.
+const int kTrayUserTileHoverBorderInset = 10;
+
+// 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));
+}
+
+class LogoutButton : public TrayPopupLabelButton {
+ public:
+ // If |placeholder| is true, button is used as placeholder. That means that
+ // button is inactive and is not painted, but consume the same ammount of
+ // space, as if it was painted.
+ LogoutButton(views::ButtonListener* listener,
+ const base::string16& text,
+ bool placeholder)
+ : TrayPopupLabelButton(listener, text), placeholder_(placeholder) {
+ SetEnabled(!placeholder_);
+ }
+
+ virtual ~LogoutButton() {}
+
+ private:
+ virtual void Paint(gfx::Canvas* canvas) OVERRIDE {
+ // Just skip paint if this button used as a placeholder.
+ if (!placeholder_)
+ TrayPopupLabelButton::Paint(canvas);
+ }
+
+ bool placeholder_;
+ DISALLOW_COPY_AND_ASSIGN(LogoutButton);
+};
+
+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 menu item view which gets shown when the user clicks in multi profile
+// mode onto the user item.
+class AddUserView : public views::View {
+ public:
+ // The |owner| is the view for which this view gets created.
+ AddUserView(ButtonFromView* owner);
+ virtual ~AddUserView();
+
+ // Get the anchor view for a message.
+ views::View* anchor() { return anchor_; }
+
+ private:
+ // Overridden from views::View.
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+
+ // Create the additional client content for this item.
+ void AddContent();
+
+ // This is the content we create and show.
+ views::View* add_user_;
+
+ // This is the owner view of this item.
+ ButtonFromView* owner_;
+
+ // The anchor view for targetted bubble messages.
+ views::View* anchor_;
+
+ DISALLOW_COPY_AND_ASSIGN(AddUserView);
+};
+
+AddUserView::AddUserView(ButtonFromView* owner)
+ : add_user_(NULL), owner_(owner), anchor_(NULL) {
+ AddContent();
+ owner_->ForceBorderVisible(true);
+}
+
+AddUserView::~AddUserView() {
+ owner_->ForceBorderVisible(false);
+}
+
+gfx::Size AddUserView::GetPreferredSize() {
+ return owner_->bounds().size();
+}
+
+void AddUserView::AddContent() {
+ SetLayoutManager(new views::FillLayout());
+ set_background(views::Background::CreateSolidBackground(kBackgroundColor));
+
+ add_user_ = new views::View;
+ add_user_->SetBorder(views::Border::CreateEmptyBorder(
+ kTrayPopupUserCardVerticalPadding,
+ kTrayPopupPaddingHorizontal - kTrayUserTileHoverBorderInset,
+ kTrayPopupUserCardVerticalPadding,
+ 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.
+ RoundedImageView* icon = new RoundedImageView(kTrayAvatarCornerRadius, true);
+ anchor_ = icon;
+ icon->SetImage(*ui::ResourceBundle::GetSharedInstance()
+ .GetImageNamed(IDR_AURA_UBER_TRAY_ADD_MULTIPROFILE_USER)
+ .ToImageSkia(),
+ gfx::Size(kTrayAvatarSize, kTrayAvatarSize));
+ add_user_->AddChildView(icon);
+
+ // Add the command text.
+ views::Label* command_label = new views::Label(
+ l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_SIGN_IN_ANOTHER_ACCOUNT));
+ command_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ add_user_->AddChildView(command_label);
+}
+
+} // namespace
+
+UserView::UserView(SystemTrayItem* owner,
+ user::LoginStatus login,
+ MultiProfileIndex index,
+ bool for_detailed_view)
+ : multiprofile_index_(index),
+ user_card_view_(NULL),
+ owner_(owner),
+ is_user_card_button_(false),
+ logout_button_(NULL),
+ add_user_disabled_(false),
+ for_detailed_view_(for_detailed_view) {
+ 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(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_disabled_ ? TrayUser::ACTIVE_BUT_DISABLED
+ : TrayUser::ACTIVE;
+ }
+
+ if (!is_user_card_button_)
+ return TrayUser::SHOWN;
+
+ return static_cast<ButtonFromView*>(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 (IsMultiProfileSupportedAndUserActive() ||
+ IsMultiAccountSupportedAndUserActive()) {
+ // 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_ && !multiprofile_index_ &&
+ IsMultiAccountSupportedAndUserActive()) {
+ owner_->TransitionDetailedView();
+ } else if (sender == user_card_view_ &&
+ IsMultiProfileSupportedAndUserActive()) {
+ 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();
+ owner_->system_tray()->CloseSystemBubble();
+ } else {
+ NOTREACHED();
+ }
+}
+
+void UserView::AddLogoutButton(user::LoginStatus login) {
+ const base::string16 title =
+ user::GetLocalizedSignOutStringForStatus(login, true);
+ TrayPopupLabelButton* logout_button =
+ new LogoutButton(this, title, for_detailed_view_);
+ 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) {
+ scoped_ptr<TrayPopupLabelButtonBorder> border(
+ new TrayPopupLabelButtonBorder());
+ 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));
+ logout_button_->SetBorder(border.PassAs<views::Border>());
+ }
+ AddChildView(logout_button_);
+}
+
+void UserView::AddUserCard(user::LoginStatus login) {
+ // Add padding around the panel.
+ SetBorder(views::Border::CreateEmptyBorder(kTrayPopupUserCardVerticalPadding,
+ kTrayPopupPaddingHorizontal,
+ kTrayPopupUserCardVerticalPadding,
+ kTrayPopupPaddingHorizontal));
+
+ views::TrayBubbleView* bubble_view =
+ owner_->system_tray()->GetSystemBubble()->bubble_view();
+ int max_card_width =
+ bubble_view->GetMaximumSize().width() -
+ (2 * kTrayPopupPaddingHorizontal + kTrayPopupPaddingBetweenItems);
+ if (logout_button_)
+ max_card_width -= logout_button_->GetPreferredSize().width();
+ user_card_view_ =
+ new UserCardView(login, max_card_width, multiprofile_index_);
+ bool clickable = IsMultiProfileSupportedAndUserActive() ||
+ IsMultiAccountSupportedAndUserActive();
+ if (clickable) {
+ // 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_) {
+ SetBorder(views::Border::CreateEmptyBorder(
+ kTrayPopupUserCardVerticalPadding,
+ kTrayPopupPaddingHorizontal - kTrayUserTileHoverBorderInset,
+ kTrayPopupUserCardVerticalPadding,
+ kTrayPopupPaddingHorizontal));
+ user_card_view_->SetBorder(views::Border::CreateEmptyBorder(
+ 0, kTrayUserTileHoverBorderInset, 0, 0));
+ }
+ if (!for_detailed_view_) {
+ user_card_view_ =
+ new ButtonFromView(user_card_view_, this, !multiprofile_index_);
+ } else {
+ // We want user card for detailed view to have exactly the same look
+ // as user card for default view. That's why we wrap it in a button
+ // without click listener and special hover behaviour.
+ user_card_view_ = new ButtonFromView(user_card_view_, NULL, false);
+ }
+ is_user_card_button_ = true;
+ }
+ AddChildViewAt(user_card_view_, 0);
+ // Card for locally managed user can consume more space than currently
+ // available. In that case we should increase system bubble's width.
+ if (login == user::LOGGED_IN_PUBLIC)
+ bubble_view->SetWidth(GetPreferredSize().width());
+}
+
+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..
+ 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(), wm::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.
+ add_menu_option_->SetAlwaysOnTop(true);
+ add_menu_option_->Show();
+
+ AddUserView* add_user_view =
+ new AddUserView(static_cast<ButtonFromView*>(user_card_view_));
+
+ const SessionStateDelegate* delegate =
+ Shell::GetInstance()->session_state_delegate();
+ add_user_disabled_ = delegate->NumberOfLoggedInUsers() >=
+ delegate->GetMaximumNumberOfLoggedInUsers();
+ ButtonFromView* button = add_user_disabled_
+ ? new ButtonFromView(add_user_view, NULL, false)
+ : new ButtonFromView(add_user_view, this, true);
+ button->ForceBorderVisible(true);
+ add_menu_option_->SetContentsView(button);
+
+ if (add_user_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();
+}
+
+} // namespace tray
+} // namespace ash
diff --git a/ash/system/user/user_view.h b/ash/system/user/user_view.h
new file mode 100644
index 0000000..e6657c6
--- /dev/null
+++ b/ash/system/user/user_view.h
@@ -0,0 +1,94 @@
+// Copyright 2014 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.
+
+#ifndef ASH_SYSTEM_USER_USER_VIEW_H_
+#define ASH_SYSTEM_USER_USER_VIEW_H_
+
+#include "ash/session_state_delegate.h"
+#include "ash/system/tray/tray_constants.h"
+#include "ash/system/user/login_status.h"
+#include "ash/system/user/tray_user.h"
+#include "base/macros.h"
+#include "ui/views/controls/button/button.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/mouse_watcher.h"
+#include "ui/views/view.h"
+
+namespace gfx {
+class Rect;
+class Size;
+}
+
+namespace ash {
+
+class PopupMessage;
+class SystemTrayItem;
+
+namespace tray {
+
+// The view of a user item in system tray bubble.
+class UserView : public views::View,
+ public views::ButtonListener,
+ public views::MouseWatcherListener {
+ public:
+ UserView(SystemTrayItem* owner,
+ ash::user::LoginStatus login,
+ MultiProfileIndex index,
+ bool for_detailed_view);
+ 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(user::LoginStatus login);
+
+ // Create the menu option to add another user. If |disabled| is set the user
+ // cannot actively click on the item.
+ void ToggleAddUserMenuOption();
+
+ 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 |ButtonFromView| - otherwise it is only
+ // a |UserCardView|.
+ bool is_user_card_button_;
+
+ 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_disabled_;
+
+ // True if this view will be used inside detailed view.
+ bool for_detailed_view_;
+
+ // The mouse watcher which takes care of out of window hover events.
+ scoped_ptr<views::MouseWatcher> mouse_watcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(UserView);
+};
+
+} // namespace tray
+} // namespace ash
+
+#endif // ASH_SYSTEM_USER_USER_VIEW_H_