Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / ui / views / avatar_menu_bubble_view.cc
blobbb6f7e887a0cd58f7ecbe6fed746133d4b07de16
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.
5 #include "chrome/browser/ui/views/avatar_menu_bubble_view.h"
7 #include <algorithm>
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"
47 namespace {
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,
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);
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 {
83 public:
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;
93 private:
94 gfx::Size ComputeSize(const gfx::ImageSkia& icon,
95 const gfx::Size& size,
96 const gfx::ImageSkia& badge);
98 const gfx::ImageSkia icon_;
99 gfx::Size icon_size_;
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),
109 icon_(icon),
110 icon_size_(icon_size),
111 badge_(badge) {
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 {
138 public:
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 {
151 public:
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_; }
162 private:
163 HighlightDelegate* delegate_;
164 views::CustomButton::ButtonState state_;
167 EditProfileLink::EditProfileLink(const base::string16& title,
168 HighlightDelegate* delegate)
169 : views::Link(title),
170 delegate_(delegate),
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
201 // them instead.
202 class ProfileImageView : public views::ImageView {
203 public:
204 virtual bool HitTestRect(const gfx::Rect& rect) const OVERRIDE;
207 bool ProfileImageView::HitTestRect(const gfx::Rect& rect) const {
208 return false;
211 } // namespace
213 // ProfileItemView ------------------------------------------------------------
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);
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;
230 virtual void OnHighlightStateChanged() OVERRIDE;
231 virtual void OnFocusStateChanged(bool has_focus) OVERRIDE;
233 const AvatarMenu::Item& item() const { return item_; }
234 EditProfileLink* edit_link() { return edit_link_; }
236 private:
237 gfx::ImageSkia GetBadgedIcon(const gfx::ImageSkia& icon);
239 bool IsHighlighted();
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_;
249 DISALLOW_COPY_AND_ASSIGN(ProfileItemView);
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);
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_);
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_);
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_);
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_);
295 OnHighlightStateChanged();
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);
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());
320 image_view_->SetBoundsRect(icon_rect);
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));
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());
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());
345 void ProfileItemView::OnMouseEntered(const ui::MouseEvent& event) {
346 views::CustomButton::OnMouseEntered(event);
347 OnHighlightStateChanged();
350 void ProfileItemView::OnMouseExited(const ui::MouseEvent& event) {
351 views::CustomButton::OnMouseExited(event);
352 OnHighlightStateChanged();
355 void ProfileItemView::OnFocus() {
356 views::CustomButton::OnFocus();
357 OnFocusStateChanged(true);
360 void ProfileItemView::OnBlur() {
361 views::CustomButton::OnBlur();
362 OnFocusStateChanged(false);
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);
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();
379 void ProfileItemView::OnFocusStateChanged(bool has_focus) {
380 if (!has_focus && state() != views::CustomButton::STATE_DISABLED)
381 SetState(views::CustomButton::STATE_NORMAL);
382 OnHighlightStateChanged();
385 // static
386 gfx::ImageSkia ProfileItemView::GetBadgedIcon(const gfx::ImageSkia& icon) {
387 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
388 const gfx::ImageSkia* badge = NULL;
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
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());
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();
415 // ActionButtonView -----------------------------------------------------------
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);
423 private:
424 views::LabelButton* manage_button_;
425 views::LabelButton* signout_button_;
427 DISALLOW_COPY_AND_ASSIGN(ActionButtonView);
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();
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);
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)));
461 signout_button_->set_tag(IDS_PROFILES_PROFILE_SIGNOUT_BUTTON);
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);
476 // AvatarMenuBubbleView -------------------------------------------------------
478 // static
479 AvatarMenuBubbleView* AvatarMenuBubbleView::avatar_bubble_ = NULL;
480 bool AvatarMenuBubbleView::close_on_deactivate_for_testing_ = true;
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;
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();
502 // static
503 bool AvatarMenuBubbleView::IsShowing() {
504 return avatar_bubble_ != NULL;
507 // static
508 void AvatarMenuBubbleView::Hide() {
509 if (IsShowing())
510 avatar_bubble_->GetWidget()->Close();
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();
533 AvatarMenuBubbleView::~AvatarMenuBubbleView() {
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);
545 if (buttons_view_) {
546 preferred_size.Enlarge(
547 0, kSeparatorPaddingY * 2 + separator_->GetPreferredSize().height());
549 gfx::Size buttons_size = buttons_view_->GetPreferredSize();
550 preferred_size.Enlarge(0, buttons_size.height());
551 preferred_size.SetToMax(buttons_size);
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);
562 // Add the height of the two separators.
563 preferred_size.Enlarge(
565 kSeparatorPaddingY * 4 + separator_->GetPreferredSize().height() * 2);
568 const int kBubbleViewMaxWidth = 800;
569 preferred_size.SetToMin(
570 gfx::Size(kBubbleViewMaxWidth, preferred_size.height()));
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(
580 managed_user_info_->GetHeightForWidth(remaining_width) + kItemMarginY);
583 return preferred_size;
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;
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;
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);
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);
631 if (item_views_.empty())
632 return true;
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;
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();
651 return true;
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;
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);
673 break;
678 void AvatarMenuBubbleView::LinkClicked(views::Link* source, int event_flags) {
679 if (source == buttons_view_) {
680 avatar_menu_->AddNewProfile(ProfileMetrics::ADD_NEW_USER_ICON);
681 return;
683 if (source == switch_profile_link_) {
684 expanded_ = true;
685 OnAvatarMenuChanged(avatar_menu_.get());
686 return;
689 for (size_t i = 0; i < item_views_.size(); ++i) {
690 ProfileItemView* item_view = item_views_[i];
691 if (source == item_view->edit_link()) {
692 avatar_menu_->EditProfile(i);
693 return;
698 gfx::Rect AvatarMenuBubbleView::GetAnchorRect() {
699 return anchor_rect_;
702 void AvatarMenuBubbleView::Init() {
703 // Build the menu for the first time.
704 OnAvatarMenuChanged(avatar_menu_.get());
705 AddAccelerator(ui::Accelerator(ui::VKEY_DOWN, ui::EF_NONE));
706 AddAccelerator(ui::Accelerator(ui::VKEY_UP, ui::EF_NONE));
709 void AvatarMenuBubbleView::WindowClosing() {
710 DCHECK_EQ(avatar_bubble_, this);
711 avatar_bubble_ = NULL;
714 void AvatarMenuBubbleView::InitMenuContents(
715 AvatarMenu* avatar_menu) {
716 for (size_t i = 0; i < avatar_menu->GetNumberOfItems(); ++i) {
717 const AvatarMenu::Item& item = avatar_menu->GetItemAt(i);
718 ProfileItemView* item_view = new ProfileItemView(item,
719 this,
720 avatar_menu_.get());
721 item_view->SetAccessibleName(l10n_util::GetStringFUTF16(
722 IDS_PROFILES_SWITCH_TO_PROFILE_ACCESSIBLE_NAME, item.name));
723 item_view->SetFocusable(true);
724 AddChildView(item_view);
725 item_views_.push_back(item_view);
728 if (switches::IsNewProfileManagement()) {
729 separator_ = new views::Separator(views::Separator::HORIZONTAL);
730 AddChildView(separator_);
731 buttons_view_ = new ActionButtonView(this, browser_->profile());
732 AddChildView(buttons_view_);
733 } else if (avatar_menu_->ShouldShowAddNewProfileLink()) {
734 views::Link* add_profile_link = new views::Link(
735 l10n_util::GetStringUTF16(IDS_PROFILES_CREATE_NEW_PROFILE_LINK));
736 add_profile_link->set_listener(this);
737 add_profile_link->SetHorizontalAlignment(gfx::ALIGN_CENTER);
738 add_profile_link->SetBackgroundColor(color());
739 separator_ = new views::Separator(views::Separator::HORIZONTAL);
740 AddChildView(separator_);
741 buttons_view_ = add_profile_link;
742 AddChildView(buttons_view_);
746 void AvatarMenuBubbleView::InitManagedUserContents(
747 AvatarMenu* avatar_menu) {
748 // Show the profile of the managed user.
749 size_t active_index = avatar_menu->GetActiveProfileIndex();
750 const AvatarMenu::Item& item =
751 avatar_menu->GetItemAt(active_index);
752 ProfileItemView* item_view = new ProfileItemView(item,
753 this,
754 avatar_menu_.get());
755 item_view->SetAccessibleName(l10n_util::GetStringFUTF16(
756 IDS_PROFILES_SWITCH_TO_PROFILE_ACCESSIBLE_NAME, item.name));
757 item_views_.push_back(item_view);
758 AddChildView(item_view);
759 separator_ = new views::Separator(views::Separator::HORIZONTAL);
760 AddChildView(separator_);
762 // Add information about managed users.
763 managed_user_info_ =
764 new views::Label(avatar_menu_->GetManagedUserInformation(),
765 ui::ResourceBundle::GetSharedInstance().GetFontList(
766 ui::ResourceBundle::SmallFont));
767 managed_user_info_->SetMultiLine(true);
768 managed_user_info_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
769 managed_user_info_->SetBackgroundColor(color());
770 AddChildView(managed_user_info_);
772 // Add the managed user icon.
773 icon_view_ = new views::ImageView();
774 icon_view_->SetImage(avatar_menu_->GetManagedUserIcon().ToImageSkia());
775 AddChildView(icon_view_);
777 // Add a link for switching profiles.
778 separator_switch_users_ = new views::Separator(views::Separator::HORIZONTAL);
779 AddChildView(separator_switch_users_);
780 switch_profile_link_ = new views::Link(
781 l10n_util::GetStringUTF16(IDS_PROFILES_SWITCH_PROFILE_LINK));
782 switch_profile_link_->set_listener(this);
783 switch_profile_link_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
784 switch_profile_link_->SetBackgroundColor(color());
785 AddChildView(switch_profile_link_);
788 void AvatarMenuBubbleView::OnAvatarMenuChanged(
789 AvatarMenu* avatar_menu) {
790 // Unset all our child view references and call RemoveAllChildViews() which
791 // will actually delete them.
792 buttons_view_ = NULL;
793 managed_user_info_ = NULL;
794 item_views_.clear();
795 RemoveAllChildViews(true);
797 if (avatar_menu_->GetManagedUserInformation().empty() || expanded_)
798 InitMenuContents(avatar_menu);
799 else
800 InitManagedUserContents(avatar_menu);
802 // If the bubble has already been shown then resize and reposition the bubble.
803 Layout();
804 if (GetBubbleFrameView())
805 SizeToContents();
808 void AvatarMenuBubbleView::SetBackgroundColors() {
809 for (size_t i = 0; i < item_views_.size(); ++i) {
810 item_views_[i]->OnHighlightStateChanged();