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 "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" 46 47namespace { 48 49const int kItemHeight = 44; 50const int kItemMarginY = 4; 51const int kIconMarginX = 6; 52const int kSeparatorPaddingY = 5; 53const int kMaxItemTextWidth = 200; 54const SkColor kHighlightColor = 0xFFE3EDF6; 55 56inline int Round(double x) { 57 return static_cast<int>(x + 0.5); 58} 59 60gfx::Rect GetCenteredAndScaledRect(int src_width, int src_height, 61 int dst_x, int dst_y, 62 int dst_width, int dst_height) { 63 int scaled_width; 64 int scaled_height; 65 if (src_width > src_height) { 66 scaled_width = std::min(src_width, dst_width); 67 float scale = static_cast<float>(scaled_width) / 68 static_cast<float>(src_width); 69 scaled_height = Round(src_height * scale); 70 } else { 71 scaled_height = std::min(src_height, dst_height); 72 float scale = static_cast<float>(scaled_height) / 73 static_cast<float>(src_height); 74 scaled_width = Round(src_width * scale); 75 } 76 int x = dst_x + (dst_width - scaled_width) / 2; 77 int y = dst_y + (dst_height - scaled_height) / 2; 78 return gfx::Rect(x, y, scaled_width, scaled_height); 79} 80 81// BadgeImageSource ----------------------------------------------------------- 82class BadgeImageSource: public gfx::CanvasImageSource { 83 public: 84 BadgeImageSource(const gfx::ImageSkia& icon, 85 const gfx::Size& icon_size, 86 const gfx::ImageSkia& badge); 87 88 virtual ~BadgeImageSource(); 89 90 // Overridden from CanvasImageSource: 91 virtual void Draw(gfx::Canvas* canvas) OVERRIDE; 92 93 private: 94 gfx::Size ComputeSize(const gfx::ImageSkia& icon, 95 const gfx::Size& size, 96 const gfx::ImageSkia& badge); 97 98 const gfx::ImageSkia icon_; 99 gfx::Size icon_size_; 100 const gfx::ImageSkia badge_; 101 102 DISALLOW_COPY_AND_ASSIGN(BadgeImageSource); 103}; 104 105BadgeImageSource::BadgeImageSource(const gfx::ImageSkia& icon, 106 const gfx::Size& icon_size, 107 const gfx::ImageSkia& badge) 108 : gfx::CanvasImageSource(ComputeSize(icon, icon_size, badge), false), 109 icon_(icon), 110 icon_size_(icon_size), 111 badge_(badge) { 112} 113 114BadgeImageSource::~BadgeImageSource() { 115} 116 117void BadgeImageSource::Draw(gfx::Canvas* canvas) { 118 canvas->DrawImageInt(icon_, 0, 0, icon_.width(), icon_.height(), 0, 0, 119 icon_size_.width(), icon_size_.height(), true); 120 canvas->DrawImageInt(badge_, size().width() - badge_.width(), 121 size().height() - badge_.height()); 122} 123 124gfx::Size BadgeImageSource::ComputeSize(const gfx::ImageSkia& icon, 125 const gfx::Size& icon_size, 126 const gfx::ImageSkia& badge) { 127 const float kBadgeOverlapRatioX = 1.0f / 5.0f; 128 int width = icon_size.width() + badge.width() * kBadgeOverlapRatioX; 129 const float kBadgeOverlapRatioY = 1.0f / 3.0f; 130 int height = icon_size.height() + badge.height() * kBadgeOverlapRatioY; 131 return gfx::Size(width, height); 132} 133 134// HighlightDelegate ---------------------------------------------------------- 135 136// Delegate to callback when the highlight state of a control changes. 137class HighlightDelegate { 138 public: 139 virtual ~HighlightDelegate() {} 140 virtual void OnHighlightStateChanged() = 0; 141 virtual void OnFocusStateChanged(bool has_focus) = 0; 142}; 143 144 145// EditProfileLink ------------------------------------------------------------ 146 147// A custom Link control that forwards highlight state changes. We need to do 148// this to make sure that the ProfileItemView looks highlighted even when 149// the mouse is over this link. 150class EditProfileLink : public views::Link { 151 public: 152 explicit EditProfileLink(const 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 167EditProfileLink::EditProfileLink(const base::string16& title, 168 HighlightDelegate* delegate) 169 : views::Link(title), 170 delegate_(delegate), 171 state_(views::CustomButton::STATE_NORMAL) { 172} 173 174void EditProfileLink::OnMouseEntered(const ui::MouseEvent& event) { 175 views::Link::OnMouseEntered(event); 176 state_ = views::CustomButton::STATE_HOVERED; 177 delegate_->OnHighlightStateChanged(); 178} 179 180void EditProfileLink::OnMouseExited(const ui::MouseEvent& event) { 181 views::Link::OnMouseExited(event); 182 state_ = views::CustomButton::STATE_NORMAL; 183 delegate_->OnHighlightStateChanged(); 184} 185 186void EditProfileLink::OnFocus() { 187 views::Link::OnFocus(); 188 delegate_->OnFocusStateChanged(true); 189} 190 191void EditProfileLink::OnBlur() { 192 views::Link::OnBlur(); 193 state_ = views::CustomButton::STATE_NORMAL; 194 delegate_->OnFocusStateChanged(false); 195} 196 197 198// ProfileImageView ----------------------------------------------------------- 199 200// A custom image view that ignores mouse events so that the parent can receive 201// them instead. 202class ProfileImageView : public views::ImageView { 203 public: 204 // views::View: 205 virtual bool CanProcessEventsWithinSubtree() const OVERRIDE; 206}; 207 208bool ProfileImageView::CanProcessEventsWithinSubtree() const { 209 // Send events to the parent view for handling. 210 return false; 211} 212 213} // namespace 214 215// ProfileItemView ------------------------------------------------------------ 216 217// Control that shows information about a single profile. 218class ProfileItemView : public views::CustomButton, 219 public HighlightDelegate { 220 public: 221 ProfileItemView(const AvatarMenu::Item& item, 222 AvatarMenuBubbleView* parent, 223 AvatarMenu* menu); 224 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; 231 232 virtual void OnHighlightStateChanged() OVERRIDE; 233 virtual void OnFocusStateChanged(bool has_focus) OVERRIDE; 234 235 const AvatarMenu::Item& item() const { return item_; } 236 EditProfileLink* edit_link() { return edit_link_; } 237 238 private: 239 gfx::ImageSkia GetBadgedIcon(const gfx::ImageSkia& icon); 240 241 bool IsHighlighted(); 242 243 AvatarMenu::Item item_; 244 AvatarMenuBubbleView* parent_; 245 AvatarMenu* menu_; 246 views::ImageView* image_view_; 247 views::Label* name_label_; 248 views::Label* sync_state_label_; 249 EditProfileLink* edit_link_; 250 251 DISALLOW_COPY_AND_ASSIGN(ProfileItemView); 252}; 253 254ProfileItemView::ProfileItemView(const AvatarMenu::Item& item, 255 AvatarMenuBubbleView* parent, 256 AvatarMenu* menu) 257 : views::CustomButton(parent), 258 item_(item), 259 parent_(parent), 260 menu_(menu) { 261 set_notify_enter_exit_on_child(true); 262 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)); 269 else 270 image_view_->SetImage(profile_icon); 271 AddChildView(image_view_); 272 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_); 281 282 // Add a label to show the sync state. 283 sync_state_label_ = new views::Label(item_.sync_state); 284 if (item_.signed_in) 285 sync_state_label_->SetElideBehavior(gfx::ELIDE_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_); 291 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_); 298 299 OnHighlightStateChanged(); 300} 301 302gfx::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, 308 kItemHeight); 309} 310 311void ProfileItemView::Layout() { 312 // Profile icon. 313 gfx::Rect icon_rect; 314 if (item_.active) { 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); 319 } else { 320 const gfx::ImageSkia& icon = image_view_->GetImage(); 321 icon_rect = GetCenteredAndScaledRect(icon.width(), icon.height(), 0, 0, 322 profiles::kAvatarIconWidth, height()); 323 } 324 image_view_->SetBoundsRect(icon_rect); 325 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)); 334 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()); 340 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()); 347} 348 349void ProfileItemView::OnMouseEntered(const ui::MouseEvent& event) { 350 views::CustomButton::OnMouseEntered(event); 351 OnHighlightStateChanged(); 352} 353 354void ProfileItemView::OnMouseExited(const ui::MouseEvent& event) { 355 views::CustomButton::OnMouseExited(event); 356 OnHighlightStateChanged(); 357} 358 359void ProfileItemView::OnFocus() { 360 views::CustomButton::OnFocus(); 361 OnFocusStateChanged(true); 362} 363 364void ProfileItemView::OnBlur() { 365 views::CustomButton::OnBlur(); 366 OnFocusStateChanged(false); 367} 368 369void 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); 375 376 bool show_edit = IsHighlighted() && item_.active && 377 menu_->ShouldShowEditProfileLink(); 378 sync_state_label_->SetVisible(!show_edit); 379 edit_link_->SetVisible(show_edit); 380 SchedulePaint(); 381} 382 383void ProfileItemView::OnFocusStateChanged(bool has_focus) { 384 if (!has_focus && state() != views::CustomButton::STATE_DISABLED) 385 SetState(views::CustomButton::STATE_NORMAL); 386 OnHighlightStateChanged(); 387} 388 389// static 390gfx::ImageSkia ProfileItemView::GetBadgedIcon(const gfx::ImageSkia& icon) { 391 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance(); 392 const gfx::ImageSkia* badge = NULL; 393 394 if (item_.active) 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); 398 else 399 NOTREACHED(); // function should only be called if one of above is true 400 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()); 407} 408 409bool 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 || 414 HasFocus() || 415 edit_link_->HasFocus(); 416} 417 418 419// ActionButtonView ----------------------------------------------------------- 420 421// A custom view that manages the "action" buttons at the bottom of the list 422// of profiles. 423class ActionButtonView : public views::View { 424 public: 425 ActionButtonView(views::ButtonListener* listener, Profile* profile); 426 427 private: 428 views::LabelButton* manage_button_; 429 views::LabelButton* signout_button_; 430 431 DISALLOW_COPY_AND_ASSIGN(ActionButtonView); 432}; 433 434 435ActionButtonView::ActionButtonView(views::ButtonListener* listener, 436 Profile* profile) 437 : manage_button_(NULL), 438 signout_button_(NULL) { 439 std::string username; 440 SigninManagerBase* signin = 441 SigninManagerFactory::GetForProfile(profile); 442 if (signin != NULL) 443 username = signin->GetAuthenticatedUsername(); 444 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); 451 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); 460 } else { 461 signout_button_->SetTooltipText( 462 l10n_util::GetStringFUTF16(IDS_PROFILES_PROFILE_SIGNOUT_BUTTON_TIP, 463 base::UTF8ToUTF16(username))); 464 } 465 signout_button_->set_tag(IDS_PROFILES_PROFILE_SIGNOUT_BUTTON); 466 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); 477} 478 479 480// AvatarMenuBubbleView ------------------------------------------------------- 481 482// static 483AvatarMenuBubbleView* AvatarMenuBubbleView::avatar_bubble_ = NULL; 484bool AvatarMenuBubbleView::close_on_deactivate_for_testing_ = true; 485 486// static 487void 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, 493 Browser* browser) { 494 if (IsShowing()) 495 return; 496 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(); 506} 507 508// static 509bool AvatarMenuBubbleView::IsShowing() { 510 return avatar_bubble_ != NULL; 511} 512 513// static 514void AvatarMenuBubbleView::Hide() { 515 if (IsShowing()) 516 avatar_bubble_->GetWidget()->Close(); 517} 518 519AvatarMenuBubbleView::AvatarMenuBubbleView( 520 views::View* anchor_view, 521 views::BubbleBorder::Arrow arrow, 522 const gfx::Rect& anchor_rect, 523 Browser* browser) 524 : BubbleDelegateView(anchor_view, arrow), 525 anchor_rect_(anchor_rect), 526 browser_(browser), 527 separator_(NULL), 528 buttons_view_(NULL), 529 supervised_user_info_(NULL), 530 separator_switch_users_(NULL), 531 expanded_(false) { 532 avatar_menu_.reset(new AvatarMenu( 533 &g_browser_process->profile_manager()->GetProfileInfoCache(), 534 this, 535 browser_)); 536 avatar_menu_->RebuildMenu(); 537} 538 539AvatarMenuBubbleView::~AvatarMenuBubbleView() { 540} 541 542gfx::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); 549 } 550 551 if (buttons_view_) { 552 preferred_size.Enlarge( 553 0, kSeparatorPaddingY * 2 + separator_->GetPreferredSize().height()); 554 555 gfx::Size buttons_size = buttons_view_->GetPreferredSize(); 556 preferred_size.Enlarge(0, buttons_size.height()); 557 preferred_size.SetToMax(buttons_size); 558 } 559 560 561 if (supervised_user_info_) { 562 // First handle the switch profile link because it can still affect the 563 // preferred width. 564 gfx::Size size = switch_profile_link_->GetPreferredSize(); 565 preferred_size.Enlarge(0, size.height()); 566 preferred_size.SetToMax(size); 567 568 // Add the height of the two separators. 569 preferred_size.Enlarge( 570 0, 571 kSeparatorPaddingY * 4 + separator_->GetPreferredSize().height() * 2); 572 } 573 574 const int kBubbleViewMaxWidth = 800; 575 preferred_size.SetToMin( 576 gfx::Size(kBubbleViewMaxWidth, preferred_size.height())); 577 578 // We have to do this after the final width is calculated, since the label 579 // will wrap based on the width. 580 if (supervised_user_info_) { 581 int remaining_width = 582 preferred_size.width() - icon_view_->GetPreferredSize().width() - 583 views::kRelatedControlSmallHorizontalSpacing; 584 preferred_size.Enlarge( 585 0, 586 supervised_user_info_->GetHeightForWidth(remaining_width) + 587 kItemMarginY); 588 } 589 590 return preferred_size; 591} 592 593void AvatarMenuBubbleView::Layout() { 594 int y = 0; 595 for (size_t i = 0; i < item_views_.size(); ++i) { 596 views::CustomButton* item_view = item_views_[i]; 597 int item_height = item_view->GetPreferredSize().height(); 598 int item_width = width(); 599 item_view->SetBounds(0, y, item_width, item_height); 600 y += item_height + kItemMarginY; 601 } 602 603 int separator_height; 604 if (buttons_view_ || supervised_user_info_) { 605 separator_height = separator_->GetPreferredSize().height(); 606 y += kSeparatorPaddingY; 607 separator_->SetBounds(0, y, width(), separator_height); 608 y += kSeparatorPaddingY + separator_height; 609 } 610 611 if (buttons_view_) { 612 buttons_view_->SetBounds(0, y, 613 width(), buttons_view_->GetPreferredSize().height()); 614 } else if (supervised_user_info_) { 615 gfx::Size icon_size = icon_view_->GetPreferredSize(); 616 gfx::Rect icon_bounds(0, y, icon_size.width(), icon_size.height()); 617 icon_view_->SetBoundsRect(icon_bounds); 618 int info_width = width() - icon_bounds.right() - 619 views::kRelatedControlSmallHorizontalSpacing; 620 int height = supervised_user_info_->GetHeightForWidth(info_width); 621 supervised_user_info_->SetBounds( 622 icon_bounds.right() + views::kRelatedControlSmallHorizontalSpacing, 623 y, info_width, height); 624 y += height + kItemMarginY + kSeparatorPaddingY; 625 separator_switch_users_->SetBounds(0, y, width(), separator_height); 626 y += separator_height + kSeparatorPaddingY; 627 int link_height = switch_profile_link_->GetPreferredSize().height(); 628 switch_profile_link_->SetBounds(0, y, width(), link_height); 629 } 630} 631 632bool AvatarMenuBubbleView::AcceleratorPressed( 633 const ui::Accelerator& accelerator) { 634 if (accelerator.key_code() != ui::VKEY_DOWN && 635 accelerator.key_code() != ui::VKEY_UP) 636 return BubbleDelegateView::AcceleratorPressed(accelerator); 637 638 if (item_views_.empty()) 639 return true; 640 641 // Find the currently focused item. Note that if there is no focused item, the 642 // code below correctly handles a |focus_index| of -1. 643 int focus_index = -1; 644 for (size_t i = 0; i < item_views_.size(); ++i) { 645 if (item_views_[i]->HasFocus()) { 646 focus_index = i; 647 break; 648 } 649 } 650 651 // Moved the focus up or down by 1. 652 if (accelerator.key_code() == ui::VKEY_DOWN) 653 focus_index = (focus_index + 1) % item_views_.size(); 654 else 655 focus_index = ((focus_index > 0) ? focus_index : item_views_.size()) - 1; 656 item_views_[focus_index]->RequestFocus(); 657 658 return true; 659} 660 661void AvatarMenuBubbleView::ButtonPressed(views::Button* sender, 662 const ui::Event& event) { 663 if (sender->tag() == IDS_PROFILES_MANAGE_PROFILES_BUTTON) { 664 std::string subpage = chrome::kSearchUsersSubPage; 665 chrome::ShowSettingsSubPage(browser_, subpage); 666 return; 667 } else if (sender->tag() == IDS_PROFILES_PROFILE_SIGNOUT_BUTTON) { 668 profiles::LockProfile(browser_->profile()); 669 return; 670 } 671 672 for (size_t i = 0; i < item_views_.size(); ++i) { 673 ProfileItemView* item_view = item_views_[i]; 674 if (sender == item_view) { 675 // Clicking on the active profile shouldn't do anything. 676 if (!item_view->item().active) { 677 avatar_menu_->SwitchToProfile( 678 i, ui::DispositionFromEventFlags(event.flags()) == NEW_WINDOW, 679 ProfileMetrics::SWITCH_PROFILE_ICON); 680 } 681 break; 682 } 683 } 684} 685 686void AvatarMenuBubbleView::LinkClicked(views::Link* source, int event_flags) { 687 if (source == buttons_view_) { 688 avatar_menu_->AddNewProfile(ProfileMetrics::ADD_NEW_USER_ICON); 689 return; 690 } 691 if (source == switch_profile_link_) { 692 expanded_ = true; 693 OnAvatarMenuChanged(avatar_menu_.get()); 694 return; 695 } 696 697 for (size_t i = 0; i < item_views_.size(); ++i) { 698 ProfileItemView* item_view = item_views_[i]; 699 if (source == item_view->edit_link()) { 700 avatar_menu_->EditProfile(i); 701 return; 702 } 703 } 704} 705 706gfx::Rect AvatarMenuBubbleView::GetAnchorRect() const { 707 return anchor_rect_; 708} 709 710void AvatarMenuBubbleView::Init() { 711 // Build the menu for the first time. 712 OnAvatarMenuChanged(avatar_menu_.get()); 713 AddAccelerator(ui::Accelerator(ui::VKEY_DOWN, ui::EF_NONE)); 714 AddAccelerator(ui::Accelerator(ui::VKEY_UP, ui::EF_NONE)); 715} 716 717void AvatarMenuBubbleView::WindowClosing() { 718 DCHECK_EQ(avatar_bubble_, this); 719 avatar_bubble_ = NULL; 720} 721 722void AvatarMenuBubbleView::InitMenuContents( 723 AvatarMenu* avatar_menu) { 724 for (size_t i = 0; i < avatar_menu->GetNumberOfItems(); ++i) { 725 const AvatarMenu::Item& item = avatar_menu->GetItemAt(i); 726 ProfileItemView* item_view = new ProfileItemView(item, 727 this, 728 avatar_menu_.get()); 729 item_view->SetAccessibleName(l10n_util::GetStringFUTF16( 730 IDS_PROFILES_SWITCH_TO_PROFILE_ACCESSIBLE_NAME, item.name)); 731 item_view->SetFocusable(true); 732 AddChildView(item_view); 733 item_views_.push_back(item_view); 734 } 735 736 if (avatar_menu_->ShouldShowAddNewProfileLink()) { 737 views::Link* add_profile_link = new views::Link( 738 l10n_util::GetStringUTF16(IDS_PROFILES_CREATE_NEW_PROFILE_LINK)); 739 add_profile_link->set_listener(this); 740 add_profile_link->SetHorizontalAlignment(gfx::ALIGN_CENTER); 741 add_profile_link->SetBackgroundColor(color()); 742 separator_ = new views::Separator(views::Separator::HORIZONTAL); 743 AddChildView(separator_); 744 buttons_view_ = add_profile_link; 745 AddChildView(buttons_view_); 746 } 747} 748 749void AvatarMenuBubbleView::InitSupervisedUserContents( 750 AvatarMenu* avatar_menu) { 751 // Show the profile of the supervised user. 752 size_t active_index = avatar_menu->GetActiveProfileIndex(); 753 const AvatarMenu::Item& item = 754 avatar_menu->GetItemAt(active_index); 755 ProfileItemView* item_view = new ProfileItemView(item, 756 this, 757 avatar_menu_.get()); 758 item_view->SetAccessibleName(l10n_util::GetStringFUTF16( 759 IDS_PROFILES_SWITCH_TO_PROFILE_ACCESSIBLE_NAME, item.name)); 760 item_views_.push_back(item_view); 761 AddChildView(item_view); 762 separator_ = new views::Separator(views::Separator::HORIZONTAL); 763 AddChildView(separator_); 764 765 // Add information about supervised users. 766 supervised_user_info_ = 767 new views::Label(avatar_menu_->GetSupervisedUserInformation(), 768 ui::ResourceBundle::GetSharedInstance().GetFontList( 769 ui::ResourceBundle::SmallFont)); 770 supervised_user_info_->SetMultiLine(true); 771 supervised_user_info_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 772 supervised_user_info_->SetBackgroundColor(color()); 773 AddChildView(supervised_user_info_); 774 775 // Add the supervised user icon. 776 icon_view_ = new views::ImageView(); 777 icon_view_->SetImage(avatar_menu_->GetSupervisedUserIcon().ToImageSkia()); 778 AddChildView(icon_view_); 779 780 // Add a link for switching profiles. 781 separator_switch_users_ = new views::Separator(views::Separator::HORIZONTAL); 782 AddChildView(separator_switch_users_); 783 switch_profile_link_ = new views::Link( 784 l10n_util::GetStringUTF16(IDS_PROFILES_SWITCH_PROFILE_LINK)); 785 switch_profile_link_->set_listener(this); 786 switch_profile_link_->SetHorizontalAlignment(gfx::ALIGN_CENTER); 787 switch_profile_link_->SetBackgroundColor(color()); 788 AddChildView(switch_profile_link_); 789} 790 791void AvatarMenuBubbleView::OnAvatarMenuChanged( 792 AvatarMenu* avatar_menu) { 793 // Unset all our child view references and call RemoveAllChildViews() which 794 // will actually delete them. 795 buttons_view_ = NULL; 796 supervised_user_info_ = NULL; 797 item_views_.clear(); 798 RemoveAllChildViews(true); 799 800 if (avatar_menu_->GetSupervisedUserInformation().empty() || expanded_) 801 InitMenuContents(avatar_menu); 802 else 803 InitSupervisedUserContents(avatar_menu); 804 805 // If the bubble has already been shown then resize and reposition the bubble. 806 Layout(); 807 if (GetBubbleFrameView()) 808 SizeToContents(); 809} 810 811void AvatarMenuBubbleView::SetBackgroundColors() { 812 for (size_t i = 0; i < item_views_.size(); ++i) { 813 item_views_[i]->OnHighlightStateChanged(); 814 } 815} 816