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.
5 #include "chrome/browser/ui/views/profiles/avatar_menu_bubble_view.h"
9 #include "base/strings/string16.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "chrome/app/chrome_command_ids.h"
12 #include "chrome/browser/browser_process.h"
13 #include "chrome/browser/profiles/avatar_menu.h"
14 #include "chrome/browser/profiles/profile_avatar_icon_util.h"
15 #include "chrome/browser/profiles/profile_info_cache.h"
16 #include "chrome/browser/profiles/profile_manager.h"
17 #include "chrome/browser/profiles/profile_window.h"
18 #include "chrome/browser/signin/signin_manager_factory.h"
19 #include "chrome/browser/ui/browser.h"
20 #include "chrome/browser/ui/browser_commands.h"
21 #include "chrome/browser/ui/browser_list.h"
22 #include "chrome/browser/ui/browser_window.h"
23 #include "chrome/browser/ui/chrome_pages.h"
24 #include "chrome/common/url_constants.h"
25 #include "components/signin/core/browser/signin_manager.h"
26 #include "components/signin/core/common/profile_management_switches.h"
27 #include "content/public/browser/page_navigator.h"
28 #include "content/public/browser/web_contents.h"
29 #include "grit/generated_resources.h"
30 #include "grit/theme_resources.h"
31 #include "ui/base/l10n/l10n_util.h"
32 #include "ui/base/resource/resource_bundle.h"
33 #include "ui/gfx/canvas.h"
34 #include "ui/gfx/image/canvas_image_source.h"
35 #include "ui/gfx/image/image.h"
36 #include "ui/views/controls/button/custom_button.h"
37 #include "ui/views/controls/button/image_button.h"
38 #include "ui/views/controls/button/label_button.h"
39 #include "ui/views/controls/image_view.h"
40 #include "ui/views/controls/label.h"
41 #include "ui/views/controls/link.h"
42 #include "ui/views/controls/separator.h"
43 #include "ui/views/layout/grid_layout.h"
44 #include "ui/views/layout/layout_constants.h"
45 #include "ui/views/widget/widget.h"
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;
56 inline int Round(double x
) {
57 return static_cast<int>(x
+ 0.5);
60 gfx::Rect
GetCenteredAndScaledRect(int src_width
, int src_height
,
62 int dst_width
, int dst_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
);
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
);
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
);
81 // BadgeImageSource -----------------------------------------------------------
82 class BadgeImageSource
: public gfx::CanvasImageSource
{
84 BadgeImageSource(const gfx::ImageSkia
& icon
,
85 const gfx::Size
& icon_size
,
86 const gfx::ImageSkia
& badge
);
88 virtual ~BadgeImageSource();
90 // Overridden from CanvasImageSource:
91 virtual void Draw(gfx::Canvas
* canvas
) OVERRIDE
;
94 gfx::Size
ComputeSize(const gfx::ImageSkia
& icon
,
95 const gfx::Size
& size
,
96 const gfx::ImageSkia
& badge
);
98 const gfx::ImageSkia icon_
;
100 const gfx::ImageSkia badge_
;
102 DISALLOW_COPY_AND_ASSIGN(BadgeImageSource
);
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),
110 icon_size_(icon_size
),
114 BadgeImageSource::~BadgeImageSource() {
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());
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
);
134 // HighlightDelegate ----------------------------------------------------------
136 // Delegate to callback when the highlight state of a control changes.
137 class HighlightDelegate
{
139 virtual ~HighlightDelegate() {}
140 virtual void OnHighlightStateChanged() = 0;
141 virtual void OnFocusStateChanged(bool has_focus
) = 0;
145 // EditProfileLink ------------------------------------------------------------
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
{
152 explicit EditProfileLink(const base::string16
& title
,
153 HighlightDelegate
* delegate
);
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
;
160 views::CustomButton::ButtonState
state() { return state_
; }
163 HighlightDelegate
* delegate_
;
164 views::CustomButton::ButtonState state_
;
167 EditProfileLink::EditProfileLink(const base::string16
& title
,
168 HighlightDelegate
* delegate
)
169 : views::Link(title
),
171 state_(views::CustomButton::STATE_NORMAL
) {
174 void EditProfileLink::OnMouseEntered(const ui::MouseEvent
& event
) {
175 views::Link::OnMouseEntered(event
);
176 state_
= views::CustomButton::STATE_HOVERED
;
177 delegate_
->OnHighlightStateChanged();
180 void EditProfileLink::OnMouseExited(const ui::MouseEvent
& event
) {
181 views::Link::OnMouseExited(event
);
182 state_
= views::CustomButton::STATE_NORMAL
;
183 delegate_
->OnHighlightStateChanged();
186 void EditProfileLink::OnFocus() {
187 views::Link::OnFocus();
188 delegate_
->OnFocusStateChanged(true);
191 void EditProfileLink::OnBlur() {
192 views::Link::OnBlur();
193 state_
= views::CustomButton::STATE_NORMAL
;
194 delegate_
->OnFocusStateChanged(false);
198 // ProfileImageView -----------------------------------------------------------
200 // A custom image view that ignores mouse events so that the parent can receive
202 class ProfileImageView
: public views::ImageView
{
205 virtual bool CanProcessEventsWithinSubtree() const OVERRIDE
;
208 bool ProfileImageView::CanProcessEventsWithinSubtree() const {
209 // Send events to the parent view for handling.
215 // ProfileItemView ------------------------------------------------------------
217 // Control that shows information about a single profile.
218 class ProfileItemView
: public views::CustomButton
,
219 public HighlightDelegate
{
221 ProfileItemView(const AvatarMenu::Item
& item
,
222 AvatarMenuBubbleView
* parent
,
225 virtual gfx::Size
GetPreferredSize() const OVERRIDE
;
226 virtual void Layout() OVERRIDE
;
227 virtual void OnMouseEntered(const ui::MouseEvent
& event
) OVERRIDE
;
228 virtual void OnMouseExited(const ui::MouseEvent
& event
) OVERRIDE
;
229 virtual void OnFocus() OVERRIDE
;
230 virtual void OnBlur() OVERRIDE
;
232 virtual void OnHighlightStateChanged() OVERRIDE
;
233 virtual void OnFocusStateChanged(bool has_focus
) OVERRIDE
;
235 const AvatarMenu::Item
& item() const { return item_
; }
236 EditProfileLink
* edit_link() { return edit_link_
; }
239 gfx::ImageSkia
GetBadgedIcon(const gfx::ImageSkia
& icon
);
241 bool IsHighlighted();
243 AvatarMenu::Item item_
;
244 AvatarMenuBubbleView
* parent_
;
246 views::ImageView
* image_view_
;
247 views::Label
* name_label_
;
248 views::Label
* sync_state_label_
;
249 EditProfileLink
* edit_link_
;
251 DISALLOW_COPY_AND_ASSIGN(ProfileItemView
);
254 ProfileItemView::ProfileItemView(const AvatarMenu::Item
& item
,
255 AvatarMenuBubbleView
* parent
,
257 : views::CustomButton(parent
),
261 set_notify_enter_exit_on_child(true);
263 image_view_
= new ProfileImageView();
264 // GetSizedAvatarIcon will resize the icon in case it's too large.
265 const gfx::ImageSkia profile_icon
= *profiles::GetSizedAvatarIcon(item_
.icon
,
266 false, profiles::kAvatarIconWidth
, kItemHeight
).ToImageSkia();
267 if (item_
.active
|| item_
.signin_required
)
268 image_view_
->SetImage(GetBadgedIcon(profile_icon
));
270 image_view_
->SetImage(profile_icon
);
271 AddChildView(image_view_
);
273 // Add a label to show the profile name.
274 ui::ResourceBundle
* rb
= &ui::ResourceBundle::GetSharedInstance();
275 name_label_
= new views::Label(item_
.name
,
276 rb
->GetFontList(item_
.active
?
277 ui::ResourceBundle::BoldFont
:
278 ui::ResourceBundle::BaseFont
));
279 name_label_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
280 AddChildView(name_label_
);
282 // Add a label to show the sync state.
283 sync_state_label_
= new views::Label(item_
.sync_state
);
285 sync_state_label_
->SetElideBehavior(views::Label::ELIDE_AS_EMAIL
);
286 sync_state_label_
->SetFontList(
287 rb
->GetFontList(ui::ResourceBundle::SmallFont
));
288 sync_state_label_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
289 sync_state_label_
->SetEnabled(false);
290 AddChildView(sync_state_label_
);
292 // Add an edit profile link.
293 edit_link_
= new EditProfileLink(
294 l10n_util::GetStringUTF16(IDS_PROFILES_EDIT_PROFILE_LINK
), this);
295 edit_link_
->set_listener(parent
);
296 edit_link_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
297 AddChildView(edit_link_
);
299 OnHighlightStateChanged();
302 gfx::Size
ProfileItemView::GetPreferredSize() const {
303 int text_width
= std::max(name_label_
->GetPreferredSize().width(),
304 sync_state_label_
->GetPreferredSize().width());
305 text_width
= std::max(edit_link_
->GetPreferredSize().width(), text_width
);
306 text_width
= std::min(kMaxItemTextWidth
, text_width
);
307 return gfx::Size(profiles::kAvatarIconWidth
+ kIconMarginX
+ text_width
,
311 void ProfileItemView::Layout() {
315 // If this is the active item then the icon is already scaled and so
316 // just use the preferred size.
317 icon_rect
.set_size(image_view_
->GetPreferredSize());
318 icon_rect
.set_y((height() - icon_rect
.height()) / 2);
320 const gfx::ImageSkia
& icon
= image_view_
->GetImage();
321 icon_rect
= GetCenteredAndScaledRect(icon
.width(), icon
.height(), 0, 0,
322 profiles::kAvatarIconWidth
, height());
324 image_view_
->SetBoundsRect(icon_rect
);
326 int label_x
= profiles::kAvatarIconWidth
+ kIconMarginX
;
327 int max_label_width
= std::max(width() - label_x
, 0);
328 gfx::Size name_size
= name_label_
->GetPreferredSize();
329 name_size
.set_width(std::min(name_size
.width(), max_label_width
));
330 gfx::Size state_size
= sync_state_label_
->GetPreferredSize();
331 state_size
.set_width(std::min(state_size
.width(), max_label_width
));
332 gfx::Size edit_size
= edit_link_
->GetPreferredSize();
333 edit_size
.set_width(std::min(edit_size
.width(), max_label_width
));
335 const int kNameStatePaddingY
= 2;
336 int labels_height
= name_size
.height() + kNameStatePaddingY
+
337 std::max(state_size
.height(), edit_size
.height());
338 int y
= (height() - labels_height
) / 2;
339 name_label_
->SetBounds(label_x
, y
, name_size
.width(), name_size
.height());
341 int bottom
= y
+ labels_height
;
342 sync_state_label_
->SetBounds(label_x
, bottom
- state_size
.height(),
343 state_size
.width(), state_size
.height());
344 // The edit link overlaps the sync state label.
345 edit_link_
->SetBounds(label_x
, bottom
- edit_size
.height(),
346 edit_size
.width(), edit_size
.height());
349 void ProfileItemView::OnMouseEntered(const ui::MouseEvent
& event
) {
350 views::CustomButton::OnMouseEntered(event
);
351 OnHighlightStateChanged();
354 void ProfileItemView::OnMouseExited(const ui::MouseEvent
& event
) {
355 views::CustomButton::OnMouseExited(event
);
356 OnHighlightStateChanged();
359 void ProfileItemView::OnFocus() {
360 views::CustomButton::OnFocus();
361 OnFocusStateChanged(true);
364 void ProfileItemView::OnBlur() {
365 views::CustomButton::OnBlur();
366 OnFocusStateChanged(false);
369 void ProfileItemView::OnHighlightStateChanged() {
370 const SkColor color
= IsHighlighted() ? kHighlightColor
: parent_
->color();
371 set_background(views::Background::CreateSolidBackground(color
));
372 name_label_
->SetBackgroundColor(color
);
373 sync_state_label_
->SetBackgroundColor(color
);
374 edit_link_
->SetBackgroundColor(color
);
376 bool show_edit
= IsHighlighted() && item_
.active
&&
377 menu_
->ShouldShowEditProfileLink();
378 sync_state_label_
->SetVisible(!show_edit
);
379 edit_link_
->SetVisible(show_edit
);
383 void ProfileItemView::OnFocusStateChanged(bool has_focus
) {
384 if (!has_focus
&& state() != views::CustomButton::STATE_DISABLED
)
385 SetState(views::CustomButton::STATE_NORMAL
);
386 OnHighlightStateChanged();
390 gfx::ImageSkia
ProfileItemView::GetBadgedIcon(const gfx::ImageSkia
& icon
) {
391 ui::ResourceBundle
* rb
= &ui::ResourceBundle::GetSharedInstance();
392 const gfx::ImageSkia
* badge
= NULL
;
395 badge
= rb
->GetImageSkiaNamed(IDR_PROFILE_SELECTED
);
396 else if (item_
.signin_required
) // TODO(bcwhite): create new icon
397 badge
= rb
->GetImageSkiaNamed(IDR_OMNIBOX_HTTPS_VALID
);
399 NOTREACHED(); // function should only be called if one of above is true
401 gfx::Size icon_size
= GetCenteredAndScaledRect(icon
.width(), icon
.height(),
402 0, 0, profiles::kAvatarIconWidth
, kItemHeight
).size();
403 gfx::CanvasImageSource
* source
=
404 new BadgeImageSource(icon
, icon_size
, *badge
);
405 // ImageSkia takes ownership of |source|.
406 return gfx::ImageSkia(source
, source
->size());
409 bool ProfileItemView::IsHighlighted() {
410 return state() == views::CustomButton::STATE_PRESSED
||
411 state() == views::CustomButton::STATE_HOVERED
||
412 edit_link_
->state() == views::CustomButton::STATE_PRESSED
||
413 edit_link_
->state() == views::CustomButton::STATE_HOVERED
||
415 edit_link_
->HasFocus();
419 // ActionButtonView -----------------------------------------------------------
421 // A custom view that manages the "action" buttons at the bottom of the list
423 class ActionButtonView
: public views::View
{
425 ActionButtonView(views::ButtonListener
* listener
, Profile
* profile
);
428 views::LabelButton
* manage_button_
;
429 views::LabelButton
* signout_button_
;
431 DISALLOW_COPY_AND_ASSIGN(ActionButtonView
);
435 ActionButtonView::ActionButtonView(views::ButtonListener
* listener
,
437 : manage_button_(NULL
),
438 signout_button_(NULL
) {
439 std::string username
;
440 SigninManagerBase
* signin
=
441 SigninManagerFactory::GetForProfile(profile
);
443 username
= signin
->GetAuthenticatedUsername();
445 manage_button_
= new views::LabelButton(
446 listener
, l10n_util::GetStringUTF16(IDS_PROFILES_MANAGE_PROFILES_BUTTON
));
447 manage_button_
->SetStyle(views::Button::STYLE_BUTTON
);
448 manage_button_
->SetTooltipText(
449 l10n_util::GetStringUTF16(IDS_PROFILES_MANAGE_PROFILES_BUTTON_TIP
));
450 manage_button_
->set_tag(IDS_PROFILES_MANAGE_PROFILES_BUTTON
);
452 signout_button_
= new views::LabelButton(
453 listener
, l10n_util::GetStringUTF16(IDS_PROFILES_PROFILE_SIGNOUT_BUTTON
));
454 signout_button_
->SetStyle(views::Button::STYLE_BUTTON
);
455 if (username
.empty()) {
456 signout_button_
->SetTooltipText(
457 l10n_util::GetStringUTF16(
458 IDS_PROFILES_PROFILE_SIGNOUT_BUTTON_TIP_UNAVAILABLE
));
459 signout_button_
->SetEnabled(false);
461 signout_button_
->SetTooltipText(
462 l10n_util::GetStringFUTF16(IDS_PROFILES_PROFILE_SIGNOUT_BUTTON_TIP
,
463 base::UTF8ToUTF16(username
)));
465 signout_button_
->set_tag(IDS_PROFILES_PROFILE_SIGNOUT_BUTTON
);
467 views::GridLayout
* layout
= new views::GridLayout(this);
468 views::ColumnSet
* columns
= layout
->AddColumnSet(0);
469 columns
->AddColumn(views::GridLayout::LEADING
, views::GridLayout::FILL
, 1,
470 views::GridLayout::USE_PREF
, 0, 0);
471 columns
->AddColumn(views::GridLayout::TRAILING
, views::GridLayout::FILL
, 1,
472 views::GridLayout::USE_PREF
, 0, 0);
473 layout
->StartRow(0, 0);
474 layout
->AddView(signout_button_
);
475 layout
->AddView(manage_button_
);
476 SetLayoutManager(layout
);
480 // AvatarMenuBubbleView -------------------------------------------------------
483 AvatarMenuBubbleView
* AvatarMenuBubbleView::avatar_bubble_
= NULL
;
484 bool AvatarMenuBubbleView::close_on_deactivate_for_testing_
= true;
487 void AvatarMenuBubbleView::ShowBubble(
488 views::View
* anchor_view
,
489 views::BubbleBorder::Arrow arrow
,
490 views::BubbleBorder::ArrowPaintType arrow_paint_type
,
491 views::BubbleBorder::BubbleAlignment border_alignment
,
492 const gfx::Rect
& anchor_rect
,
497 DCHECK(chrome::IsCommandEnabled(browser
, IDC_SHOW_AVATAR_MENU
));
498 avatar_bubble_
= new AvatarMenuBubbleView(
499 anchor_view
, arrow
, anchor_rect
, browser
);
500 views::BubbleDelegateView::CreateBubble(avatar_bubble_
);
501 avatar_bubble_
->set_close_on_deactivate(close_on_deactivate_for_testing_
);
502 avatar_bubble_
->SetBackgroundColors();
503 avatar_bubble_
->SetAlignment(border_alignment
);
504 avatar_bubble_
->SetArrowPaintType(arrow_paint_type
);
505 avatar_bubble_
->GetWidget()->Show();
509 bool AvatarMenuBubbleView::IsShowing() {
510 return avatar_bubble_
!= NULL
;
514 void AvatarMenuBubbleView::Hide() {
516 avatar_bubble_
->GetWidget()->Close();
519 AvatarMenuBubbleView::AvatarMenuBubbleView(
520 views::View
* anchor_view
,
521 views::BubbleBorder::Arrow arrow
,
522 const gfx::Rect
& anchor_rect
,
524 : BubbleDelegateView(anchor_view
, arrow
),
525 anchor_rect_(anchor_rect
),
529 managed_user_info_(NULL
),
530 separator_switch_users_(NULL
),
532 avatar_menu_
.reset(new AvatarMenu(
533 &g_browser_process
->profile_manager()->GetProfileInfoCache(),
536 avatar_menu_
->RebuildMenu();
539 AvatarMenuBubbleView::~AvatarMenuBubbleView() {
542 gfx::Size
AvatarMenuBubbleView::GetPreferredSize() const {
543 const int kBubbleViewMinWidth
= 175;
544 gfx::Size
preferred_size(kBubbleViewMinWidth
, 0);
545 for (size_t i
= 0; i
< item_views_
.size(); ++i
) {
546 gfx::Size size
= item_views_
[i
]->GetPreferredSize();
547 preferred_size
.Enlarge(0, size
.height() + kItemMarginY
);
548 preferred_size
.SetToMax(size
);
552 preferred_size
.Enlarge(
553 0, kSeparatorPaddingY
* 2 + separator_
->GetPreferredSize().height());
555 gfx::Size buttons_size
= buttons_view_
->GetPreferredSize();
556 preferred_size
.Enlarge(0, buttons_size
.height());
557 preferred_size
.SetToMax(buttons_size
);
561 if (managed_user_info_
) {
562 // First handle the switch profile link because it can still affect the
564 gfx::Size size
= switch_profile_link_
->GetPreferredSize();
565 preferred_size
.Enlarge(0, size
.height());
566 preferred_size
.SetToMax(size
);
568 // Add the height of the two separators.
569 preferred_size
.Enlarge(
571 kSeparatorPaddingY
* 4 + separator_
->GetPreferredSize().height() * 2);
574 const int kBubbleViewMaxWidth
= 800;
575 preferred_size
.SetToMin(
576 gfx::Size(kBubbleViewMaxWidth
, preferred_size
.height()));
578 // We have to do this after the final width is calculated, since the label
579 // will wrap based on the width.
580 if (managed_user_info_
) {
581 int remaining_width
=
582 preferred_size
.width() - icon_view_
->GetPreferredSize().width() -
583 views::kRelatedControlSmallHorizontalSpacing
;
584 preferred_size
.Enlarge(
586 managed_user_info_
->GetHeightForWidth(remaining_width
) + kItemMarginY
);
589 return preferred_size
;
592 void AvatarMenuBubbleView::Layout() {
594 for (size_t i
= 0; i
< item_views_
.size(); ++i
) {
595 views::CustomButton
* item_view
= item_views_
[i
];
596 int item_height
= item_view
->GetPreferredSize().height();
597 int item_width
= width();
598 item_view
->SetBounds(0, y
, item_width
, item_height
);
599 y
+= item_height
+ kItemMarginY
;
602 int separator_height
;
603 if (buttons_view_
|| managed_user_info_
) {
604 separator_height
= separator_
->GetPreferredSize().height();
605 y
+= kSeparatorPaddingY
;
606 separator_
->SetBounds(0, y
, width(), separator_height
);
607 y
+= kSeparatorPaddingY
+ separator_height
;
611 buttons_view_
->SetBounds(0, y
,
612 width(), buttons_view_
->GetPreferredSize().height());
613 } else if (managed_user_info_
) {
614 gfx::Size icon_size
= icon_view_
->GetPreferredSize();
615 gfx::Rect
icon_bounds(0, y
, icon_size
.width(), icon_size
.height());
616 icon_view_
->SetBoundsRect(icon_bounds
);
617 int info_width
= width() - icon_bounds
.right() -
618 views::kRelatedControlSmallHorizontalSpacing
;
619 int height
= managed_user_info_
->GetHeightForWidth(info_width
);
620 managed_user_info_
->SetBounds(
621 icon_bounds
.right() + views::kRelatedControlSmallHorizontalSpacing
,
622 y
, info_width
, height
);
623 y
+= height
+ kItemMarginY
+ kSeparatorPaddingY
;
624 separator_switch_users_
->SetBounds(0, y
, width(), separator_height
);
625 y
+= separator_height
+ kSeparatorPaddingY
;
626 int link_height
= switch_profile_link_
->GetPreferredSize().height();
627 switch_profile_link_
->SetBounds(0, y
, width(), link_height
);
631 bool AvatarMenuBubbleView::AcceleratorPressed(
632 const ui::Accelerator
& accelerator
) {
633 if (accelerator
.key_code() != ui::VKEY_DOWN
&&
634 accelerator
.key_code() != ui::VKEY_UP
)
635 return BubbleDelegateView::AcceleratorPressed(accelerator
);
637 if (item_views_
.empty())
640 // Find the currently focused item. Note that if there is no focused item, the
641 // code below correctly handles a |focus_index| of -1.
642 int focus_index
= -1;
643 for (size_t i
= 0; i
< item_views_
.size(); ++i
) {
644 if (item_views_
[i
]->HasFocus()) {
650 // Moved the focus up or down by 1.
651 if (accelerator
.key_code() == ui::VKEY_DOWN
)
652 focus_index
= (focus_index
+ 1) % item_views_
.size();
654 focus_index
= ((focus_index
> 0) ? focus_index
: item_views_
.size()) - 1;
655 item_views_
[focus_index
]->RequestFocus();
660 void AvatarMenuBubbleView::ButtonPressed(views::Button
* sender
,
661 const ui::Event
& event
) {
662 if (sender
->tag() == IDS_PROFILES_MANAGE_PROFILES_BUTTON
) {
663 std::string subpage
= chrome::kSearchUsersSubPage
;
664 chrome::ShowSettingsSubPage(browser_
, subpage
);
666 } else if (sender
->tag() == IDS_PROFILES_PROFILE_SIGNOUT_BUTTON
) {
667 profiles::LockProfile(browser_
->profile());
671 for (size_t i
= 0; i
< item_views_
.size(); ++i
) {
672 ProfileItemView
* item_view
= item_views_
[i
];
673 if (sender
== item_view
) {
674 // Clicking on the active profile shouldn't do anything.
675 if (!item_view
->item().active
) {
676 avatar_menu_
->SwitchToProfile(
677 i
, ui::DispositionFromEventFlags(event
.flags()) == NEW_WINDOW
,
678 ProfileMetrics::SWITCH_PROFILE_ICON
);
685 void AvatarMenuBubbleView::LinkClicked(views::Link
* source
, int event_flags
) {
686 if (source
== buttons_view_
) {
687 avatar_menu_
->AddNewProfile(ProfileMetrics::ADD_NEW_USER_ICON
);
690 if (source
== switch_profile_link_
) {
692 OnAvatarMenuChanged(avatar_menu_
.get());
696 for (size_t i
= 0; i
< item_views_
.size(); ++i
) {
697 ProfileItemView
* item_view
= item_views_
[i
];
698 if (source
== item_view
->edit_link()) {
699 avatar_menu_
->EditProfile(i
);
705 gfx::Rect
AvatarMenuBubbleView::GetAnchorRect() const {
709 void AvatarMenuBubbleView::Init() {
710 // Build the menu for the first time.
711 OnAvatarMenuChanged(avatar_menu_
.get());
712 AddAccelerator(ui::Accelerator(ui::VKEY_DOWN
, ui::EF_NONE
));
713 AddAccelerator(ui::Accelerator(ui::VKEY_UP
, ui::EF_NONE
));
716 void AvatarMenuBubbleView::WindowClosing() {
717 DCHECK_EQ(avatar_bubble_
, this);
718 avatar_bubble_
= NULL
;
721 void AvatarMenuBubbleView::InitMenuContents(
722 AvatarMenu
* avatar_menu
) {
723 for (size_t i
= 0; i
< avatar_menu
->GetNumberOfItems(); ++i
) {
724 const AvatarMenu::Item
& item
= avatar_menu
->GetItemAt(i
);
725 ProfileItemView
* item_view
= new ProfileItemView(item
,
728 item_view
->SetAccessibleName(l10n_util::GetStringFUTF16(
729 IDS_PROFILES_SWITCH_TO_PROFILE_ACCESSIBLE_NAME
, item
.name
));
730 item_view
->SetFocusable(true);
731 AddChildView(item_view
);
732 item_views_
.push_back(item_view
);
735 if (avatar_menu_
->ShouldShowAddNewProfileLink()) {
736 views::Link
* add_profile_link
= new views::Link(
737 l10n_util::GetStringUTF16(IDS_PROFILES_CREATE_NEW_PROFILE_LINK
));
738 add_profile_link
->set_listener(this);
739 add_profile_link
->SetHorizontalAlignment(gfx::ALIGN_CENTER
);
740 add_profile_link
->SetBackgroundColor(color());
741 separator_
= new views::Separator(views::Separator::HORIZONTAL
);
742 AddChildView(separator_
);
743 buttons_view_
= add_profile_link
;
744 AddChildView(buttons_view_
);
748 void AvatarMenuBubbleView::InitManagedUserContents(
749 AvatarMenu
* avatar_menu
) {
750 // Show the profile of the managed user.
751 size_t active_index
= avatar_menu
->GetActiveProfileIndex();
752 const AvatarMenu::Item
& item
=
753 avatar_menu
->GetItemAt(active_index
);
754 ProfileItemView
* item_view
= new ProfileItemView(item
,
757 item_view
->SetAccessibleName(l10n_util::GetStringFUTF16(
758 IDS_PROFILES_SWITCH_TO_PROFILE_ACCESSIBLE_NAME
, item
.name
));
759 item_views_
.push_back(item_view
);
760 AddChildView(item_view
);
761 separator_
= new views::Separator(views::Separator::HORIZONTAL
);
762 AddChildView(separator_
);
764 // Add information about managed users.
766 new views::Label(avatar_menu_
->GetManagedUserInformation(),
767 ui::ResourceBundle::GetSharedInstance().GetFontList(
768 ui::ResourceBundle::SmallFont
));
769 managed_user_info_
->SetMultiLine(true);
770 managed_user_info_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
771 managed_user_info_
->SetBackgroundColor(color());
772 AddChildView(managed_user_info_
);
774 // Add the managed user icon.
775 icon_view_
= new views::ImageView();
776 icon_view_
->SetImage(avatar_menu_
->GetManagedUserIcon().ToImageSkia());
777 AddChildView(icon_view_
);
779 // Add a link for switching profiles.
780 separator_switch_users_
= new views::Separator(views::Separator::HORIZONTAL
);
781 AddChildView(separator_switch_users_
);
782 switch_profile_link_
= new views::Link(
783 l10n_util::GetStringUTF16(IDS_PROFILES_SWITCH_PROFILE_LINK
));
784 switch_profile_link_
->set_listener(this);
785 switch_profile_link_
->SetHorizontalAlignment(gfx::ALIGN_CENTER
);
786 switch_profile_link_
->SetBackgroundColor(color());
787 AddChildView(switch_profile_link_
);
790 void AvatarMenuBubbleView::OnAvatarMenuChanged(
791 AvatarMenu
* avatar_menu
) {
792 // Unset all our child view references and call RemoveAllChildViews() which
793 // will actually delete them.
794 buttons_view_
= NULL
;
795 managed_user_info_
= NULL
;
797 RemoveAllChildViews(true);
799 if (avatar_menu_
->GetManagedUserInformation().empty() || expanded_
)
800 InitMenuContents(avatar_menu
);
802 InitManagedUserContents(avatar_menu
);
804 // If the bubble has already been shown then resize and reposition the bubble.
806 if (GetBubbleFrameView())
810 void AvatarMenuBubbleView::SetBackgroundColors() {
811 for (size_t i
= 0; i
< item_views_
.size(); ++i
) {
812 item_views_
[i
]->OnHighlightStateChanged();