diff options
author | dzhioev@chromium.org <dzhioev@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-04-10 13:17:17 +0000 |
---|---|---|
committer | dzhioev@chromium.org <dzhioev@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-04-10 13:17:17 +0000 |
commit | d7ffac75143b8ca0ba900844360f1c6583d36b0d (patch) | |
tree | 1c8d4210bd6786d695fa6cdb4910dc3dcdb95a40 /ash/system/user | |
parent | 1ddf5f18253195a96ff84e4eb2ac9428b7939da6 (diff) | |
download | chromium_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.cc | 227 | ||||
-rw-r--r-- | ash/system/user/accounts_detailed_view.h | 70 | ||||
-rw-r--r-- | ash/system/user/button_from_view.cc | 71 | ||||
-rw-r--r-- | ash/system/user/button_from_view.h | 55 | ||||
-rw-r--r-- | ash/system/user/config.cc | 36 | ||||
-rw-r--r-- | ash/system/user/config.h | 22 | ||||
-rw-r--r-- | ash/system/user/rounded_image_view.cc | 79 | ||||
-rw-r--r-- | ash/system/user/rounded_image_view.h | 55 | ||||
-rw-r--r-- | ash/system/user/tray_user.cc | 1085 | ||||
-rw-r--r-- | ash/system/user/tray_user.h | 3 | ||||
-rw-r--r-- | ash/system/user/user_accounts_delegate.cc | 27 | ||||
-rw-r--r-- | ash/system/user/user_accounts_delegate.h | 69 | ||||
-rw-r--r-- | ash/system/user/user_card_view.cc | 382 | ||||
-rw-r--r-- | ash/system/user/user_card_view.h | 44 | ||||
-rw-r--r-- | ash/system/user/user_view.cc | 490 | ||||
-rw-r--r-- | ash/system/user/user_view.h | 94 |
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_ |