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