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