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