Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(4951)

Unified Diff: ash/system/user/user_view.cc

Issue 210903003: Implemented system tray UI for new account management. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Merge conflicts resolved. Created 6 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « ash/system/user/user_view.h ('k') | ash/test/test_session_state_delegate.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: ash/system/user/user_view.cc
diff --git a/ash/system/user/user_view.cc b/ash/system/user/user_view.cc
new file mode 100644
index 0000000000000000000000000000000000000000..474316361fbabba5e9c02df006ed3bc076186e49
--- /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
« no previous file with comments | « ash/system/user/user_view.h ('k') | ash/test/test_session_state_delegate.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698