avatar_menu_bubble_view.cc revision f8ee788a64d60abd8f2d742a5fdedde054ecd910
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// ProfileImageView -----------------------------------------------------------
199
200// A custom image view that ignores mouse events so that the parent can receive
201// them instead.
202class ProfileImageView : public views::ImageView {
203 public:
204  // views::View:
205  virtual bool CanProcessEventsWithinSubtree() const OVERRIDE;
206};
207
208bool 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.
218class 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
254ProfileItemView::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
302gfx::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
311void 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
349void ProfileItemView::OnMouseEntered(const ui::MouseEvent& event) {
350  views::CustomButton::OnMouseEntered(event);
351  OnHighlightStateChanged();
352}
353
354void ProfileItemView::OnMouseExited(const ui::MouseEvent& event) {
355  views::CustomButton::OnMouseExited(event);
356  OnHighlightStateChanged();
357}
358
359void ProfileItemView::OnFocus() {
360  views::CustomButton::OnFocus();
361  OnFocusStateChanged(true);
362}
363
364void ProfileItemView::OnBlur() {
365  views::CustomButton::OnBlur();
366  OnFocusStateChanged(false);
367}
368
369void 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
383void 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
390gfx::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
409bool 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.
423class 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
435ActionButtonView::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
483AvatarMenuBubbleView* AvatarMenuBubbleView::avatar_bubble_ = NULL;
484bool AvatarMenuBubbleView::close_on_deactivate_for_testing_ = true;
485
486// static
487void 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
509bool AvatarMenuBubbleView::IsShowing() {
510  return avatar_bubble_ != NULL;
511}
512
513// static
514void AvatarMenuBubbleView::Hide() {
515  if (IsShowing())
516    avatar_bubble_->GetWidget()->Close();
517}
518
519AvatarMenuBubbleView::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
539AvatarMenuBubbleView::~AvatarMenuBubbleView() {
540}
541
542gfx::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
593void 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
632bool 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
661void 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
686void 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
706gfx::Rect AvatarMenuBubbleView::GetAnchorRect() const {
707  return anchor_rect_;
708}
709
710void 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
717void AvatarMenuBubbleView::WindowClosing() {
718  DCHECK_EQ(avatar_bubble_, this);
719  avatar_bubble_ = NULL;
720}
721
722void 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
749void 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
791void 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
811void AvatarMenuBubbleView::SetBackgroundColors() {
812  for (size_t i = 0; i < item_views_.size(); ++i) {
813    item_views_[i]->OnHighlightStateChanged();
814  }
815}
816