• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "chrome/grit/generated_resources.h"
26 #include "components/signin/core/browser/signin_manager.h"
27 #include "components/signin/core/common/profile_management_switches.h"
28 #include "content/public/browser/page_navigator.h"
29 #include "content/public/browser/web_contents.h"
30 #include "grit/theme_resources.h"
31 #include "ui/base/l10n/l10n_util.h"
32 #include "ui/base/resource/resource_bundle.h"
33 #include "ui/gfx/canvas.h"
34 #include "ui/gfx/image/canvas_image_source.h"
35 #include "ui/gfx/image/image.h"
36 #include "ui/views/controls/button/custom_button.h"
37 #include "ui/views/controls/button/image_button.h"
38 #include "ui/views/controls/button/label_button.h"
39 #include "ui/views/controls/image_view.h"
40 #include "ui/views/controls/label.h"
41 #include "ui/views/controls/link.h"
42 #include "ui/views/controls/separator.h"
43 #include "ui/views/layout/grid_layout.h"
44 #include "ui/views/layout/layout_constants.h"
45 #include "ui/views/widget/widget.h"
46 
47 namespace {
48 
49 const int kItemHeight = 44;
50 const int kItemMarginY = 4;
51 const int kIconMarginX = 6;
52 const int kSeparatorPaddingY = 5;
53 const int kMaxItemTextWidth = 200;
54 const SkColor kHighlightColor = 0xFFE3EDF6;
55 
Round(double x)56 inline int Round(double x) {
57   return static_cast<int>(x + 0.5);
58 }
59 
GetCenteredAndScaledRect(int src_width,int src_height,int dst_x,int dst_y,int dst_width,int dst_height)60 gfx::Rect GetCenteredAndScaledRect(int src_width, int src_height,
61                                    int dst_x, int dst_y,
62                                    int dst_width, int dst_height) {
63   int scaled_width;
64   int scaled_height;
65   if (src_width > src_height) {
66     scaled_width = std::min(src_width, dst_width);
67     float scale = static_cast<float>(scaled_width) /
68                   static_cast<float>(src_width);
69     scaled_height = Round(src_height * scale);
70   } else {
71     scaled_height = std::min(src_height, dst_height);
72     float scale = static_cast<float>(scaled_height) /
73                   static_cast<float>(src_height);
74     scaled_width = Round(src_width * scale);
75   }
76   int x = dst_x + (dst_width - scaled_width) / 2;
77   int y = dst_y + (dst_height - scaled_height) / 2;
78   return gfx::Rect(x, y, scaled_width, scaled_height);
79 }
80 
81 // BadgeImageSource -----------------------------------------------------------
82 class BadgeImageSource: public gfx::CanvasImageSource {
83  public:
84   BadgeImageSource(const gfx::ImageSkia& icon,
85                    const gfx::Size& icon_size,
86                    const gfx::ImageSkia& badge);
87 
88   virtual ~BadgeImageSource();
89 
90   // Overridden from CanvasImageSource:
91   virtual void Draw(gfx::Canvas* canvas) OVERRIDE;
92 
93  private:
94   gfx::Size ComputeSize(const gfx::ImageSkia& icon,
95                         const gfx::Size& size,
96                         const gfx::ImageSkia& badge);
97 
98   const gfx::ImageSkia icon_;
99   gfx::Size icon_size_;
100   const gfx::ImageSkia badge_;
101 
102   DISALLOW_COPY_AND_ASSIGN(BadgeImageSource);
103 };
104 
BadgeImageSource(const gfx::ImageSkia & icon,const gfx::Size & icon_size,const gfx::ImageSkia & badge)105 BadgeImageSource::BadgeImageSource(const gfx::ImageSkia& icon,
106                                    const gfx::Size& icon_size,
107                                    const gfx::ImageSkia& badge)
108     : gfx::CanvasImageSource(ComputeSize(icon, icon_size, badge), false),
109       icon_(icon),
110       icon_size_(icon_size),
111       badge_(badge) {
112 }
113 
~BadgeImageSource()114 BadgeImageSource::~BadgeImageSource() {
115 }
116 
Draw(gfx::Canvas * canvas)117 void BadgeImageSource::Draw(gfx::Canvas* canvas) {
118   canvas->DrawImageInt(icon_, 0, 0, icon_.width(), icon_.height(), 0, 0,
119                        icon_size_.width(), icon_size_.height(), true);
120   canvas->DrawImageInt(badge_, size().width() - badge_.width(),
121                        size().height() - badge_.height());
122 }
123 
ComputeSize(const gfx::ImageSkia & icon,const gfx::Size & icon_size,const gfx::ImageSkia & badge)124 gfx::Size BadgeImageSource::ComputeSize(const gfx::ImageSkia& icon,
125                                         const gfx::Size& icon_size,
126                                         const gfx::ImageSkia& badge) {
127   const float kBadgeOverlapRatioX = 1.0f / 5.0f;
128   int width = icon_size.width() + badge.width() * kBadgeOverlapRatioX;
129   const float kBadgeOverlapRatioY = 1.0f / 3.0f;
130   int height = icon_size.height() + badge.height() * kBadgeOverlapRatioY;
131   return gfx::Size(width, height);
132 }
133 
134 // HighlightDelegate ----------------------------------------------------------
135 
136 // Delegate to callback when the highlight state of a control changes.
137 class HighlightDelegate {
138  public:
~HighlightDelegate()139   virtual ~HighlightDelegate() {}
140   virtual void OnHighlightStateChanged() = 0;
141   virtual void OnFocusStateChanged(bool has_focus) = 0;
142 };
143 
144 
145 // EditProfileLink ------------------------------------------------------------
146 
147 // A custom Link control that forwards highlight state changes. We need to do
148 // this to make sure that the ProfileItemView looks highlighted even when
149 // the mouse is over this link.
150 class EditProfileLink : public views::Link {
151  public:
152   explicit EditProfileLink(const base::string16& title,
153                            HighlightDelegate* delegate);
154 
155   virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE;
156   virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE;
157   virtual void OnFocus() OVERRIDE;
158   virtual void OnBlur() OVERRIDE;
159 
state()160   views::CustomButton::ButtonState state() { return state_; }
161 
162  private:
163   HighlightDelegate* delegate_;
164   views::CustomButton::ButtonState state_;
165 };
166 
EditProfileLink(const base::string16 & title,HighlightDelegate * delegate)167 EditProfileLink::EditProfileLink(const base::string16& title,
168                                  HighlightDelegate* delegate)
169     : views::Link(title),
170       delegate_(delegate),
171       state_(views::CustomButton::STATE_NORMAL) {
172 }
173 
OnMouseEntered(const ui::MouseEvent & event)174 void EditProfileLink::OnMouseEntered(const ui::MouseEvent& event) {
175   views::Link::OnMouseEntered(event);
176   state_ = views::CustomButton::STATE_HOVERED;
177   delegate_->OnHighlightStateChanged();
178 }
179 
OnMouseExited(const ui::MouseEvent & event)180 void EditProfileLink::OnMouseExited(const ui::MouseEvent& event) {
181   views::Link::OnMouseExited(event);
182   state_ = views::CustomButton::STATE_NORMAL;
183   delegate_->OnHighlightStateChanged();
184 }
185 
OnFocus()186 void EditProfileLink::OnFocus() {
187   views::Link::OnFocus();
188   delegate_->OnFocusStateChanged(true);
189 }
190 
OnBlur()191 void 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.
203 class 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 
item() const220   const AvatarMenu::Item& item() const { return item_; }
edit_link()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 
ProfileItemView(const AvatarMenu::Item & item,AvatarMenuBubbleView * parent,AvatarMenu * menu)239 ProfileItemView::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 
GetPreferredSize() const291 gfx::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 
Layout()300 void 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 
OnMouseEntered(const ui::MouseEvent & event)338 void ProfileItemView::OnMouseEntered(const ui::MouseEvent& event) {
339   views::CustomButton::OnMouseEntered(event);
340   OnHighlightStateChanged();
341 }
342 
OnMouseExited(const ui::MouseEvent & event)343 void ProfileItemView::OnMouseExited(const ui::MouseEvent& event) {
344   views::CustomButton::OnMouseExited(event);
345   OnHighlightStateChanged();
346 }
347 
OnFocus()348 void ProfileItemView::OnFocus() {
349   views::CustomButton::OnFocus();
350   OnFocusStateChanged(true);
351 }
352 
OnBlur()353 void ProfileItemView::OnBlur() {
354   views::CustomButton::OnBlur();
355   OnFocusStateChanged(false);
356 }
357 
OnHighlightStateChanged()358 void 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 
OnFocusStateChanged(bool has_focus)372 void 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
GetBadgedIcon(const gfx::ImageSkia & icon)379 gfx::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 
IsHighlighted()398 bool 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.
412 class 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 
ActionButtonView(views::ButtonListener * listener,Profile * profile)424 ActionButtonView::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
472 AvatarMenuBubbleView* AvatarMenuBubbleView::avatar_bubble_ = NULL;
473 bool AvatarMenuBubbleView::close_on_deactivate_for_testing_ = true;
474 
475 // static
ShowBubble(views::View * anchor_view,views::BubbleBorder::Arrow arrow,views::BubbleBorder::ArrowPaintType arrow_paint_type,views::BubbleBorder::BubbleAlignment border_alignment,const gfx::Rect & anchor_rect,Browser * browser)476 void 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
IsShowing()498 bool AvatarMenuBubbleView::IsShowing() {
499   return avatar_bubble_ != NULL;
500 }
501 
502 // static
Hide()503 void AvatarMenuBubbleView::Hide() {
504   if (IsShowing())
505     avatar_bubble_->GetWidget()->Close();
506 }
507 
AvatarMenuBubbleView(views::View * anchor_view,views::BubbleBorder::Arrow arrow,const gfx::Rect & anchor_rect,Browser * browser)508 AvatarMenuBubbleView::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       switch_profile_link_(nullptr),
521       expanded_(false) {
522   avatar_menu_.reset(new AvatarMenu(
523       &g_browser_process->profile_manager()->GetProfileInfoCache(),
524       this,
525       browser_));
526   avatar_menu_->RebuildMenu();
527 }
528 
~AvatarMenuBubbleView()529 AvatarMenuBubbleView::~AvatarMenuBubbleView() {
530 }
531 
GetPreferredSize() const532 gfx::Size AvatarMenuBubbleView::GetPreferredSize() const {
533   const int kBubbleViewMinWidth = 175;
534   gfx::Size preferred_size(kBubbleViewMinWidth, 0);
535   for (size_t i = 0; i < item_views_.size(); ++i) {
536     gfx::Size size = item_views_[i]->GetPreferredSize();
537     preferred_size.Enlarge(0, size.height() + kItemMarginY);
538     preferred_size.SetToMax(size);
539   }
540 
541   if (buttons_view_) {
542     preferred_size.Enlarge(
543         0, kSeparatorPaddingY * 2 + separator_->GetPreferredSize().height());
544 
545     gfx::Size buttons_size = buttons_view_->GetPreferredSize();
546     preferred_size.Enlarge(0, buttons_size.height());
547     preferred_size.SetToMax(buttons_size);
548   }
549 
550 
551   if (supervised_user_info_) {
552     // First handle the switch profile link because it can still affect the
553     // preferred width.
554     gfx::Size size = switch_profile_link_->GetPreferredSize();
555     preferred_size.Enlarge(0, size.height());
556     preferred_size.SetToMax(size);
557 
558     // Add the height of the two separators.
559     preferred_size.Enlarge(
560         0,
561         kSeparatorPaddingY * 4 + separator_->GetPreferredSize().height() * 2);
562   }
563 
564   const int kBubbleViewMaxWidth = 800;
565   preferred_size.SetToMin(
566       gfx::Size(kBubbleViewMaxWidth, preferred_size.height()));
567 
568   // We have to do this after the final width is calculated, since the label
569   // will wrap based on the width.
570   if (supervised_user_info_) {
571     int remaining_width =
572         preferred_size.width() - icon_view_->GetPreferredSize().width() -
573         views::kRelatedControlSmallHorizontalSpacing;
574     preferred_size.Enlarge(
575         0,
576         supervised_user_info_->GetHeightForWidth(remaining_width) +
577             kItemMarginY);
578   }
579 
580   return preferred_size;
581 }
582 
Layout()583 void AvatarMenuBubbleView::Layout() {
584   int y = 0;
585   for (size_t i = 0; i < item_views_.size(); ++i) {
586     views::CustomButton* item_view = item_views_[i];
587     int item_height = item_view->GetPreferredSize().height();
588     int item_width = width();
589     item_view->SetBounds(0, y, item_width, item_height);
590     y += item_height + kItemMarginY;
591   }
592 
593   int separator_height = 0;
594   if (buttons_view_ || supervised_user_info_) {
595     separator_height = separator_->GetPreferredSize().height();
596     y += kSeparatorPaddingY;
597     separator_->SetBounds(0, y, width(), separator_height);
598     y += kSeparatorPaddingY + separator_height;
599   }
600 
601   if (buttons_view_) {
602     buttons_view_->SetBounds(0, y,
603         width(), buttons_view_->GetPreferredSize().height());
604   } else if (supervised_user_info_) {
605     gfx::Size icon_size = icon_view_->GetPreferredSize();
606     gfx::Rect icon_bounds(0, y, icon_size.width(), icon_size.height());
607     icon_view_->SetBoundsRect(icon_bounds);
608     int info_width = width() - icon_bounds.right() -
609                      views::kRelatedControlSmallHorizontalSpacing;
610     int height = supervised_user_info_->GetHeightForWidth(info_width);
611     supervised_user_info_->SetBounds(
612         icon_bounds.right() + views::kRelatedControlSmallHorizontalSpacing,
613         y, info_width, height);
614     y += height + kItemMarginY + kSeparatorPaddingY;
615     separator_switch_users_->SetBounds(0, y, width(), separator_height);
616     y += separator_height + kSeparatorPaddingY;
617     int link_height = switch_profile_link_->GetPreferredSize().height();
618     switch_profile_link_->SetBounds(0, y, width(), link_height);
619   }
620 }
621 
AcceleratorPressed(const ui::Accelerator & accelerator)622 bool AvatarMenuBubbleView::AcceleratorPressed(
623     const ui::Accelerator& accelerator) {
624   if (accelerator.key_code() != ui::VKEY_DOWN &&
625       accelerator.key_code() != ui::VKEY_UP)
626     return BubbleDelegateView::AcceleratorPressed(accelerator);
627 
628   if (item_views_.empty())
629     return true;
630 
631   // Find the currently focused item. Note that if there is no focused item, the
632   // code below correctly handles a |focus_index| of -1.
633   int focus_index = -1;
634   for (size_t i = 0; i < item_views_.size(); ++i) {
635     if (item_views_[i]->HasFocus()) {
636       focus_index = i;
637       break;
638     }
639   }
640 
641   // Moved the focus up or down by 1.
642   if (accelerator.key_code() == ui::VKEY_DOWN)
643     focus_index = (focus_index + 1) % item_views_.size();
644   else
645     focus_index = ((focus_index > 0) ? focus_index : item_views_.size()) - 1;
646   item_views_[focus_index]->RequestFocus();
647 
648   return true;
649 }
650 
ButtonPressed(views::Button * sender,const ui::Event & event)651 void AvatarMenuBubbleView::ButtonPressed(views::Button* sender,
652                                          const ui::Event& event) {
653   if (sender->tag() == IDS_PROFILES_MANAGE_PROFILES_BUTTON) {
654     std::string subpage = chrome::kSearchUsersSubPage;
655     chrome::ShowSettingsSubPage(browser_, subpage);
656     return;
657   } else if (sender->tag() == IDS_PROFILES_PROFILE_SIGNOUT_BUTTON) {
658     profiles::LockProfile(browser_->profile());
659     return;
660   }
661 
662   for (size_t i = 0; i < item_views_.size(); ++i) {
663     ProfileItemView* item_view = item_views_[i];
664     if (sender == item_view) {
665       // Clicking on the active profile shouldn't do anything.
666       if (!item_view->item().active) {
667         avatar_menu_->SwitchToProfile(
668             i, ui::DispositionFromEventFlags(event.flags()) == NEW_WINDOW,
669             ProfileMetrics::SWITCH_PROFILE_ICON);
670       }
671       break;
672     }
673   }
674 }
675 
LinkClicked(views::Link * source,int event_flags)676 void AvatarMenuBubbleView::LinkClicked(views::Link* source, int event_flags) {
677   if (source == buttons_view_) {
678     avatar_menu_->AddNewProfile(ProfileMetrics::ADD_NEW_USER_ICON);
679     return;
680   }
681   if (source == switch_profile_link_) {
682     expanded_ = true;
683     OnAvatarMenuChanged(avatar_menu_.get());
684     return;
685   }
686 
687   for (size_t i = 0; i < item_views_.size(); ++i) {
688     ProfileItemView* item_view = item_views_[i];
689     if (source == item_view->edit_link()) {
690       avatar_menu_->EditProfile(i);
691       return;
692     }
693   }
694 }
695 
GetAnchorRect() const696 gfx::Rect AvatarMenuBubbleView::GetAnchorRect() const {
697   return anchor_rect_;
698 }
699 
Init()700 void AvatarMenuBubbleView::Init() {
701   // Build the menu for the first time.
702   OnAvatarMenuChanged(avatar_menu_.get());
703   AddAccelerator(ui::Accelerator(ui::VKEY_DOWN, ui::EF_NONE));
704   AddAccelerator(ui::Accelerator(ui::VKEY_UP, ui::EF_NONE));
705 }
706 
WindowClosing()707 void AvatarMenuBubbleView::WindowClosing() {
708   DCHECK_EQ(avatar_bubble_, this);
709   avatar_bubble_ = NULL;
710 }
711 
InitMenuContents(AvatarMenu * avatar_menu)712 void AvatarMenuBubbleView::InitMenuContents(
713     AvatarMenu* avatar_menu) {
714   for (size_t i = 0; i < avatar_menu->GetNumberOfItems(); ++i) {
715     const AvatarMenu::Item& item = avatar_menu->GetItemAt(i);
716     ProfileItemView* item_view = new ProfileItemView(item,
717                                                      this,
718                                                      avatar_menu_.get());
719     item_view->SetAccessibleName(l10n_util::GetStringFUTF16(
720         IDS_PROFILES_SWITCH_TO_PROFILE_ACCESSIBLE_NAME, item.name));
721     item_view->SetFocusable(true);
722     AddChildView(item_view);
723     item_views_.push_back(item_view);
724   }
725 
726   if (avatar_menu_->ShouldShowAddNewProfileLink()) {
727     views::Link* add_profile_link = new views::Link(
728         l10n_util::GetStringUTF16(IDS_PROFILES_CREATE_NEW_PROFILE_LINK));
729     add_profile_link->set_listener(this);
730     add_profile_link->SetHorizontalAlignment(gfx::ALIGN_CENTER);
731     add_profile_link->SetBackgroundColor(color());
732     separator_ = new views::Separator(views::Separator::HORIZONTAL);
733     AddChildView(separator_);
734     buttons_view_ = add_profile_link;
735     AddChildView(buttons_view_);
736   }
737 }
738 
InitSupervisedUserContents(AvatarMenu * avatar_menu)739 void AvatarMenuBubbleView::InitSupervisedUserContents(
740     AvatarMenu* avatar_menu) {
741   // Show the profile of the supervised user.
742   size_t active_index = avatar_menu->GetActiveProfileIndex();
743   const AvatarMenu::Item& item =
744       avatar_menu->GetItemAt(active_index);
745   ProfileItemView* item_view = new ProfileItemView(item,
746                                                    this,
747                                                    avatar_menu_.get());
748   item_view->SetAccessibleName(l10n_util::GetStringFUTF16(
749       IDS_PROFILES_SWITCH_TO_PROFILE_ACCESSIBLE_NAME, item.name));
750   item_views_.push_back(item_view);
751   AddChildView(item_view);
752   separator_ = new views::Separator(views::Separator::HORIZONTAL);
753   AddChildView(separator_);
754 
755   // Add information about supervised users.
756   supervised_user_info_ =
757       new views::Label(avatar_menu_->GetSupervisedUserInformation(),
758                        ui::ResourceBundle::GetSharedInstance().GetFontList(
759                            ui::ResourceBundle::SmallFont));
760   supervised_user_info_->SetMultiLine(true);
761   supervised_user_info_->SetAllowCharacterBreak(true);
762   supervised_user_info_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
763   supervised_user_info_->SetBackgroundColor(color());
764   AddChildView(supervised_user_info_);
765 
766   // Add the supervised user icon.
767   icon_view_ = new views::ImageView();
768   icon_view_->SetImage(avatar_menu_->GetSupervisedUserIcon().ToImageSkia());
769   AddChildView(icon_view_);
770 
771   // Add a link for switching profiles.
772   separator_switch_users_ = new views::Separator(views::Separator::HORIZONTAL);
773   AddChildView(separator_switch_users_);
774   switch_profile_link_ = new views::Link(
775       l10n_util::GetStringUTF16(IDS_PROFILES_SWITCH_PROFILE_LINK));
776   switch_profile_link_->set_listener(this);
777   switch_profile_link_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
778   switch_profile_link_->SetBackgroundColor(color());
779   AddChildView(switch_profile_link_);
780 }
781 
OnAvatarMenuChanged(AvatarMenu * avatar_menu)782 void AvatarMenuBubbleView::OnAvatarMenuChanged(
783     AvatarMenu* avatar_menu) {
784   // Unset all our child view references and call RemoveAllChildViews() which
785   // will actually delete them.
786   buttons_view_ = NULL;
787   supervised_user_info_ = NULL;
788   item_views_.clear();
789   RemoveAllChildViews(true);
790 
791   if (avatar_menu_->GetSupervisedUserInformation().empty() || expanded_)
792     InitMenuContents(avatar_menu);
793   else
794     InitSupervisedUserContents(avatar_menu);
795 
796   // If the bubble has already been shown then resize and reposition the bubble.
797   Layout();
798   if (GetBubbleFrameView())
799     SizeToContents();
800 }
801 
SetBackgroundColors()802 void AvatarMenuBubbleView::SetBackgroundColors() {
803   for (size_t i = 0; i < item_views_.size(); ++i) {
804     item_views_[i]->OnHighlightStateChanged();
805   }
806 }
807