Home | History | Annotate | Download | only in profiles
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/ui/views/profiles/avatar_menu_bubble_view.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "base/strings/string16.h"
     10 #include "base/strings/utf_string_conversions.h"
     11 #include "chrome/app/chrome_command_ids.h"
     12 #include "chrome/browser/browser_process.h"
     13 #include "chrome/browser/profiles/avatar_menu.h"
     14 #include "chrome/browser/profiles/profile_avatar_icon_util.h"
     15 #include "chrome/browser/profiles/profile_info_cache.h"
     16 #include "chrome/browser/profiles/profile_manager.h"
     17 #include "chrome/browser/profiles/profile_window.h"
     18 #include "chrome/browser/signin/signin_manager_factory.h"
     19 #include "chrome/browser/ui/browser.h"
     20 #include "chrome/browser/ui/browser_commands.h"
     21 #include "chrome/browser/ui/browser_list.h"
     22 #include "chrome/browser/ui/browser_window.h"
     23 #include "chrome/browser/ui/chrome_pages.h"
     24 #include "chrome/common/url_constants.h"
     25 #include "components/signin/core/browser/signin_manager.h"
     26 #include "components/signin/core/common/profile_management_switches.h"
     27 #include "content/public/browser/page_navigator.h"
     28 #include "content/public/browser/web_contents.h"
     29 #include "grit/generated_resources.h"
     30 #include "grit/theme_resources.h"
     31 #include "ui/base/l10n/l10n_util.h"
     32 #include "ui/base/resource/resource_bundle.h"
     33 #include "ui/gfx/canvas.h"
     34 #include "ui/gfx/image/canvas_image_source.h"
     35 #include "ui/gfx/image/image.h"
     36 #include "ui/views/controls/button/custom_button.h"
     37 #include "ui/views/controls/button/image_button.h"
     38 #include "ui/views/controls/button/label_button.h"
     39 #include "ui/views/controls/image_view.h"
     40 #include "ui/views/controls/label.h"
     41 #include "ui/views/controls/link.h"
     42 #include "ui/views/controls/separator.h"
     43 #include "ui/views/layout/grid_layout.h"
     44 #include "ui/views/layout/layout_constants.h"
     45 #include "ui/views/widget/widget.h"
     46 
     47 namespace {
     48 
     49 const int kItemHeight = 44;
     50 const int kItemMarginY = 4;
     51 const int kIconMarginX = 6;
     52 const int kSeparatorPaddingY = 5;
     53 const int kMaxItemTextWidth = 200;
     54 const SkColor kHighlightColor = 0xFFE3EDF6;
     55 
     56 inline int Round(double x) {
     57   return static_cast<int>(x + 0.5);
     58 }
     59 
     60 gfx::Rect GetCenteredAndScaledRect(int src_width, int src_height,
     61                                    int dst_x, int dst_y,
     62                                    int dst_width, int dst_height) {
     63   int scaled_width;
     64   int scaled_height;
     65   if (src_width > src_height) {
     66     scaled_width = std::min(src_width, dst_width);
     67     float scale = static_cast<float>(scaled_width) /
     68                   static_cast<float>(src_width);
     69     scaled_height = Round(src_height * scale);
     70   } else {
     71     scaled_height = std::min(src_height, dst_height);
     72     float scale = static_cast<float>(scaled_height) /
     73                   static_cast<float>(src_height);
     74     scaled_width = Round(src_width * scale);
     75   }
     76   int x = dst_x + (dst_width - scaled_width) / 2;
     77   int y = dst_y + (dst_height - scaled_height) / 2;
     78   return gfx::Rect(x, y, scaled_width, scaled_height);
     79 }
     80 
     81 // BadgeImageSource -----------------------------------------------------------
     82 class BadgeImageSource: public gfx::CanvasImageSource {
     83  public:
     84   BadgeImageSource(const gfx::ImageSkia& icon,
     85                    const gfx::Size& icon_size,
     86                    const gfx::ImageSkia& badge);
     87 
     88   virtual ~BadgeImageSource();
     89 
     90   // Overridden from CanvasImageSource:
     91   virtual void Draw(gfx::Canvas* canvas) OVERRIDE;
     92 
     93  private:
     94   gfx::Size ComputeSize(const gfx::ImageSkia& icon,
     95                         const gfx::Size& size,
     96                         const gfx::ImageSkia& badge);
     97 
     98   const gfx::ImageSkia icon_;
     99   gfx::Size icon_size_;
    100   const gfx::ImageSkia badge_;
    101 
    102   DISALLOW_COPY_AND_ASSIGN(BadgeImageSource);
    103 };
    104 
    105 BadgeImageSource::BadgeImageSource(const gfx::ImageSkia& icon,
    106                                    const gfx::Size& icon_size,
    107                                    const gfx::ImageSkia& badge)
    108     : gfx::CanvasImageSource(ComputeSize(icon, icon_size, badge), false),
    109       icon_(icon),
    110       icon_size_(icon_size),
    111       badge_(badge) {
    112 }
    113 
    114 BadgeImageSource::~BadgeImageSource() {
    115 }
    116 
    117 void BadgeImageSource::Draw(gfx::Canvas* canvas) {
    118   canvas->DrawImageInt(icon_, 0, 0, icon_.width(), icon_.height(), 0, 0,
    119                        icon_size_.width(), icon_size_.height(), true);
    120   canvas->DrawImageInt(badge_, size().width() - badge_.width(),
    121                        size().height() - badge_.height());
    122 }
    123 
    124 gfx::Size BadgeImageSource::ComputeSize(const gfx::ImageSkia& icon,
    125                                         const gfx::Size& icon_size,
    126                                         const gfx::ImageSkia& badge) {
    127   const float kBadgeOverlapRatioX = 1.0f / 5.0f;
    128   int width = icon_size.width() + badge.width() * kBadgeOverlapRatioX;
    129   const float kBadgeOverlapRatioY = 1.0f / 3.0f;
    130   int height = icon_size.height() + badge.height() * kBadgeOverlapRatioY;
    131   return gfx::Size(width, height);
    132 }
    133 
    134 // HighlightDelegate ----------------------------------------------------------
    135 
    136 // Delegate to callback when the highlight state of a control changes.
    137 class HighlightDelegate {
    138  public:
    139   virtual ~HighlightDelegate() {}
    140   virtual void OnHighlightStateChanged() = 0;
    141   virtual void OnFocusStateChanged(bool has_focus) = 0;
    142 };
    143 
    144 
    145 // EditProfileLink ------------------------------------------------------------
    146 
    147 // A custom Link control that forwards highlight state changes. We need to do
    148 // this to make sure that the ProfileItemView looks highlighted even when
    149 // the mouse is over this link.
    150 class EditProfileLink : public views::Link {
    151  public:
    152   explicit EditProfileLink(const base::string16& title,
    153                            HighlightDelegate* delegate);
    154 
    155   virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE;
    156   virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE;
    157   virtual void OnFocus() OVERRIDE;
    158   virtual void OnBlur() OVERRIDE;
    159 
    160   views::CustomButton::ButtonState state() { return state_; }
    161 
    162  private:
    163   HighlightDelegate* delegate_;
    164   views::CustomButton::ButtonState state_;
    165 };
    166 
    167 EditProfileLink::EditProfileLink(const base::string16& title,
    168                                  HighlightDelegate* delegate)
    169     : views::Link(title),
    170       delegate_(delegate),
    171       state_(views::CustomButton::STATE_NORMAL) {
    172 }
    173 
    174 void EditProfileLink::OnMouseEntered(const ui::MouseEvent& event) {
    175   views::Link::OnMouseEntered(event);
    176   state_ = views::CustomButton::STATE_HOVERED;
    177   delegate_->OnHighlightStateChanged();
    178 }
    179 
    180 void EditProfileLink::OnMouseExited(const ui::MouseEvent& event) {
    181   views::Link::OnMouseExited(event);
    182   state_ = views::CustomButton::STATE_NORMAL;
    183   delegate_->OnHighlightStateChanged();
    184 }
    185 
    186 void EditProfileLink::OnFocus() {
    187   views::Link::OnFocus();
    188   delegate_->OnFocusStateChanged(true);
    189 }
    190 
    191 void EditProfileLink::OnBlur() {
    192   views::Link::OnBlur();
    193   state_ = views::CustomButton::STATE_NORMAL;
    194   delegate_->OnFocusStateChanged(false);
    195 }
    196 
    197 
    198 // ProfileImageView -----------------------------------------------------------
    199 
    200 // A custom image view that ignores mouse events so that the parent can receive
    201 // them instead.
    202 class ProfileImageView : public views::ImageView {
    203  public:
    204   // views::View:
    205   virtual bool CanProcessEventsWithinSubtree() const OVERRIDE;
    206 };
    207 
    208 bool ProfileImageView::CanProcessEventsWithinSubtree() const {
    209   // Send events to the parent view for handling.
    210   return false;
    211 }
    212 
    213 }  // namespace
    214 
    215 // ProfileItemView ------------------------------------------------------------
    216 
    217 // Control that shows information about a single profile.
    218 class ProfileItemView : public views::CustomButton,
    219                         public HighlightDelegate {
    220  public:
    221   ProfileItemView(const AvatarMenu::Item& item,
    222                   AvatarMenuBubbleView* parent,
    223                   AvatarMenu* menu);
    224 
    225   virtual gfx::Size GetPreferredSize() const OVERRIDE;
    226   virtual void Layout() OVERRIDE;
    227   virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE;
    228   virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE;
    229   virtual void OnFocus() OVERRIDE;
    230   virtual void OnBlur() OVERRIDE;
    231 
    232   virtual void OnHighlightStateChanged() OVERRIDE;
    233   virtual void OnFocusStateChanged(bool has_focus) OVERRIDE;
    234 
    235   const AvatarMenu::Item& item() const { return item_; }
    236   EditProfileLink* edit_link() { return edit_link_; }
    237 
    238  private:
    239   gfx::ImageSkia GetBadgedIcon(const gfx::ImageSkia& icon);
    240 
    241   bool IsHighlighted();
    242 
    243   AvatarMenu::Item item_;
    244   AvatarMenuBubbleView* parent_;
    245   AvatarMenu* menu_;
    246   views::ImageView* image_view_;
    247   views::Label* name_label_;
    248   views::Label* sync_state_label_;
    249   EditProfileLink* edit_link_;
    250 
    251   DISALLOW_COPY_AND_ASSIGN(ProfileItemView);
    252 };
    253 
    254 ProfileItemView::ProfileItemView(const AvatarMenu::Item& item,
    255                                  AvatarMenuBubbleView* parent,
    256                                  AvatarMenu* menu)
    257     : views::CustomButton(parent),
    258       item_(item),
    259       parent_(parent),
    260       menu_(menu) {
    261   set_notify_enter_exit_on_child(true);
    262 
    263   image_view_ = new ProfileImageView();
    264   // GetSizedAvatarIcon will resize the icon in case it's too large.
    265   const gfx::ImageSkia profile_icon = *profiles::GetSizedAvatarIcon(item_.icon,
    266       false, profiles::kAvatarIconWidth, kItemHeight).ToImageSkia();
    267   if (item_.active || item_.signin_required)
    268     image_view_->SetImage(GetBadgedIcon(profile_icon));
    269   else
    270     image_view_->SetImage(profile_icon);
    271   AddChildView(image_view_);
    272 
    273   // Add a label to show the profile name.
    274   ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
    275   name_label_ = new views::Label(item_.name,
    276                                  rb->GetFontList(item_.active ?
    277                                                  ui::ResourceBundle::BoldFont :
    278                                                  ui::ResourceBundle::BaseFont));
    279   name_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    280   AddChildView(name_label_);
    281 
    282   // Add a label to show the sync state.
    283   sync_state_label_ = new views::Label(item_.sync_state);
    284   if (item_.signed_in)
    285     sync_state_label_->SetElideBehavior(gfx::ELIDE_EMAIL);
    286   sync_state_label_->SetFontList(
    287       rb->GetFontList(ui::ResourceBundle::SmallFont));
    288   sync_state_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    289   sync_state_label_->SetEnabled(false);
    290   AddChildView(sync_state_label_);
    291 
    292   // Add an edit profile link.
    293   edit_link_ = new EditProfileLink(
    294       l10n_util::GetStringUTF16(IDS_PROFILES_EDIT_PROFILE_LINK), this);
    295   edit_link_->set_listener(parent);
    296   edit_link_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    297   AddChildView(edit_link_);
    298 
    299   OnHighlightStateChanged();
    300 }
    301 
    302 gfx::Size ProfileItemView::GetPreferredSize() const {
    303   int text_width = std::max(name_label_->GetPreferredSize().width(),
    304                             sync_state_label_->GetPreferredSize().width());
    305   text_width = std::max(edit_link_->GetPreferredSize().width(), text_width);
    306   text_width = std::min(kMaxItemTextWidth, text_width);
    307   return gfx::Size(profiles::kAvatarIconWidth + kIconMarginX + text_width,
    308                    kItemHeight);
    309 }
    310 
    311 void ProfileItemView::Layout() {
    312   // Profile icon.
    313   gfx::Rect icon_rect;
    314   if (item_.active) {
    315     // If this is the active item then the icon is already scaled and so
    316     // just use the preferred size.
    317     icon_rect.set_size(image_view_->GetPreferredSize());
    318     icon_rect.set_y((height() - icon_rect.height()) / 2);
    319   } else {
    320     const gfx::ImageSkia& icon = image_view_->GetImage();
    321     icon_rect = GetCenteredAndScaledRect(icon.width(), icon.height(), 0, 0,
    322         profiles::kAvatarIconWidth, height());
    323   }
    324   image_view_->SetBoundsRect(icon_rect);
    325 
    326   int label_x = profiles::kAvatarIconWidth + kIconMarginX;
    327   int max_label_width = std::max(width() - label_x, 0);
    328   gfx::Size name_size = name_label_->GetPreferredSize();
    329   name_size.set_width(std::min(name_size.width(), max_label_width));
    330   gfx::Size state_size = sync_state_label_->GetPreferredSize();
    331   state_size.set_width(std::min(state_size.width(), max_label_width));
    332   gfx::Size edit_size = edit_link_->GetPreferredSize();
    333   edit_size.set_width(std::min(edit_size.width(), max_label_width));
    334 
    335   const int kNameStatePaddingY = 2;
    336   int labels_height = name_size.height() + kNameStatePaddingY +
    337       std::max(state_size.height(), edit_size.height());
    338   int y = (height() - labels_height) / 2;
    339   name_label_->SetBounds(label_x, y, name_size.width(), name_size.height());
    340 
    341   int bottom = y + labels_height;
    342   sync_state_label_->SetBounds(label_x, bottom - state_size.height(),
    343                                state_size.width(), state_size.height());
    344   // The edit link overlaps the sync state label.
    345   edit_link_->SetBounds(label_x, bottom - edit_size.height(),
    346                         edit_size.width(), edit_size.height());
    347 }
    348 
    349 void ProfileItemView::OnMouseEntered(const ui::MouseEvent& event) {
    350   views::CustomButton::OnMouseEntered(event);
    351   OnHighlightStateChanged();
    352 }
    353 
    354 void ProfileItemView::OnMouseExited(const ui::MouseEvent& event) {
    355   views::CustomButton::OnMouseExited(event);
    356   OnHighlightStateChanged();
    357 }
    358 
    359 void ProfileItemView::OnFocus() {
    360   views::CustomButton::OnFocus();
    361   OnFocusStateChanged(true);
    362 }
    363 
    364 void ProfileItemView::OnBlur() {
    365   views::CustomButton::OnBlur();
    366   OnFocusStateChanged(false);
    367 }
    368 
    369 void ProfileItemView::OnHighlightStateChanged() {
    370   const SkColor color = IsHighlighted() ? kHighlightColor : parent_->color();
    371   set_background(views::Background::CreateSolidBackground(color));
    372   name_label_->SetBackgroundColor(color);
    373   sync_state_label_->SetBackgroundColor(color);
    374   edit_link_->SetBackgroundColor(color);
    375 
    376   bool show_edit = IsHighlighted() && item_.active &&
    377       menu_->ShouldShowEditProfileLink();
    378   sync_state_label_->SetVisible(!show_edit);
    379   edit_link_->SetVisible(show_edit);
    380   SchedulePaint();
    381 }
    382 
    383 void ProfileItemView::OnFocusStateChanged(bool has_focus) {
    384   if (!has_focus && state() != views::CustomButton::STATE_DISABLED)
    385     SetState(views::CustomButton::STATE_NORMAL);
    386   OnHighlightStateChanged();
    387 }
    388 
    389 // static
    390 gfx::ImageSkia ProfileItemView::GetBadgedIcon(const gfx::ImageSkia& icon) {
    391   ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
    392   const gfx::ImageSkia* badge = NULL;
    393 
    394   if (item_.active)
    395     badge = rb->GetImageSkiaNamed(IDR_PROFILE_SELECTED);
    396   else if (item_.signin_required)  // TODO(bcwhite): create new icon
    397     badge = rb->GetImageSkiaNamed(IDR_OMNIBOX_HTTPS_VALID);
    398   else
    399     NOTREACHED();  // function should only be called if one of above is true
    400 
    401   gfx::Size icon_size = GetCenteredAndScaledRect(icon.width(), icon.height(),
    402       0, 0, profiles::kAvatarIconWidth, kItemHeight).size();
    403   gfx::CanvasImageSource* source =
    404       new BadgeImageSource(icon, icon_size, *badge);
    405   // ImageSkia takes ownership of |source|.
    406   return gfx::ImageSkia(source, source->size());
    407 }
    408 
    409 bool ProfileItemView::IsHighlighted() {
    410   return state() == views::CustomButton::STATE_PRESSED ||
    411          state() == views::CustomButton::STATE_HOVERED ||
    412          edit_link_->state() == views::CustomButton::STATE_PRESSED ||
    413          edit_link_->state() == views::CustomButton::STATE_HOVERED ||
    414          HasFocus() ||
    415          edit_link_->HasFocus();
    416 }
    417 
    418 
    419 // ActionButtonView -----------------------------------------------------------
    420 
    421 // A custom view that manages the "action" buttons at the bottom of the list
    422 // of profiles.
    423 class ActionButtonView : public views::View {
    424  public:
    425   ActionButtonView(views::ButtonListener* listener, Profile* profile);
    426 
    427  private:
    428   views::LabelButton* manage_button_;
    429   views::LabelButton* signout_button_;
    430 
    431   DISALLOW_COPY_AND_ASSIGN(ActionButtonView);
    432 };
    433 
    434 
    435 ActionButtonView::ActionButtonView(views::ButtonListener* listener,
    436                                    Profile* profile)
    437   : manage_button_(NULL),
    438     signout_button_(NULL) {
    439   std::string username;
    440   SigninManagerBase* signin =
    441       SigninManagerFactory::GetForProfile(profile);
    442   if (signin != NULL)
    443     username = signin->GetAuthenticatedUsername();
    444 
    445   manage_button_ = new views::LabelButton(
    446       listener, l10n_util::GetStringUTF16(IDS_PROFILES_MANAGE_PROFILES_BUTTON));
    447   manage_button_->SetStyle(views::Button::STYLE_BUTTON);
    448   manage_button_->SetTooltipText(
    449       l10n_util::GetStringUTF16(IDS_PROFILES_MANAGE_PROFILES_BUTTON_TIP));
    450   manage_button_->set_tag(IDS_PROFILES_MANAGE_PROFILES_BUTTON);
    451 
    452   signout_button_ = new views::LabelButton(
    453       listener, l10n_util::GetStringUTF16(IDS_PROFILES_PROFILE_SIGNOUT_BUTTON));
    454   signout_button_->SetStyle(views::Button::STYLE_BUTTON);
    455   if (username.empty()) {
    456     signout_button_->SetTooltipText(
    457         l10n_util::GetStringUTF16(
    458             IDS_PROFILES_PROFILE_SIGNOUT_BUTTON_TIP_UNAVAILABLE));
    459     signout_button_->SetEnabled(false);
    460   } else {
    461     signout_button_->SetTooltipText(
    462         l10n_util::GetStringFUTF16(IDS_PROFILES_PROFILE_SIGNOUT_BUTTON_TIP,
    463                                    base::UTF8ToUTF16(username)));
    464   }
    465   signout_button_->set_tag(IDS_PROFILES_PROFILE_SIGNOUT_BUTTON);
    466 
    467   views::GridLayout* layout = new views::GridLayout(this);
    468   views::ColumnSet* columns = layout->AddColumnSet(0);
    469   columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL, 1,
    470                      views::GridLayout::USE_PREF, 0, 0);
    471   columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL, 1,
    472                      views::GridLayout::USE_PREF, 0, 0);
    473   layout->StartRow(0, 0);
    474   layout->AddView(signout_button_);
    475   layout->AddView(manage_button_);
    476   SetLayoutManager(layout);
    477 }
    478 
    479 
    480 // AvatarMenuBubbleView -------------------------------------------------------
    481 
    482 // static
    483 AvatarMenuBubbleView* AvatarMenuBubbleView::avatar_bubble_ = NULL;
    484 bool AvatarMenuBubbleView::close_on_deactivate_for_testing_ = true;
    485 
    486 // static
    487 void AvatarMenuBubbleView::ShowBubble(
    488     views::View* anchor_view,
    489     views::BubbleBorder::Arrow arrow,
    490     views::BubbleBorder::ArrowPaintType arrow_paint_type,
    491     views::BubbleBorder::BubbleAlignment border_alignment,
    492     const gfx::Rect& anchor_rect,
    493     Browser* browser) {
    494   if (IsShowing())
    495     return;
    496 
    497   DCHECK(chrome::IsCommandEnabled(browser, IDC_SHOW_AVATAR_MENU));
    498   avatar_bubble_ = new AvatarMenuBubbleView(
    499       anchor_view, arrow, anchor_rect, browser);
    500   views::BubbleDelegateView::CreateBubble(avatar_bubble_);
    501   avatar_bubble_->set_close_on_deactivate(close_on_deactivate_for_testing_);
    502   avatar_bubble_->SetBackgroundColors();
    503   avatar_bubble_->SetAlignment(border_alignment);
    504   avatar_bubble_->SetArrowPaintType(arrow_paint_type);
    505   avatar_bubble_->GetWidget()->Show();
    506 }
    507 
    508 // static
    509 bool AvatarMenuBubbleView::IsShowing() {
    510   return avatar_bubble_ != NULL;
    511 }
    512 
    513 // static
    514 void AvatarMenuBubbleView::Hide() {
    515   if (IsShowing())
    516     avatar_bubble_->GetWidget()->Close();
    517 }
    518 
    519 AvatarMenuBubbleView::AvatarMenuBubbleView(
    520     views::View* anchor_view,
    521     views::BubbleBorder::Arrow arrow,
    522     const gfx::Rect& anchor_rect,
    523     Browser* browser)
    524     : BubbleDelegateView(anchor_view, arrow),
    525       anchor_rect_(anchor_rect),
    526       browser_(browser),
    527       separator_(NULL),
    528       buttons_view_(NULL),
    529       supervised_user_info_(NULL),
    530       separator_switch_users_(NULL),
    531       expanded_(false) {
    532   avatar_menu_.reset(new AvatarMenu(
    533       &g_browser_process->profile_manager()->GetProfileInfoCache(),
    534       this,
    535       browser_));
    536   avatar_menu_->RebuildMenu();
    537 }
    538 
    539 AvatarMenuBubbleView::~AvatarMenuBubbleView() {
    540 }
    541 
    542 gfx::Size AvatarMenuBubbleView::GetPreferredSize() const {
    543   const int kBubbleViewMinWidth = 175;
    544   gfx::Size preferred_size(kBubbleViewMinWidth, 0);
    545   for (size_t i = 0; i < item_views_.size(); ++i) {
    546     gfx::Size size = item_views_[i]->GetPreferredSize();
    547     preferred_size.Enlarge(0, size.height() + kItemMarginY);
    548     preferred_size.SetToMax(size);
    549   }
    550 
    551   if (buttons_view_) {
    552     preferred_size.Enlarge(
    553         0, kSeparatorPaddingY * 2 + separator_->GetPreferredSize().height());
    554 
    555     gfx::Size buttons_size = buttons_view_->GetPreferredSize();
    556     preferred_size.Enlarge(0, buttons_size.height());
    557     preferred_size.SetToMax(buttons_size);
    558   }
    559 
    560 
    561   if (supervised_user_info_) {
    562     // First handle the switch profile link because it can still affect the
    563     // preferred width.
    564     gfx::Size size = switch_profile_link_->GetPreferredSize();
    565     preferred_size.Enlarge(0, size.height());
    566     preferred_size.SetToMax(size);
    567 
    568     // Add the height of the two separators.
    569     preferred_size.Enlarge(
    570         0,
    571         kSeparatorPaddingY * 4 + separator_->GetPreferredSize().height() * 2);
    572   }
    573 
    574   const int kBubbleViewMaxWidth = 800;
    575   preferred_size.SetToMin(
    576       gfx::Size(kBubbleViewMaxWidth, preferred_size.height()));
    577 
    578   // We have to do this after the final width is calculated, since the label
    579   // will wrap based on the width.
    580   if (supervised_user_info_) {
    581     int remaining_width =
    582         preferred_size.width() - icon_view_->GetPreferredSize().width() -
    583         views::kRelatedControlSmallHorizontalSpacing;
    584     preferred_size.Enlarge(
    585         0,
    586         supervised_user_info_->GetHeightForWidth(remaining_width) +
    587             kItemMarginY);
    588   }
    589 
    590   return preferred_size;
    591 }
    592 
    593 void AvatarMenuBubbleView::Layout() {
    594   int y = 0;
    595   for (size_t i = 0; i < item_views_.size(); ++i) {
    596     views::CustomButton* item_view = item_views_[i];
    597     int item_height = item_view->GetPreferredSize().height();
    598     int item_width = width();
    599     item_view->SetBounds(0, y, item_width, item_height);
    600     y += item_height + kItemMarginY;
    601   }
    602 
    603   int separator_height;
    604   if (buttons_view_ || supervised_user_info_) {
    605     separator_height = separator_->GetPreferredSize().height();
    606     y += kSeparatorPaddingY;
    607     separator_->SetBounds(0, y, width(), separator_height);
    608     y += kSeparatorPaddingY + separator_height;
    609   }
    610 
    611   if (buttons_view_) {
    612     buttons_view_->SetBounds(0, y,
    613         width(), buttons_view_->GetPreferredSize().height());
    614   } else if (supervised_user_info_) {
    615     gfx::Size icon_size = icon_view_->GetPreferredSize();
    616     gfx::Rect icon_bounds(0, y, icon_size.width(), icon_size.height());
    617     icon_view_->SetBoundsRect(icon_bounds);
    618     int info_width = width() - icon_bounds.right() -
    619                      views::kRelatedControlSmallHorizontalSpacing;
    620     int height = supervised_user_info_->GetHeightForWidth(info_width);
    621     supervised_user_info_->SetBounds(
    622         icon_bounds.right() + views::kRelatedControlSmallHorizontalSpacing,
    623         y, info_width, height);
    624     y += height + kItemMarginY + kSeparatorPaddingY;
    625     separator_switch_users_->SetBounds(0, y, width(), separator_height);
    626     y += separator_height + kSeparatorPaddingY;
    627     int link_height = switch_profile_link_->GetPreferredSize().height();
    628     switch_profile_link_->SetBounds(0, y, width(), link_height);
    629   }
    630 }
    631 
    632 bool AvatarMenuBubbleView::AcceleratorPressed(
    633     const ui::Accelerator& accelerator) {
    634   if (accelerator.key_code() != ui::VKEY_DOWN &&
    635       accelerator.key_code() != ui::VKEY_UP)
    636     return BubbleDelegateView::AcceleratorPressed(accelerator);
    637 
    638   if (item_views_.empty())
    639     return true;
    640 
    641   // Find the currently focused item. Note that if there is no focused item, the
    642   // code below correctly handles a |focus_index| of -1.
    643   int focus_index = -1;
    644   for (size_t i = 0; i < item_views_.size(); ++i) {
    645     if (item_views_[i]->HasFocus()) {
    646       focus_index = i;
    647       break;
    648     }
    649   }
    650 
    651   // Moved the focus up or down by 1.
    652   if (accelerator.key_code() == ui::VKEY_DOWN)
    653     focus_index = (focus_index + 1) % item_views_.size();
    654   else
    655     focus_index = ((focus_index > 0) ? focus_index : item_views_.size()) - 1;
    656   item_views_[focus_index]->RequestFocus();
    657 
    658   return true;
    659 }
    660 
    661 void AvatarMenuBubbleView::ButtonPressed(views::Button* sender,
    662                                          const ui::Event& event) {
    663   if (sender->tag() == IDS_PROFILES_MANAGE_PROFILES_BUTTON) {
    664     std::string subpage = chrome::kSearchUsersSubPage;
    665     chrome::ShowSettingsSubPage(browser_, subpage);
    666     return;
    667   } else if (sender->tag() == IDS_PROFILES_PROFILE_SIGNOUT_BUTTON) {
    668     profiles::LockProfile(browser_->profile());
    669     return;
    670   }
    671 
    672   for (size_t i = 0; i < item_views_.size(); ++i) {
    673     ProfileItemView* item_view = item_views_[i];
    674     if (sender == item_view) {
    675       // Clicking on the active profile shouldn't do anything.
    676       if (!item_view->item().active) {
    677         avatar_menu_->SwitchToProfile(
    678             i, ui::DispositionFromEventFlags(event.flags()) == NEW_WINDOW,
    679             ProfileMetrics::SWITCH_PROFILE_ICON);
    680       }
    681       break;
    682     }
    683   }
    684 }
    685 
    686 void AvatarMenuBubbleView::LinkClicked(views::Link* source, int event_flags) {
    687   if (source == buttons_view_) {
    688     avatar_menu_->AddNewProfile(ProfileMetrics::ADD_NEW_USER_ICON);
    689     return;
    690   }
    691   if (source == switch_profile_link_) {
    692     expanded_ = true;
    693     OnAvatarMenuChanged(avatar_menu_.get());
    694     return;
    695   }
    696 
    697   for (size_t i = 0; i < item_views_.size(); ++i) {
    698     ProfileItemView* item_view = item_views_[i];
    699     if (source == item_view->edit_link()) {
    700       avatar_menu_->EditProfile(i);
    701       return;
    702     }
    703   }
    704 }
    705 
    706 gfx::Rect AvatarMenuBubbleView::GetAnchorRect() const {
    707   return anchor_rect_;
    708 }
    709 
    710 void AvatarMenuBubbleView::Init() {
    711   // Build the menu for the first time.
    712   OnAvatarMenuChanged(avatar_menu_.get());
    713   AddAccelerator(ui::Accelerator(ui::VKEY_DOWN, ui::EF_NONE));
    714   AddAccelerator(ui::Accelerator(ui::VKEY_UP, ui::EF_NONE));
    715 }
    716 
    717 void AvatarMenuBubbleView::WindowClosing() {
    718   DCHECK_EQ(avatar_bubble_, this);
    719   avatar_bubble_ = NULL;
    720 }
    721 
    722 void AvatarMenuBubbleView::InitMenuContents(
    723     AvatarMenu* avatar_menu) {
    724   for (size_t i = 0; i < avatar_menu->GetNumberOfItems(); ++i) {
    725     const AvatarMenu::Item& item = avatar_menu->GetItemAt(i);
    726     ProfileItemView* item_view = new ProfileItemView(item,
    727                                                      this,
    728                                                      avatar_menu_.get());
    729     item_view->SetAccessibleName(l10n_util::GetStringFUTF16(
    730         IDS_PROFILES_SWITCH_TO_PROFILE_ACCESSIBLE_NAME, item.name));
    731     item_view->SetFocusable(true);
    732     AddChildView(item_view);
    733     item_views_.push_back(item_view);
    734   }
    735 
    736   if (avatar_menu_->ShouldShowAddNewProfileLink()) {
    737     views::Link* add_profile_link = new views::Link(
    738         l10n_util::GetStringUTF16(IDS_PROFILES_CREATE_NEW_PROFILE_LINK));
    739     add_profile_link->set_listener(this);
    740     add_profile_link->SetHorizontalAlignment(gfx::ALIGN_CENTER);
    741     add_profile_link->SetBackgroundColor(color());
    742     separator_ = new views::Separator(views::Separator::HORIZONTAL);
    743     AddChildView(separator_);
    744     buttons_view_ = add_profile_link;
    745     AddChildView(buttons_view_);
    746   }
    747 }
    748 
    749 void AvatarMenuBubbleView::InitSupervisedUserContents(
    750     AvatarMenu* avatar_menu) {
    751   // Show the profile of the supervised user.
    752   size_t active_index = avatar_menu->GetActiveProfileIndex();
    753   const AvatarMenu::Item& item =
    754       avatar_menu->GetItemAt(active_index);
    755   ProfileItemView* item_view = new ProfileItemView(item,
    756                                                    this,
    757                                                    avatar_menu_.get());
    758   item_view->SetAccessibleName(l10n_util::GetStringFUTF16(
    759       IDS_PROFILES_SWITCH_TO_PROFILE_ACCESSIBLE_NAME, item.name));
    760   item_views_.push_back(item_view);
    761   AddChildView(item_view);
    762   separator_ = new views::Separator(views::Separator::HORIZONTAL);
    763   AddChildView(separator_);
    764 
    765   // Add information about supervised users.
    766   supervised_user_info_ =
    767       new views::Label(avatar_menu_->GetSupervisedUserInformation(),
    768                        ui::ResourceBundle::GetSharedInstance().GetFontList(
    769                            ui::ResourceBundle::SmallFont));
    770   supervised_user_info_->SetMultiLine(true);
    771   supervised_user_info_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    772   supervised_user_info_->SetBackgroundColor(color());
    773   AddChildView(supervised_user_info_);
    774 
    775   // Add the supervised user icon.
    776   icon_view_ = new views::ImageView();
    777   icon_view_->SetImage(avatar_menu_->GetSupervisedUserIcon().ToImageSkia());
    778   AddChildView(icon_view_);
    779 
    780   // Add a link for switching profiles.
    781   separator_switch_users_ = new views::Separator(views::Separator::HORIZONTAL);
    782   AddChildView(separator_switch_users_);
    783   switch_profile_link_ = new views::Link(
    784       l10n_util::GetStringUTF16(IDS_PROFILES_SWITCH_PROFILE_LINK));
    785   switch_profile_link_->set_listener(this);
    786   switch_profile_link_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
    787   switch_profile_link_->SetBackgroundColor(color());
    788   AddChildView(switch_profile_link_);
    789 }
    790 
    791 void AvatarMenuBubbleView::OnAvatarMenuChanged(
    792     AvatarMenu* avatar_menu) {
    793   // Unset all our child view references and call RemoveAllChildViews() which
    794   // will actually delete them.
    795   buttons_view_ = NULL;
    796   supervised_user_info_ = NULL;
    797   item_views_.clear();
    798   RemoveAllChildViews(true);
    799 
    800   if (avatar_menu_->GetSupervisedUserInformation().empty() || expanded_)
    801     InitMenuContents(avatar_menu);
    802   else
    803     InitSupervisedUserContents(avatar_menu);
    804 
    805   // If the bubble has already been shown then resize and reposition the bubble.
    806   Layout();
    807   if (GetBubbleFrameView())
    808     SizeToContents();
    809 }
    810 
    811 void AvatarMenuBubbleView::SetBackgroundColors() {
    812   for (size_t i = 0; i < item_views_.size(); ++i) {
    813     item_views_[i]->OnHighlightStateChanged();
    814   }
    815 }
    816