| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ui/views/avatar_menu_bubble_view.h" |
| |
| #include <algorithm> |
| |
| #include "base/command_line.h" |
| #include "base/strings/string16.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/app/chrome_command_ids.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/profiles/avatar_menu_model.h" |
| #include "chrome/browser/profiles/profile_info_cache.h" |
| #include "chrome/browser/profiles/profile_info_util.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/signin/signin_manager.h" |
| #include "chrome/browser/signin/signin_manager_factory.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_commands.h" |
| #include "chrome/browser/ui/browser_list.h" |
| #include "chrome/browser/ui/browser_window.h" |
| #include "chrome/browser/ui/chrome_pages.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/url_constants.h" |
| #include "content/public/browser/page_navigator.h" |
| #include "content/public/browser/web_contents.h" |
| #include "grit/generated_resources.h" |
| #include "grit/theme_resources.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/image/canvas_image_source.h" |
| #include "ui/gfx/image/image.h" |
| #include "ui/views/controls/button/custom_button.h" |
| #include "ui/views/controls/button/image_button.h" |
| #include "ui/views/controls/button/label_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/separator.h" |
| #include "ui/views/layout/grid_layout.h" |
| #include "ui/views/layout/layout_constants.h" |
| #include "ui/views/widget/widget.h" |
| |
| namespace { |
| |
| const int kItemHeight = 44; |
| const int kItemMarginY = 4; |
| const int kIconMarginX = 6; |
| const int kSeparatorPaddingY = 5; |
| const int kMaxItemTextWidth = 200; |
| const SkColor kHighlightColor = 0xFFE3EDF6; |
| |
| inline int Round(double x) { |
| return static_cast<int>(x + 0.5); |
| } |
| |
| gfx::Rect GetCenteredAndScaledRect(int src_width, int src_height, |
| int dst_x, int dst_y, |
| int dst_width, int dst_height) { |
| int scaled_width; |
| int scaled_height; |
| if (src_width > src_height) { |
| scaled_width = std::min(src_width, dst_width); |
| float scale = static_cast<float>(scaled_width) / |
| static_cast<float>(src_width); |
| scaled_height = Round(src_height * scale); |
| } else { |
| scaled_height = std::min(src_height, dst_height); |
| float scale = static_cast<float>(scaled_height) / |
| static_cast<float>(src_height); |
| scaled_width = Round(src_width * scale); |
| } |
| int x = dst_x + (dst_width - scaled_width) / 2; |
| int y = dst_y + (dst_height - scaled_height) / 2; |
| return gfx::Rect(x, y, scaled_width, scaled_height); |
| } |
| |
| // BadgeImageSource ----------------------------------------------------------- |
| class BadgeImageSource: public gfx::CanvasImageSource { |
| public: |
| BadgeImageSource(const gfx::ImageSkia& icon, |
| const gfx::Size& icon_size, |
| const gfx::ImageSkia& badge); |
| |
| virtual ~BadgeImageSource(); |
| |
| // Overridden from CanvasImageSource: |
| virtual void Draw(gfx::Canvas* canvas) OVERRIDE; |
| |
| private: |
| gfx::Size ComputeSize(const gfx::ImageSkia& icon, |
| const gfx::Size& size, |
| const gfx::ImageSkia& badge); |
| |
| const gfx::ImageSkia icon_; |
| gfx::Size icon_size_; |
| const gfx::ImageSkia badge_; |
| |
| DISALLOW_COPY_AND_ASSIGN(BadgeImageSource); |
| }; |
| |
| BadgeImageSource::BadgeImageSource(const gfx::ImageSkia& icon, |
| const gfx::Size& icon_size, |
| const gfx::ImageSkia& badge) |
| : gfx::CanvasImageSource(ComputeSize(icon, icon_size, badge), false), |
| icon_(icon), |
| icon_size_(icon_size), |
| badge_(badge) { |
| } |
| |
| BadgeImageSource::~BadgeImageSource() { |
| } |
| |
| void BadgeImageSource::Draw(gfx::Canvas* canvas) { |
| canvas->DrawImageInt(icon_, 0, 0, icon_.width(), icon_.height(), 0, 0, |
| icon_size_.width(), icon_size_.height(), true); |
| canvas->DrawImageInt(badge_, size().width() - badge_.width(), |
| size().height() - badge_.height()); |
| } |
| |
| gfx::Size BadgeImageSource::ComputeSize(const gfx::ImageSkia& icon, |
| const gfx::Size& icon_size, |
| const gfx::ImageSkia& badge) { |
| const float kBadgeOverlapRatioX = 1.0f / 5.0f; |
| int width = icon_size.width() + badge.width() * kBadgeOverlapRatioX; |
| const float kBadgeOverlapRatioY = 1.0f / 3.0f; |
| int height = icon_size.height() + badge.height() * kBadgeOverlapRatioY; |
| return gfx::Size(width, height); |
| } |
| |
| // HighlightDelegate ---------------------------------------------------------- |
| |
| // Delegate to callback when the highlight state of a control changes. |
| class HighlightDelegate { |
| public: |
| virtual ~HighlightDelegate() {} |
| virtual void OnHighlightStateChanged() = 0; |
| virtual void OnFocusStateChanged(bool has_focus) = 0; |
| }; |
| |
| |
| // EditProfileLink ------------------------------------------------------------ |
| |
| // A custom Link control that forwards highlight state changes. We need to do |
| // this to make sure that the ProfileItemView looks highlighted even when |
| // the mouse is over this link. |
| class EditProfileLink : public views::Link { |
| public: |
| explicit EditProfileLink(const string16& title, |
| HighlightDelegate* delegate); |
| |
| virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE; |
| virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE; |
| virtual void OnFocus() OVERRIDE; |
| virtual void OnBlur() OVERRIDE; |
| |
| views::CustomButton::ButtonState state() { return state_; } |
| |
| private: |
| HighlightDelegate* delegate_; |
| views::CustomButton::ButtonState state_; |
| }; |
| |
| EditProfileLink::EditProfileLink(const string16& title, |
| HighlightDelegate* delegate) |
| : views::Link(title), |
| delegate_(delegate), |
| state_(views::CustomButton::STATE_NORMAL) { |
| } |
| |
| void EditProfileLink::OnMouseEntered(const ui::MouseEvent& event) { |
| views::Link::OnMouseEntered(event); |
| state_ = views::CustomButton::STATE_HOVERED; |
| delegate_->OnHighlightStateChanged(); |
| } |
| |
| void EditProfileLink::OnMouseExited(const ui::MouseEvent& event) { |
| views::Link::OnMouseExited(event); |
| state_ = views::CustomButton::STATE_NORMAL; |
| delegate_->OnHighlightStateChanged(); |
| } |
| |
| void EditProfileLink::OnFocus() { |
| views::Link::OnFocus(); |
| delegate_->OnFocusStateChanged(true); |
| } |
| |
| void EditProfileLink::OnBlur() { |
| views::Link::OnBlur(); |
| state_ = views::CustomButton::STATE_NORMAL; |
| delegate_->OnFocusStateChanged(false); |
| } |
| |
| |
| // ProfileImageView ----------------------------------------------------------- |
| |
| // A custom image view that ignores mouse events so that the parent can receive |
| // them instead. |
| class ProfileImageView : public views::ImageView { |
| public: |
| virtual bool HitTestRect(const gfx::Rect& rect) const OVERRIDE; |
| }; |
| |
| bool ProfileImageView::HitTestRect(const gfx::Rect& rect) const { |
| return false; |
| } |
| |
| } // namespace |
| |
| // ProfileItemView ------------------------------------------------------------ |
| |
| // Control that shows information about a single profile. |
| class ProfileItemView : public views::CustomButton, |
| public HighlightDelegate { |
| public: |
| ProfileItemView(const AvatarMenuModel::Item& item, |
| AvatarMenuBubbleView* parent); |
| |
| virtual gfx::Size GetPreferredSize() OVERRIDE; |
| virtual void Layout() OVERRIDE; |
| virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE; |
| virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE; |
| virtual void OnFocus() OVERRIDE; |
| virtual void OnBlur() OVERRIDE; |
| |
| virtual void OnHighlightStateChanged() OVERRIDE; |
| virtual void OnFocusStateChanged(bool has_focus) OVERRIDE; |
| |
| const AvatarMenuModel::Item& item() const { return item_; } |
| EditProfileLink* edit_link() { return edit_link_; } |
| |
| private: |
| gfx::ImageSkia GetBadgedIcon(const gfx::ImageSkia& icon); |
| |
| bool IsHighlighted(); |
| |
| AvatarMenuModel::Item item_; |
| AvatarMenuBubbleView* parent_; |
| views::ImageView* image_view_; |
| views::Label* name_label_; |
| views::Label* sync_state_label_; |
| EditProfileLink* edit_link_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ProfileItemView); |
| }; |
| |
| ProfileItemView::ProfileItemView(const AvatarMenuModel::Item& item, |
| AvatarMenuBubbleView* parent) |
| : views::CustomButton(parent), |
| item_(item), |
| parent_(parent) { |
| set_notify_enter_exit_on_child(true); |
| |
| image_view_ = new ProfileImageView(); |
| gfx::ImageSkia profile_icon = *item_.icon.ToImageSkia(); |
| if (item_.active || item_.signin_required) |
| image_view_->SetImage(GetBadgedIcon(profile_icon)); |
| else |
| image_view_->SetImage(profile_icon); |
| AddChildView(image_view_); |
| |
| // Add a label to show the profile name. |
| ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance(); |
| name_label_ = new views::Label(item_.name, |
| rb->GetFont(item_.active ? |
| ui::ResourceBundle::BoldFont : |
| ui::ResourceBundle::BaseFont)); |
| name_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| AddChildView(name_label_); |
| |
| // Add a label to show the sync state. |
| sync_state_label_ = new views::Label(item_.sync_state); |
| if (item_.signed_in) |
| sync_state_label_->SetElideBehavior(views::Label::ELIDE_AS_EMAIL); |
| sync_state_label_->SetFont(rb->GetFont(ui::ResourceBundle::SmallFont)); |
| sync_state_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| sync_state_label_->SetEnabled(false); |
| AddChildView(sync_state_label_); |
| |
| // Add an edit profile link. |
| edit_link_ = new EditProfileLink( |
| l10n_util::GetStringUTF16(IDS_PROFILES_EDIT_PROFILE_LINK), this); |
| edit_link_->set_listener(parent); |
| edit_link_->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| edit_link_->SetHasFocusBorder(true); |
| AddChildView(edit_link_); |
| |
| OnHighlightStateChanged(); |
| } |
| |
| gfx::Size ProfileItemView::GetPreferredSize() { |
| int text_width = std::max(name_label_->GetPreferredSize().width(), |
| sync_state_label_->GetPreferredSize().width()); |
| text_width = std::max(edit_link_->GetPreferredSize().width(), text_width); |
| text_width = std::min(kMaxItemTextWidth, text_width); |
| return gfx::Size(profiles::kAvatarIconWidth + kIconMarginX + text_width, |
| kItemHeight); |
| } |
| |
| void ProfileItemView::Layout() { |
| // Profile icon. |
| gfx::Rect icon_rect; |
| if (item_.active) { |
| // If this is the active item then the icon is already scaled and so |
| // just use the preferred size. |
| icon_rect.set_size(image_view_->GetPreferredSize()); |
| icon_rect.set_y((height() - icon_rect.height()) / 2); |
| } else { |
| const gfx::ImageSkia& icon = image_view_->GetImage(); |
| icon_rect = GetCenteredAndScaledRect(icon.width(), icon.height(), 0, 0, |
| profiles::kAvatarIconWidth, height()); |
| } |
| image_view_->SetBoundsRect(icon_rect); |
| |
| int label_x = profiles::kAvatarIconWidth + kIconMarginX; |
| int max_label_width = std::max(width() - label_x, 0); |
| gfx::Size name_size = name_label_->GetPreferredSize(); |
| name_size.set_width(std::min(name_size.width(), max_label_width)); |
| gfx::Size state_size = sync_state_label_->GetPreferredSize(); |
| state_size.set_width(std::min(state_size.width(), max_label_width)); |
| gfx::Size edit_size = edit_link_->GetPreferredSize(); |
| edit_size.set_width(std::min(edit_size.width(), max_label_width)); |
| |
| const int kNameStatePaddingY = 2; |
| int labels_height = name_size.height() + kNameStatePaddingY + |
| std::max(state_size.height(), edit_size.height()); |
| int y = (height() - labels_height) / 2; |
| name_label_->SetBounds(label_x, y, name_size.width(), name_size.height()); |
| |
| int bottom = y + labels_height; |
| sync_state_label_->SetBounds(label_x, bottom - state_size.height(), |
| state_size.width(), state_size.height()); |
| // The edit link overlaps the sync state label. |
| edit_link_->SetBounds(label_x, bottom - edit_size.height(), |
| edit_size.width(), edit_size.height()); |
| } |
| |
| void ProfileItemView::OnMouseEntered(const ui::MouseEvent& event) { |
| views::CustomButton::OnMouseEntered(event); |
| OnHighlightStateChanged(); |
| } |
| |
| void ProfileItemView::OnMouseExited(const ui::MouseEvent& event) { |
| views::CustomButton::OnMouseExited(event); |
| OnHighlightStateChanged(); |
| } |
| |
| void ProfileItemView::OnFocus() { |
| views::CustomButton::OnFocus(); |
| OnFocusStateChanged(true); |
| } |
| |
| void ProfileItemView::OnBlur() { |
| views::CustomButton::OnBlur(); |
| OnFocusStateChanged(false); |
| } |
| |
| void ProfileItemView::OnHighlightStateChanged() { |
| const SkColor color = IsHighlighted() ? kHighlightColor : parent_->color(); |
| set_background(views::Background::CreateSolidBackground(color)); |
| name_label_->SetBackgroundColor(color); |
| sync_state_label_->SetBackgroundColor(color); |
| edit_link_->SetBackgroundColor(color); |
| |
| bool show_edit = IsHighlighted() && item_.active; |
| sync_state_label_->SetVisible(!show_edit); |
| edit_link_->SetVisible(show_edit); |
| SchedulePaint(); |
| } |
| |
| void ProfileItemView::OnFocusStateChanged(bool has_focus) { |
| if (!has_focus && state() != views::CustomButton::STATE_DISABLED) |
| SetState(views::CustomButton::STATE_NORMAL); |
| OnHighlightStateChanged(); |
| } |
| |
| // static |
| gfx::ImageSkia ProfileItemView::GetBadgedIcon(const gfx::ImageSkia& icon) { |
| ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance(); |
| const gfx::ImageSkia* badge = NULL; |
| |
| if (item_.active) |
| badge = rb->GetImageSkiaNamed(IDR_PROFILE_SELECTED); |
| else if (item_.signin_required) // TODO(bcwhite): create new icon |
| badge = rb->GetImageSkiaNamed(IDR_OMNIBOX_HTTPS_VALID); |
| else |
| NOTREACHED(); // function should only be called if one of above is true |
| |
| gfx::Size icon_size = GetCenteredAndScaledRect(icon.width(), icon.height(), |
| 0, 0, profiles::kAvatarIconWidth, kItemHeight).size(); |
| gfx::CanvasImageSource* source = |
| new BadgeImageSource(icon, icon_size, *badge); |
| // ImageSkia takes ownership of |source|. |
| return gfx::ImageSkia(source, source->size()); |
| } |
| |
| bool ProfileItemView::IsHighlighted() { |
| return state() == views::CustomButton::STATE_PRESSED || |
| state() == views::CustomButton::STATE_HOVERED || |
| edit_link_->state() == views::CustomButton::STATE_PRESSED || |
| edit_link_->state() == views::CustomButton::STATE_HOVERED || |
| HasFocus() || |
| edit_link_->HasFocus(); |
| } |
| |
| |
| // ActionButtonView ----------------------------------------------------------- |
| |
| // A custom view that manages the "action" buttons at the bottom of the list |
| // of profiles. |
| class ActionButtonView : public views::View { |
| public: |
| ActionButtonView(views::ButtonListener* listener, Profile* profile); |
| |
| private: |
| views::LabelButton* manage_button_; |
| views::LabelButton* signout_button_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ActionButtonView); |
| }; |
| |
| |
| ActionButtonView::ActionButtonView(views::ButtonListener* listener, |
| Profile* profile) |
| : manage_button_(NULL), |
| signout_button_(NULL) { |
| std::string username; |
| SigninManagerBase* signin = |
| SigninManagerFactory::GetForProfile(profile); |
| if (signin != NULL) |
| username = signin->GetAuthenticatedUsername(); |
| |
| manage_button_ = new views::LabelButton( |
| listener, l10n_util::GetStringUTF16(IDS_PROFILES_MANAGE_PROFILES_BUTTON)); |
| manage_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON); |
| manage_button_->SetTooltipText( |
| l10n_util::GetStringUTF16(IDS_PROFILES_MANAGE_PROFILES_BUTTON_TIP)); |
| manage_button_->set_tag(IDS_PROFILES_MANAGE_PROFILES_BUTTON); |
| |
| signout_button_ = new views::LabelButton( |
| listener, l10n_util::GetStringUTF16(IDS_PROFILES_PROFILE_SIGNOUT_BUTTON)); |
| signout_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON); |
| if (username.empty()) { |
| signout_button_->SetTooltipText( |
| l10n_util::GetStringUTF16( |
| IDS_PROFILES_PROFILE_SIGNOUT_BUTTON_TIP_UNAVAILABLE)); |
| signout_button_->SetEnabled(false); |
| } else { |
| signout_button_->SetTooltipText( |
| l10n_util::GetStringFUTF16(IDS_PROFILES_PROFILE_SIGNOUT_BUTTON_TIP, |
| UTF8ToUTF16(username))); |
| } |
| signout_button_->set_tag(IDS_PROFILES_PROFILE_SIGNOUT_BUTTON); |
| |
| views::GridLayout* layout = new views::GridLayout(this); |
| views::ColumnSet* columns = layout->AddColumnSet(0); |
| columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL, 1, |
| views::GridLayout::USE_PREF, 0, 0); |
| columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL, 1, |
| views::GridLayout::USE_PREF, 0, 0); |
| layout->StartRow(0, 0); |
| layout->AddView(signout_button_); |
| layout->AddView(manage_button_); |
| SetLayoutManager(layout); |
| } |
| |
| |
| // AvatarMenuBubbleView ------------------------------------------------------- |
| |
| // static |
| AvatarMenuBubbleView* AvatarMenuBubbleView::avatar_bubble_ = NULL; |
| bool AvatarMenuBubbleView::close_on_deactivate_ = true; |
| |
| // static |
| void AvatarMenuBubbleView::ShowBubble( |
| views::View* anchor_view, |
| views::BubbleBorder::Arrow arrow, |
| views::BubbleBorder::BubbleAlignment border_alignment, |
| const gfx::Rect& anchor_rect, |
| Browser* browser) { |
| if (IsShowing()) |
| return; |
| |
| DCHECK(chrome::IsCommandEnabled(browser, IDC_SHOW_AVATAR_MENU)); |
| avatar_bubble_ = new AvatarMenuBubbleView( |
| anchor_view, arrow, anchor_rect, browser); |
| views::BubbleDelegateView::CreateBubble(avatar_bubble_); |
| avatar_bubble_->set_close_on_deactivate(close_on_deactivate_); |
| avatar_bubble_->SetBackgroundColors(); |
| avatar_bubble_->SetAlignment(border_alignment); |
| avatar_bubble_->GetWidget()->Show(); |
| } |
| |
| // static |
| bool AvatarMenuBubbleView::IsShowing() { |
| return avatar_bubble_ != NULL; |
| } |
| |
| // static |
| void AvatarMenuBubbleView::Hide() { |
| if (IsShowing()) |
| avatar_bubble_->GetWidget()->Close(); |
| } |
| |
| AvatarMenuBubbleView::AvatarMenuBubbleView( |
| views::View* anchor_view, |
| views::BubbleBorder::Arrow arrow, |
| const gfx::Rect& anchor_rect, |
| Browser* browser) |
| : BubbleDelegateView(anchor_view, arrow), |
| anchor_rect_(anchor_rect), |
| browser_(browser), |
| separator_(NULL), |
| buttons_view_(NULL), |
| managed_user_info_(NULL), |
| separator_switch_users_(NULL), |
| expanded_(false) { |
| avatar_menu_model_.reset(new AvatarMenuModel( |
| &g_browser_process->profile_manager()->GetProfileInfoCache(), |
| this, browser_)); |
| } |
| |
| AvatarMenuBubbleView::~AvatarMenuBubbleView() { |
| } |
| |
| gfx::Size AvatarMenuBubbleView::GetPreferredSize() { |
| const int kBubbleViewMinWidth = 175; |
| gfx::Size preferred_size(kBubbleViewMinWidth, 0); |
| for (size_t i = 0; i < item_views_.size(); ++i) { |
| gfx::Size size = item_views_[i]->GetPreferredSize(); |
| preferred_size.Enlarge(0, size.height() + kItemMarginY); |
| preferred_size.SetToMax(size); |
| } |
| |
| if (buttons_view_) { |
| preferred_size.Enlarge( |
| 0, kSeparatorPaddingY * 2 + separator_->GetPreferredSize().height()); |
| |
| gfx::Size buttons_size = buttons_view_->GetPreferredSize(); |
| preferred_size.Enlarge(0, buttons_size.height()); |
| preferred_size.SetToMax(buttons_size); |
| } |
| |
| |
| if (managed_user_info_) { |
| // First handle the switch profile link because it can still affect the |
| // preferred width. |
| gfx::Size size = switch_profile_link_->GetPreferredSize(); |
| preferred_size.Enlarge(0, size.height()); |
| preferred_size.SetToMax(size); |
| |
| // Add the height of the two separators. |
| preferred_size.Enlarge( |
| 0, |
| kSeparatorPaddingY * 4 + separator_->GetPreferredSize().height() * 2); |
| } |
| |
| const int kBubbleViewMaxWidth = 800; |
| preferred_size.SetToMin( |
| gfx::Size(kBubbleViewMaxWidth, preferred_size.height())); |
| |
| // We have to do this after the final width is calculated, since the label |
| // will wrap based on the width. |
| if (managed_user_info_) { |
| int remaining_width = |
| preferred_size.width() - icon_view_->GetPreferredSize().width() - |
| views::kRelatedControlSmallHorizontalSpacing; |
| preferred_size.Enlarge( |
| 0, |
| managed_user_info_->GetHeightForWidth(remaining_width) + kItemMarginY); |
| } |
| |
| return preferred_size; |
| } |
| |
| void AvatarMenuBubbleView::Layout() { |
| int y = 0; |
| for (size_t i = 0; i < item_views_.size(); ++i) { |
| views::CustomButton* item_view = item_views_[i]; |
| int item_height = item_view->GetPreferredSize().height(); |
| int item_width = width(); |
| item_view->SetBounds(0, y, item_width, item_height); |
| y += item_height + kItemMarginY; |
| } |
| |
| int separator_height; |
| if (buttons_view_ || managed_user_info_) { |
| separator_height = separator_->GetPreferredSize().height(); |
| y += kSeparatorPaddingY; |
| separator_->SetBounds(0, y, width(), separator_height); |
| y += kSeparatorPaddingY + separator_height; |
| } |
| |
| if (buttons_view_) { |
| buttons_view_->SetBounds(0, y, |
| width(), buttons_view_->GetPreferredSize().height()); |
| } else if (managed_user_info_) { |
| gfx::Size icon_size = icon_view_->GetPreferredSize(); |
| gfx::Rect icon_bounds(0, y, icon_size.width(), icon_size.height()); |
| icon_view_->SetBoundsRect(icon_bounds); |
| int info_width = width() - icon_bounds.right() - |
| views::kRelatedControlSmallHorizontalSpacing; |
| int height = managed_user_info_->GetHeightForWidth(info_width); |
| managed_user_info_->SetBounds( |
| icon_bounds.right() + views::kRelatedControlSmallHorizontalSpacing, |
| y, info_width, height); |
| y += height + kItemMarginY + kSeparatorPaddingY; |
| separator_switch_users_->SetBounds(0, y, width(), separator_height); |
| y += separator_height + kSeparatorPaddingY; |
| int link_height = switch_profile_link_->GetPreferredSize().height(); |
| switch_profile_link_->SetBounds(0, y, width(), link_height); |
| } |
| } |
| |
| bool AvatarMenuBubbleView::AcceleratorPressed( |
| const ui::Accelerator& accelerator) { |
| if (accelerator.key_code() != ui::VKEY_DOWN && |
| accelerator.key_code() != ui::VKEY_UP) |
| return BubbleDelegateView::AcceleratorPressed(accelerator); |
| |
| if (item_views_.empty()) |
| return true; |
| |
| // Find the currently focused item. Note that if there is no focused item, the |
| // code below correctly handles a |focus_index| of -1. |
| int focus_index = -1; |
| for (size_t i = 0; i < item_views_.size(); ++i) { |
| if (item_views_[i]->HasFocus()) { |
| focus_index = i; |
| break; |
| } |
| } |
| |
| // Moved the focus up or down by 1. |
| if (accelerator.key_code() == ui::VKEY_DOWN) |
| focus_index = (focus_index + 1) % item_views_.size(); |
| else |
| focus_index = ((focus_index > 0) ? focus_index : item_views_.size()) - 1; |
| item_views_[focus_index]->RequestFocus(); |
| |
| return true; |
| } |
| |
| void AvatarMenuBubbleView::ButtonPressed(views::Button* sender, |
| const ui::Event& event) { |
| if (sender->tag() == IDS_PROFILES_MANAGE_PROFILES_BUTTON) { |
| std::string subpage = chrome::kSearchUsersSubPage; |
| chrome::ShowSettingsSubPage(browser_, subpage); |
| return; |
| } else if (sender->tag() == IDS_PROFILES_PROFILE_SIGNOUT_BUTTON) { |
| avatar_menu_model_->BeginSignOut(); |
| return; |
| } |
| |
| for (size_t i = 0; i < item_views_.size(); ++i) { |
| ProfileItemView* item_view = item_views_[i]; |
| if (sender == item_view) { |
| // Clicking on the active profile shouldn't do anything. |
| if (!item_view->item().active) { |
| avatar_menu_model_->SwitchToProfile( |
| i, ui::DispositionFromEventFlags(event.flags()) == NEW_WINDOW); |
| } |
| break; |
| } |
| } |
| } |
| |
| void AvatarMenuBubbleView::LinkClicked(views::Link* source, int event_flags) { |
| if (source == buttons_view_) { |
| avatar_menu_model_->AddNewProfile(ProfileMetrics::ADD_NEW_USER_ICON); |
| return; |
| } |
| if (source == switch_profile_link_) { |
| expanded_ = true; |
| OnAvatarMenuModelChanged(avatar_menu_model_.get()); |
| return; |
| } |
| |
| for (size_t i = 0; i < item_views_.size(); ++i) { |
| ProfileItemView* item_view = item_views_[i]; |
| if (source == item_view->edit_link()) { |
| avatar_menu_model_->EditProfile(i); |
| return; |
| } |
| } |
| } |
| |
| gfx::Rect AvatarMenuBubbleView::GetAnchorRect() { |
| return anchor_rect_; |
| } |
| |
| void AvatarMenuBubbleView::Init() { |
| // Build the menu for the first time. |
| OnAvatarMenuModelChanged(avatar_menu_model_.get()); |
| AddAccelerator(ui::Accelerator(ui::VKEY_DOWN, ui::EF_NONE)); |
| AddAccelerator(ui::Accelerator(ui::VKEY_UP, ui::EF_NONE)); |
| } |
| |
| void AvatarMenuBubbleView::WindowClosing() { |
| DCHECK_EQ(avatar_bubble_, this); |
| avatar_bubble_ = NULL; |
| } |
| |
| void AvatarMenuBubbleView::InitMenuContents( |
| AvatarMenuModel* avatar_menu_model) { |
| for (size_t i = 0; i < avatar_menu_model->GetNumberOfItems(); ++i) { |
| const AvatarMenuModel::Item& item = avatar_menu_model->GetItemAt(i); |
| ProfileItemView* item_view = new ProfileItemView(item, this); |
| item_view->SetAccessibleName(l10n_util::GetStringFUTF16( |
| IDS_PROFILES_SWITCH_TO_PROFILE_ACCESSIBLE_NAME, item.name)); |
| item_view->set_focusable(true); |
| AddChildView(item_view); |
| item_views_.push_back(item_view); |
| } |
| |
| if (CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kNewProfileManagement)) { |
| separator_ = new views::Separator(views::Separator::HORIZONTAL); |
| AddChildView(separator_); |
| buttons_view_ = new ActionButtonView(this, browser_->profile()); |
| AddChildView(buttons_view_); |
| } else if (avatar_menu_model_->ShouldShowAddNewProfileLink()) { |
| views::Link* add_profile_link = new views::Link( |
| l10n_util::GetStringUTF16(IDS_PROFILES_CREATE_NEW_PROFILE_LINK)); |
| add_profile_link->set_listener(this); |
| add_profile_link->SetHorizontalAlignment(gfx::ALIGN_CENTER); |
| add_profile_link->SetBackgroundColor(color()); |
| separator_ = new views::Separator(views::Separator::HORIZONTAL); |
| AddChildView(separator_); |
| buttons_view_ = add_profile_link; |
| AddChildView(buttons_view_); |
| } |
| } |
| |
| void AvatarMenuBubbleView::InitManagedUserContents( |
| AvatarMenuModel* avatar_menu_model) { |
| // Show the profile of the managed user. |
| size_t active_index = avatar_menu_model->GetActiveProfileIndex(); |
| const AvatarMenuModel::Item& item = |
| avatar_menu_model->GetItemAt(active_index); |
| ProfileItemView* item_view = new ProfileItemView(item, this); |
| item_view->SetAccessibleName(l10n_util::GetStringFUTF16( |
| IDS_PROFILES_SWITCH_TO_PROFILE_ACCESSIBLE_NAME, item.name)); |
| item_views_.push_back(item_view); |
| AddChildView(item_view); |
| separator_ = new views::Separator(views::Separator::HORIZONTAL); |
| AddChildView(separator_); |
| |
| // Add information about managed users. |
| managed_user_info_ = |
| new views::Label(avatar_menu_model_->GetManagedUserInformation(), |
| ui::ResourceBundle::GetSharedInstance().GetFont( |
| ui::ResourceBundle::SmallFont)); |
| managed_user_info_->SetMultiLine(true); |
| managed_user_info_->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| managed_user_info_->SetBackgroundColor(color()); |
| AddChildView(managed_user_info_); |
| |
| // Add the managed user icon. |
| icon_view_ = new views::ImageView(); |
| icon_view_->SetImage(avatar_menu_model_->GetManagedUserIcon().ToImageSkia()); |
| AddChildView(icon_view_); |
| |
| // Add a link for switching profiles. |
| separator_switch_users_ = new views::Separator(views::Separator::HORIZONTAL); |
| AddChildView(separator_switch_users_); |
| switch_profile_link_ = new views::Link( |
| l10n_util::GetStringUTF16(IDS_PROFILES_SWITCH_PROFILE_LINK)); |
| switch_profile_link_->set_listener(this); |
| switch_profile_link_->SetHorizontalAlignment(gfx::ALIGN_CENTER); |
| switch_profile_link_->SetBackgroundColor(color()); |
| AddChildView(switch_profile_link_); |
| } |
| |
| void AvatarMenuBubbleView::OnAvatarMenuModelChanged( |
| AvatarMenuModel* avatar_menu_model) { |
| // Unset all our child view references and call RemoveAllChildViews() which |
| // will actually delete them. |
| buttons_view_ = NULL; |
| managed_user_info_ = NULL; |
| item_views_.clear(); |
| RemoveAllChildViews(true); |
| |
| if (avatar_menu_model_->GetManagedUserInformation().empty() || expanded_) |
| InitMenuContents(avatar_menu_model); |
| else |
| InitManagedUserContents(avatar_menu_model); |
| |
| // If the bubble has already been shown then resize and reposition the bubble. |
| Layout(); |
| if (GetBubbleFrameView()) |
| SizeToContents(); |
| } |
| |
| void AvatarMenuBubbleView::SetBackgroundColors() { |
| for (size_t i = 0; i < item_views_.size(); ++i) { |
| item_views_[i]->OnHighlightStateChanged(); |
| } |
| } |