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