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